# 将 Python SDK 连接到 Unsloth

Unsloth 在同一个基础 URL 上提供三种与 OpenAI 兼容的方言：Chat Completions、Responses 和 Anthropic Messages，因此所有主流的 Python SDK 都可以与其配合使用。\
\
你只需要更改客户端上的 `base_url` 和 `api_key`  ；其余一切（流式传输、工具调用、视觉、结构化输出）都按照 SDK 文档所描述的方式工作。本页介绍开发者最先常用的两个 SDK：官方的 **OpenAI Python SDK** 和官方的 **Anthropic Python SDK**.

{% hint style="info" %}
如果你不确定该使用什么 URL / 密钥 / 模型名称，请先阅读 API 概览。它会引导你完成启动、加载模型以及创建一个 `sk-unsloth-…` 密钥。
{% endhint %}

### 🔑 前置条件

在运行下面的任何代码片段之前，你需要：

* **本地运行的 Unsloth** 并已加载一个模型（注意端口：通常是 `8000` 或 `8888`).
* **一个 `sk-unsloth-…` API 密钥** 创建自 **设置 → API**.
* **一个模型名称。** Unsloth 内部 GGUF 模型的名称（例如 `qwen-local`, `unsloth/Qwen3.6-27B-GGUF`）。如果你忘了，运行：

  ```bash
  curl http://localhost:8888/v1/models -H "Authorization: Bearer sk-unsloth-…"
  ```

  并复制 `id` 字段。

将密钥设置为环境变量，这样你就不必把它粘贴到代码中：

```bash
export UNSLOTH_STUDIO_AUTH_TOKEN=sk-unsloth-xxxxxxxxxxxx
```

#### 🤖 OpenAI SDK

Unsloth 的 `/v1/chat/completions` 端点可直接替代 OpenAI Python SDK。客户端会将 Unsloth 视为任何其他兼容 OpenAI 的提供方。

**1. 安装 SDK：**

```bash
pip install openai
```

**2. 创建一个客户端** 并将其指向 Unsloth：

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8888/v1",              # 你的 unsloth 端口 + /v1
    api_key=os.environ["UNSLOTH_STUDIO_AUTH_TOKEN"],     # 你的 sk-unsloth-… 密钥
)
```

#### 基础聊天补全

```python
response = client.chat.completions.create(
    model="default",                               # 你在 unsloth 中为模型指定的名称，或者使用 default
    messages=[
        {"role": "user", "content": "给我两个关于巴黎的事实"}
    ],
)
print(response.choices[0].message.content)
```

<figure><img src="/files/b25c83bed55a3a2fbe0b174082537730e922d66e" alt=""><figcaption></figcaption></figure>

#### 流式传输

设置 `stream=True` 并遍历返回的生成器：

```python
stream = client.chat.completions.create(
    model="qwen-local",
    messages=[{"role": "user", "content": "写一首关于本地运行 LLM 的俳句。"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices:
        delta = chunk.choices[0].delta.content
        if delta:
            print(delta, end="", flush=True)
```

<figure><img src="/files/7c0d4d294d3a4f095f7579d9fcebe50b358517dd" alt=""><figcaption></figcaption></figure>

#### 图像（视觉）

将图像附加为一个 `image_url` 内容部分。Unsloth 同时接受 HTTP(S) URL 或 `data:` base64 URI：

```python
import base64
from pathlib import Path

img_b64 = base64.b64encode(Path("test.jpg").read_bytes()).decode()

response = client.chat.completions.create(
    model="default",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"},
                },
                {"type": "text", "text": "这张图片里有什么？"},
            ],
        }
    ],
)
print(response.choices[0].message.content)
```

{% hint style="info" %}
已加载的模型必须是多模态的。如果你加载的是纯文本模型，视觉请求在结构上会成功，但模型将无法“看到”图像。
{% endhint %}

<figure><img src="/files/ba97778dc258b749b3399352b3ce68e9ffcef919" alt=""><figcaption></figcaption></figure>

#### 函数调用（OpenAI 工具）

传入 OpenAI 风格的 `tools` 以及（可选的） `tool_choice` ，Unsloth 会将它们转发到后端。你的客户端负责执行每个工具调用，并在下一轮返回结果：

```python
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取某个城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称，例如 'Paris'"},
                },
                "required": ["city"],
            },
        },
    }
]

response = client.chat.completions.create(
    model="default",
    messages=[{"role": "user", "content": "珀斯现在的天气怎么样？"}],
    tools=tools,
    tool_choice="auto",
)

tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name, tool_call.function.arguments)
```

<figure><img src="/files/7c20113057a2ae6cafb2b75dbf87da0c19deca2b" alt=""><figcaption></figcaption></figure>

#### Unsloth 服务端工具（简写）

除了 OpenAI 风格的客户端工具外，Unsloth 还可以在服务端执行 **Python**, **bash**，以及 **网页搜索** ，并自动将结果流式返回。通过 `extra_body` 参数启用，这样这些字段会直接透传给 Unsloth：

```python
stream = client.chat.completions.create(
    model="default",
    messages=[{"role": "user", "content": "123 * 456 等于多少？请使用 Python 计算。"}],
    stream=True,
    extra_body={
        "enable_tools": True,
        "enabled_tools": ["python", "web_search"],
        "session_id": "my-session",
    },
)
for chunk in stream:
    if chunk.choices:
        delta = chunk.choices[0].delta.content
        if delta:
            print(delta, end="", flush=True)
```

<figure><img src="/files/bb2ebf08a4dce7fb07e8f7e2002003f33f9df992" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fd2243da7fe17c1c222835ec902327cf5166c648" alt=""><figcaption></figcaption></figure>

这个 `session_id` 是可选的。使用它可以在多次调用之间持久化工具状态（例如 Python 内核）。

{% hint style="info" %}
`enabled_tools` 当前支持 `"python"`, `"bash"`，以及 `"web_search"`。工具结果会作为 `tool_result` 事件流式返回，这样模型在下一轮就能看到它们。
{% endhint %}

**列出模型**

```python
models = client.models.list()
for m in models.data:
    print(m.id)
```

<figure><img src="/files/3e68ee298728c5ef0eedf658691fd33fdc748621" alt=""><figcaption></figcaption></figure>

#### 🧠 Anthropic SDK

Unsloth 的 `/v1/messages` 端点可直接替代 Anthropic Python SDK。

**1. 安装 SDK：**

```bash
pip install anthropic
```

**2. 创建一个客户端** 并将其指向 Unsloth：

```python
import os
from anthropic import Anthropic

client = Anthropic(
    base_url="http://localhost:8888",                 # 你的 unsloth 端口（这里不要加 /v1 —— SDK 会自己加上）
    api_key="dummy",                                  # 任意一个非空随机值
    default_headers={"Authorization": "Bearer {os.environ['UNSLOTH_STUDIO_AUTH_TOKEN']}"} # 你的 sk-unsloth-… 密钥
)
```

#### 基础消息

```python
message = client.messages.create(
    model="default",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "用三种语言说你好。"}
    ],
)
print(message.content[0].text)
```

<figure><img src="/files/8c92f6d9c5690fab7679b1d83c74a12dee962d03" alt=""><figcaption></figcaption></figure>

#### 流式传输

该 SDK 提供了一个上下文管理器，用于产出文本增量：

```python
with client.messages.stream(
    model="default",
    max_tokens=1024,
    messages=[{"role": "user", "content": "用两句话解释 LoRA。"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
```

#### 图像（视觉）

Anthropic 风格的图像内容使用一个 `source` 块，并带有 base64 数据：

```python
import base64
from pathlib import Path

img_b64 = base64.standard_b64encode(Path("photo.jpg").read_bytes()).decode()

message = client.messages.create(
    model="default",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": img_b64,
                    },
                },
                {"type": "text", "text": "这张图片里有什么？"},
            ],
        }
    ],
)
print(message.content[0].text)
```

<figure><img src="/files/bc4742f79526760202866ac1b5c04b4af1454eb9" alt=""><figcaption></figcaption></figure>

#### 工具调用（Anthropic 工具）

传入 Anthropic 风格的 `tools` 并带有一个 `input_schema` ，Unsloth 会以原生方式转发它们：

```python
tools = [
    {
        "name": "get_weather",
        "description": "获取某个城市的当前天气",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名称，例如 'Tokyo'"},
            },
            "required": ["city"],
        },
    }
]

message = client.messages.create(
    model="default",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "东京的天气怎么样？"}],
)

for block in message.content:
    if block.type == "tool_use":
        print(block.name, block.input)
```

<figure><img src="/files/6f32b32383bfc3aa91a69c46f3c732880b2df72d" alt=""><figcaption></figcaption></figure>

#### Unsloth 服务端工具（简写）

同样的 `enable_tools` / `enabled_tools` / `session_id` 简写也适用于 `/v1/messages` 直接透传 `extra_body`:

```python
with client.messages.stream(
    model="default",
    max_tokens=1024,
    messages=[{"role": "user", "content": "搜索 Python 3.13 的特性并进行总结。"}],
    extra_body={
        "enable_tools": True,
        "enabled_tools": ["web_search", "python"],
        "session_id": "my-session",
    },
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
```

<figure><img src="/files/d7f50915a4473553530f9e612ae5753514a7e2a0" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/90b989d3055866e60ebde785a4c13fb92b89c6b3" alt=""><figcaption></figcaption></figure>

Unsloth 会发出自定义的 `tool_result` SSE 事件，用于表示模型视角下每次工具调用输出的内容。Anthropic SDK 会将这些事件原样透传到其事件流中。

#### JSON 解码（`response_format`)

Unsloth 通过 `response_format`支持 OpenAI 风格的结构化输出。传入一个 JSON Schema，模型就会被约束为生成与之匹配的 JSON。

````python
import json
import os
import re
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8888/v1",
    api_key=os.environ["UNSLOTH_STUDIO_AUTH_TOKEN"],
)

response = client.chat.completions.create(
    model="default",
    stream=False,
    temperature=0.0,
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "选择一个国家：日本、埃及或秘鲁。用一句话解释原因。",
        },
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "country_pick",
            "schema": {
                "type": "object",
                "properties": {
                    "country": {"type": "string", "enum": ["Japan", "Egypt", "Peru"]},
                    "reason":  {"type": "string"},
                },
                "required": ["country", "reason"],
                "additionalProperties": False,
            },
            "strict": True,
        },
    },
)

raw = response.choices[0].message.content
# 去掉 Gemma 4 包裹在 JSON 外层的 markdown 围栏，然后解析。
cleaned = re.sub(r"^```(?:json)?\s*", "", raw)
cleaned = re.sub(r"\s*```$", "", cleaned)
parsed = json.loads(cleaned)

print(json.dumps(parsed, indent=2))
print()
print("country:", parsed["country"])
print("reason :", parsed["reason"] )
````

这个 `strict: True` 这个标志告诉 Unsloth 在解码过程中强制执行 schema，而不是依赖模型自行遵守。 `additionalProperties: False` 和 `required` 的工作方式与标准 JSON Schema 一致。

终端输出大致应如下所示：

<figure><img src="/files/bedb8a1a458eb63057ae0b0ebf2fc5d9020d93ae" alt=""><figcaption></figcaption></figure>

## 去掉 Gemma 4 包裹在 JSON 外层的 markdown 围栏，然后解析。

cleaned = re.sub(r"^`(?:json)?\s*", "", raw) cleaned = re.sub(r"\s*`$", "", cleaned) parsed = json.loads(cleaned)

print(json.dumps(parsed, indent=2)) print() print("country:", parsed\["country"]) print("reason :", parsed\["reason"] )

### 🧪 选择 SDK

这两个 SDK 都可以配合 Unsloth 使用。正确的选择取决于你的其余技术栈：

* 使用 **OpenAI SDK** 如果你的代码已经依赖 OpenAI Python 包，你想要 OpenAI 风格的 `tools` / `tool_choice`，或者你计划调用 Responses API。
* 使用 **Anthropic SDK** 如果你的代码已经依赖 Anthropic 包，你更喜欢 Anthropic 的 `input_schema` 工具格式，或者你想使用 Anthropic 原生的流式事件类型。

你也可以在同一个项目中同时使用两者。Unsloth 会在同一个端口上提供它们，因此单个 `sk-unsloth-…` 密钥即可同时进行身份验证。

### ❔ 故障排查

**`401 未授权`**  这个 `UNSLOTH_STUDIO_API_KEY` 环境变量未设置，或者密钥错误。重新导出并用以下命令确认： `echo $UNSLOTH_STUDIO_API_KEY`.

**`404 Not Found` 来自 OpenAI SDK** 检查 `base_url` 是否以 `/v1`结尾。OpenAI SDK 会按原样将端点路径附加到基础 URL。

**`404 Not Found` 来自 Anthropic SDK** 检查 `base_url` 应该 **不** 以 `/v1`结尾。Anthropic SDK 会自己添加 `/v1/messages` 。

**`extra_body` 字段没有传到 Unsloth** 请确保你使用的是较新的 `openai` / `anthropic` SDK。旧版本会静默丢弃未知字段。使用以下命令升级： `pip install -U openai anthropic`.

**流式传输“卡住”然后一次性全部输出** 包装你输出内容的东西正在进行缓冲。在脚本中， `print(..., flush=True)`；在 notebook 中通常没问题；如果经过代理，请在代理上禁用响应缓冲。

对于端点级别的问题（错误端口、模型未加载、连接丢失等），请参阅 API 概览页面。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unsloth.ai/docs/zh/ji-cheng/jiang-python-sdk-lian-jie-dao-unsloth.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
