Transformers documentation

Serve CLI

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Serve CLI

transformers serve CLI 是本地或自托管服务器的轻量级选项。它避免了 vLLM 等专用推理引擎带来的额外运行时和运维开销。适用于评估、实验以及中等负载的部署场景。连续批处理等功能可提高吞吐量并降低延迟。

对于大规模生产部署,请使用 vLLM 或 SGLang,并以 Transformer 模型作为后端。有关更多信息,请参阅 推理后端 指南。

transformers serve 命令会启动一个与 OpenAI SDK 兼容的本地服务器。该服务器可与许多第三方应用程序配合使用,并支持以下 REST API。

  • 用于文本、图像、音频和视频请求的 /v1/chat/completions
  • /v1/completions 用于从自由格式提示词生成传统文本补全
  • /v1/responses 支持 Responses API
  • /v1/audio/transcriptions 用于音频转录
  • /v1/systemone 回答类型化问题(选择题、评分题、是/否题),并为每个允许的答案提供概率
  • /v1/models 列出可用于第三方集成的模型
  • /load_model 通过 SSE 流式传输模型加载进度

安装服务依赖项。

pip install transformers[serving]

运行 transformers serve 以启动服务器。默认服务器地址为 http://localhost:8000。

transformers serve

v1/chat/completions

v1/chat/completions API 基于 Chat Completions API。它支持针对 LLM、VLM 和多模态模型的文本、图像、音频和视频请求。您可以使用 curl、InferenceClient 或 OpenAI 客户端来调用该 API。

基于文本的补全

huggingface_hub
huggingface_hub(流式)
openai
openai(流式)
curl
curl(流式)
from huggingface_hub import InferenceClient

messages = [{"role": "user", "content": "What is the Transformers library known for?"}]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="Qwen/Qwen2.5-0.5B-Instruct", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient 会返回一个打印的字符串。

The Transformers library is primarily known for its ability to create and manipulate large-scale language models [...]

基于图像的补全

huggingface_hub
huggingface_hub(流式)
openai
openai(流式)
curl
curl(流式)
from huggingface_hub import InferenceClient

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {
                "type": "image_url",
                "image_url": {
                    "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg",
                }
            },
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="Qwen/Qwen2.5-VL-7B-Instruct", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient 会返回一个打印的字符串。

The image depicts an astronaut in a space suit standing on what appears to be the surface of the moon, given the barren, rocky landscape and the dark sky in the background. The astronaut is holding a large egg that has cracked open, revealing a small creature inside. The scene is imaginative and playful, combining elements of space exploration with a whimsical twist involving the egg and the creature.

基于音频的补全

像 Gemma 4 和 Qwen2.5-Omni 这样的多模态模型,可以通过 OpenAI 的 input_audio 内容类型接收音频输入。请将音频进行 Base64 编码,并指定格式(mp3 或 wav)。

huggingface_hub
huggingface_hub(流式)
openai
openai(流式)
curl
curl(流式)
import base64
from huggingface_hub.utils import httpx
from huggingface_hub import InferenceClient

audio_url = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"
audio_b64 = base64.b64encode(httpx.get(audio_url, follow_redirects=True).content).decode()

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Transcribe this audio."},
            {"type": "input_audio", "input_audio": {"data": audio_b64, "format": "mp3"}},
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="google/gemma-4-E2B-it", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient 会返回一个打印的字符串。

This week, I traveled to Chicago to deliver my final farewell address to the nation, following in the tradition of presidents before me. It was an opportunity to say thank you. Whether we've seen eye to eye or rarely agreed at all, my conversations with you, the American people, in living rooms and schools, at farms and on factory floors, at diners, and on distant military outposts, all these conversations are what have kept me honest.

audio_url 内容类型是一种扩展,不属于 OpenAI 标准,未来版本中可能会发生变化。

你还可以通过 audio_url 内容类型以 URL 形式传入音频,以跳过 base64 编码。

completion = client.chat.completions.create(
    model="google/gemma-4-E2B-it",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Transcribe this audio."},
                {"type": "audio_url", "audio_url": {"url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"}},
            ],
        }
    ],
)

基于视频的补全

video_url 内容类型是 OpenAI 标准之外的扩展,未来版本中可能会发生变化。

使用 video_url 内容类型进行视频输入。如果模型支持音频(例如 Gemma 4、Qwen2.5-Omni),服务器会从视频中提取音轨,并将其与视觉帧一起处理。

视频处理需要 torchcodec。请使用 pip install torchcodec 进行安装。

huggingface_hub
huggingface_hub(流式)
openai
openai(流式)
curl
curl(流式)
from huggingface_hub import InferenceClient

messages = [
    {
        "role": "user",
        "content": [
            {"type": "video_url", "video_url": {"url": "https://huggingface.co/datasets/merve/vlm_test_images/resolve/main/concert.mp4"}},
            {"type": "text", "text": "What is happening in the video and what is the song about?"},
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="google/gemma-4-E2B-it", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient 会返回一个打印的字符串。

The video captures a live music performance at a music festival or a large concert. There are several musicians on stage, including a central figure playing an acoustic guitar and singing. The foreground is filled with the backs of the audience, indicating a large crowd watching the show. The stage is dramatically lit with bright spotlights and blue and white stage lighting, with haze and smoke creating an immersive atmosphere.

The lyrics of the song are: "I don't care 'bout street, from that fresh street, 'cause there's no problem, another one I want to be, in the storm..."

多轮对话

要进行多轮对话,请在 messages 列表中提供完整的对话历史,并按 user 和 assistant 角色交替排列。与所有 OpenAI 兼容的服务器一样,该 API 是无状态的,因此每个请求都必须包含完整的对话历史。

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

completion = client.chat.completions.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    messages=[
        {"role": "user", "content": "What is the capital of France?"},
        {"role": "assistant", "content": "The capital of France is Paris."},
        {"role": "user", "content": "How many people live there?"},
    ],
)
print(completion.choices[0].message.content)

后续问题“那里有多少人居住?”依赖于先前的上下文,因此模型会针对巴黎进行回答。

As of 2021, the population of Paris is approximately 2.2 million people.

v1/completions

v1/completions API 基于旧版 Completions API。与 /v1/chat/completions 不同,它接受自由格式的文本 prompt 而非聊天消息,并在 choices[].text 中返回生成的文本。这对于基础(非指令)模型以及无需聊天模板的文本补全任务非常有用。它还支持 suffix 以实现中间填充式的文本插入。

curl
openai
curl -X POST http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-0.5B",
    "prompt": "The capital of France is",
    "max_tokens": 20
  }'

该命令返回以下响应。

{
  "id": "chatcmpl-abc123",
  "object": "text_completion",
  "created": 1234567890,
  "model": "Qwen/Qwen2.5-0.5B@main",
  "choices": [
    {
      "text": " Paris, and the capital of the United States is Washington, D.C.",
      "index": 0,
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 5,
    "completion_tokens": 16,
    "total_tokens": 21
  }
}

v1/responses

Responses API 是 OpenAI 最新的生成 API 端点。它支持有状态交互,并集成了内置工具以扩展模型的功能。对于新项目,OpenAI 建议使用 Responses API 而非 Chat Completions API。

v1/responses API 支持通过 curl 命令和 OpenAI 客户端发送文本、图像、音频和视频请求。

基于文本的响应

openai
openai(流式)
curl
curl(流式)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    input="Tell me a three sentence bedtime story about a unicorn.",
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 客户端会返回一个打印的字符串。

Once upon a time, in a faraway land, there lived a beautiful unicorn named Luna [...]

基于图像的响应

Responses API 还支持图像、音频和视频输入。

openai
openai(流式)
curl
curl(流式)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-VL-7B-Instruct",
    input=[
        {"type": "input_text", "text": "What's in this image?"},
        {
            "type": "input_image",
            "image_url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg",
        },
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 客户端会返回一个打印的字符串。

The image depicts an astronaut in a space suit standing on what appears to be the surface of the moon, given the barren, rocky landscape and the dark sky in the background. The astronaut is holding a large egg that has cracked open, revealing a small creature inside. The scene is imaginative and playful, combining elements of space exploration with a whimsical twist involving the egg and the creature.

基于音频的响应

openai
openai(流式)
curl
curl(流式)
import base64
from huggingface_hub.utils import httpx
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

audio_url = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"
audio_b64 = base64.b64encode(httpx.get(audio_url, follow_redirects=True).content).decode()

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "Transcribe this audio."},
        {"type": "input_audio", "input_audio": {"data": audio_b64, "format": "mp3"}},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 客户端会返回一个打印的字符串。

This week, I traveled to Chicago to deliver my final farewell address to the nation, following in the tradition of presidents before me. It was an opportunity to say thank you. Whether we've seen eye to eye or rarely agreed at all, my conversations with you, the American people, in living rooms and schools, at farms and on factory floors, at diners, and on distant military outposts, all these conversations are what have kept me honest.

audio_url 内容类型是一种扩展,不属于 OpenAI 标准,未来版本中可能会发生变化。

你还可以通过 audio_url 内容类型以 URL 形式传入音频,以跳过 base64 编码。

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "Transcribe this audio."},
        {"type": "audio_url", "audio_url": {"url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"}},
    ],
    max_output_tokens=256,
    stream=False,
)

基于视频的响应

video_url 内容类型是 OpenAI 标准之外的扩展,未来版本中可能会发生变化。

视频处理需要 torchcodec。请使用 pip install torchcodec 进行安装。

openai
openai(流式)
curl
curl(流式)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "What is happening in the video and what is the song about?"},
        {"type": "video_url", "video_url": {"url": "https://huggingface.co/datasets/merve/vlm_test_images/resolve/main/concert.mp4"}},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 客户端会返回一个打印的字符串。

The video captures a live music performance at a music festival or a large concert. There are several musicians on stage, including a central figure playing an acoustic guitar and singing. The foreground is filled with the backs of the audience, indicating a large crowd watching the show. The stage is dramatically lit with bright spotlights and blue and white stage lighting, with haze and smoke creating an immersive atmosphere.

The lyrics of the song are: "I don't care 'bout street, from that fresh street, 'cause there's no problem, another one I want to be, in the storm..."

多轮对话

对于多轮对话,请在 input 字段中传入一个消息列表,每条消息都包含 role 键。与所有 OpenAI 兼容的服务器一样,该 API 是无状态的,因此每个请求都必须包含完整的对话历史。

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    input=[
        {"role": "user", "content": "What is the capital of France?"},
        {"role": "assistant", "content": "The capital of France is Paris."},
        {"role": "user", "content": "How many people live there?"},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

后续问题“那里有多少人居住?”依赖于先前的上下文,因此模型会针对巴黎进行回答。

As of 2021, Paris has a population of approximately 2.8 million people.

v1/systemone

v1/systemone 端点用于回答有关共享 state 的带类型问题,遵循 TypeSafe 的 System One API。可用于支持工单路由、紧急程度评级或操作选择等决策。

模型在一次批量前向传播中对所有允许的候选答案进行评分,而无需生成文本。该端点支持带有聊天模板的自回归模型;

发送模型、要评估的状态以及按名称索引的问题:

curl
TypeSafe(TypeScript)
curl http://localhost:8000/v1/systemone \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-1.5B-Instruct",
    "state": "Our integration has been failing for 3 days and we are losing sales!",
    "questions": {
      "team": {
        "type": "choice",
        "instructions": "Which team handles this?",
        "criteria": {"billing": null, "technical": "Bugs or integration problems"}
      },
      "urgency": {
        "type": "score",
        "instructions": "How urgent is this ticket?",
        "criteria": ["low", "medium", "high"]
      },
      "upset": {"type": "noul", "instructions": "Is the customer upset?"}
    }
  }'

答案使用与问题相同的名称。例如:

{
  "model": "Qwen/Qwen2.5-1.5B-Instruct",
  "answers": {
    "team": {"type": "choice", "choice": "technical", "probabilities": {"billing": 0.000006962256520637311, "technical": 0.999993085861206}, "confidence": 0.9999861717224121},
    "urgency": {"type": "score", "score": 1.6739138662815094, "legend": {"0": "low", "1": "medium", "2": "high"}, "probabilities": {"0": 0.0031353600788861513, "1": 0.31981542706489563, "2": 0.6770492196083069}, "confidence": 0.5108707791659981},
    "upset": {"type": "noul", "noul": 0.851952850818634}
  },
  "usage": {"input_tokens": 213, "output_tokens": 0}
}
问题类型criteria答案
choice映射到描述的选项名称,可以为 null。最可能的选项、选项概率以及置信度。
score按从低到高的顺序排列的级别描述。概率加权层级索引、概率、图例以及置信度。
noultrue 和 false 的可选描述。true 的概率。

state、可选的 instructions 以及描述可以包含文本或 JSON 对象和数组。每个请求最多接受 64 个问题,每个问题包含 1 到 26 个选项或评分等级。

对于支持视觉的模型,请添加一个 images 数组,其中最多包含四个 data:image/...;base64,... 格式的 URL。每个问题都会使用相同的图像。模型的处理器和聊天模板必须支持图像;不接受远程图像 URL。

决策配置

决策模型目前尚未就提示词、答案标签和校准设置的存储方式达成统一标准。由于不同模型实现之间存在差异,--decision-config 允许你显式地提供这些配置。具有自定义决策头的模型仍然需要兼容的实现;该文件仅用于配置提示词格式和下一个 token 的评分。

默认情况下,问题使用回退提示词以及禁用思考功能的模型聊天模板。选项和分数的答案标签为 A–Z,布尔值的答案标签为 Yes/No。概率是这些标签在温度为 1 时的 softmax 值。

传入 --decision-config 以覆盖模型的这些默认值:

transformers serve openjev/openjev --decision-config decision.json

JSON 文件支持三个可选字段:

字段含义
labels按问题类型和选项顺序标注答案(对于 noul,先 true,后 false)。每个标签必须是一个不同的、已知的单个 token。
temperature按问题类型划分的正温度值,应用方式为 softmax(label_logits / temperature)。
chat_template一个内联 Jinja 模板,用于替换完整的提示词,包括聊天格式和助手前缀。

例如,要在保留默认提示词的同时更改标签和校准:

{
  "labels": {"noul": ["A", "B"]},
  "temperature": {"choice": 0.85, "score": 0.85, "noul": 1.5547129}
}

自定义模板会接收常规的 messages,以及原始的 state、images 和 question 字段。question 会暴露 name、type、instructions、options、labels、descriptions 和 legend;options、labels 和 descriptions 共享相同的顺序。使用这些字段来替换回退措辞,并使用 tojson 来渲染结构化值。用于图像输入的模板必须包含模型对应的图像占位符。

该文件在启动时加载,仅适用于 /v1/systemone。省略的设置将保留其默认值;不会自动从检查点读取决策配置。

v1/audio/transcriptions

v1/audio/transcriptions 端点使用语音转文本模型对音频进行转录。它遵循 Audio transcription API 格式。

curl -X POST http://localhost:8000/v1/audio/transcriptions \
  -H "Content-Type: multipart/form-data" \
  -F "file=@/path/to/audio.wav" \
  -F "model=openai/whisper-large-v3"

该命令返回以下响应。

{
  "text": "Transcribed text from the audio file",
}

v1/models

v1/models 端点会扫描您的本地 Hugging Face 缓存,并以 OpenAI 兼容格式返回已下载模型的列表。第三方工具使用该端点来发现可用的模型。

在运行 transformers serve 之前,请先下载一个模型。

transformers download Qwen/Qwen2.5-0.5B-Instruct

下载完成后,该模型会出现在 /v1/models 的响应中。

curl http://localhost:8000/v1/models

该端点返回一个包含可用模型的 JSON 对象。

加载模型

/load_model 端点会预先加载模型,并通过 Server-Sent Events(SSE)流式传输进度。transformers chat CLI 会自动使用该端点,使用户能够看到下载和加载进度,而不是面对一个挂起的提示符。请在发送推理请求之前使用它来预热模型。

请求

curl -N -X POST http://localhost:8000/load_model \
  -H "Content-Type: application/json" \
  -d '{"model": "Qwen/Qwen2.5-0.5B-Instruct"}'

model 字段是一个 Hugging Face 模型标识符,可选地带有 @revision 后缀(Qwen/Qwen2.5-0.5B-Instruct@main)。省略 revision 时,默认使用 main。

响应

响应是一个 SSE 流(Content-Type: text/event-stream)。每个帧都是 data: 行上的一个 JSON 对象。

data: {"status": "loading", "model": "Qwen/Qwen2.5-0.5B-Instruct@main", "stage": "processor"}

每个事件至少包含 status 和 model 字段。其他字段取决于状态。

字段存在时描述
status始终loading、ready 或 error
model始终标准 model_id@revision
stagestatus == "loading"processor、config、download、weights 之一(参见下文各阶段)
progressdownload 和 weights 阶段包含 current 和 total(整数或 null)的对象
cachedstatus == "ready"如果模型已在内存中,则为 true
messagestatus == "error"错误描述

阶段

加载按顺序经历以下阶段。某些阶段可能会被跳过(当文件已在本地缓存时,会跳过 download)。

阶段有进展吗?描述
processorNo加载分词器/处理器
configNo加载模型配置
download是(字节)下载模型文件
weights是(条目)将权重张量加载到内存

流以且仅以一个终止事件结束:ready(成功)或 error(失败)。

超时

transformers serve 处理任意模型的请求。每个模型按需加载,并驻留在 GPU 内存中。模型在 300 秒无活动后会自动卸载,以释放 GPU 内存。将 --model-timeout 设置为不同的秒数,或设置为 -1 以禁用自动卸载。

transformers serve --model-timeout 400

加载示例

以下示例分别展示了全新下载的模型、从本地缓存加载的模型(跳过下载阶段)以及已在内存中的模型的响应。

全新加载
缓存文件
内存中
data: {"status": "loading", "model": "org/model@main", "stage": "processor"}
data: {"status": "loading", "model": "org/model@main", "stage": "config"}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 0, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 134600000, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 269100000, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "weights", "progress": {"current": 1, "total": 272}}
data: {"status": "loading", "model": "org/model@main", "stage": "weights", "progress": {"current": 272, "total": 272}}
data: {"status": "ready", "model": "org/model@main", "cached": false}

工具调用

transformers serve 服务器支持 OpenAI 风格的函数调用。经过工具使用训练的模型会生成结构化的函数调用,由您的应用程序执行。

工具调用适用于任何其分词器声明了工具调用令牌的模型。Qwen 和 Gemma 4 开箱即用。如需支持特定模型,请提交一个 issue。

按照 OpenAI 格式,将工具定义为函数规范列表。

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<KEY>")

tools = [
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get the current weather in a location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "The city name, e.g. San Francisco"
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "temperature unit"
          }
        },
        "required": ["location"]
      }
    }
  }
]

通过向 create 中的 extra_body 参数传递 GenerationConfig 参数来自定义生成。

generation_config = {
  "max_new_tokens": 512,
  "temperature": 0.7,
  "top_p": 0.9,
  "top_k": 50,
  "do_sample": True,
  "repetition_penalty": 1.1,
  "no_repeat_ngram_size": 3,
}

response = client.responses.create(
  model="Qwen/Qwen2.5-7B-Instruct",
  instructions="You are a helpful weather assistant. Use the get_weather tool to answer questions.",
  input="What's the weather like in San Francisco?",
  tools=tools,
  stream=True,
  extra_body={"generation_config": json.dumps(generation_config)}
)

for event in response:
  print(event)

多轮工具调用

模型返回工具调用后,在本地执行该函数,然后通过后续请求将结果发送回去,以获取模型的最终答案。两种 API 之间的模式略有不同。完整的规范请参阅 OpenAI function calling guide。

以下示例复用了上面定义的 tools 列表。

v1/chat/completions
v1/responses

将工具结果作为带有匹配 tool_call_id 的 role: "tool" 消息传递。

# Model returns a tool call
messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]
response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=messages,
    tools=tools,
)
assistant_message = response.choices[0].message

# Execute the tool locally
tool_call = assistant_message.tool_calls[0]
result = {"temperature": 22, "condition": "sunny"}  # your actual function call here

# Send the tool result back
messages.append(assistant_message)
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": json.dumps(result),
})
final_response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=messages,
    tools=tools,
)
print(final_response.choices[0].message.content)

推理

推理模型在输出最终答案之前会生成一段隐藏的思维链。服务器会检测这些思考片段,从可见答案中去除分隔符,并将推理文本放入专用字段,以便客户端将其单独渲染。

  • Chat Completions 会在助手消息中通过 reasoning_content 返回推理内容。
  • Responses API 将推理内容作为 reasoning 输出项返回,该输出项位于 message 项之前。

推理检测依赖于响应模板,该模板告知服务器思考片段的起始和结束位置。服务器会按顺序在两个位置中查找该模板。

  1. 如果模型附带了该属性,则使用分词器的 response_template 属性。
  2. 一种内置的回退机制,基于模型架构进行匹配,适用于尚未提供模板的分词器。

对于既没有这两种情况的模型,服务将不进行响应解析。原始解码文本会作为 content 返回,并且不会显示任何推理字段,即使模型输出了 <think>...</think> 标签也是如此。

在服务器上启用推理

使用 --reasoning 来控制聊天模板是否输出思考 token。

值行为
auto(默认值)回退到聊天模板的默认值。
on通过在聊天模板的 kwargs 中设置 enable_thinking=True 来强制启用思考。
off通过设置 enable_thinking=False 来强制关闭思考功能。
transformers serve Qwen/Qwen3-1.7B --reasoning on

并非所有模型都支持推理。--reasoning 仅在模型的聊天模板处理 enable_thinking 变量(或其分词器声明了思考分隔符)时生效。对于其他模型,该标志会被静默忽略。

--reasoning on 和 --reasoning off 通过设置聊天模板变量 enable_thinking 来工作。某些聊天模板使用不同的变量名来切换思考模式,或者接受额外的变量以更改提示的渲染方式。可以直接通过 --chat-template-kwargs 以 JSON 对象的形式传入任何聊天模板变量。

transformers serve Qwen/Qwen3-1.7B \
  --chat-template-kwargs '{"enable_thinking": true}'

客户端还可以在请求体中发送 chat_template_kwargs,以在单次请求中覆盖服务器默认值,而无需重启服务器。

Chat Completions

非流式响应会在助手消息中同时包含 reasoning_content 和 content。

openai
openai(流式)
curl
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<KEY>")

completion = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[{"role": "user", "content": "What is 17 * 23?"}],
)
message = completion.choices[0].message
print("reasoning:", message.reasoning_content)
print("answer:", message.content)

Responses API

Responses API 将推理内容作为独立的 reasoning 输出项返回,该输出项位于 message 项之前。

openai
openai(流式)
response = client.responses.create(
    model="Qwen/Qwen3-1.7B",
    input="What is 17 * 23?",
    stream=False,
)
reasoning_item = next(i for i in response.output if i.type == "reasoning")
message_item = next(i for i in response.output if i.type == "message")
print("reasoning:", reasoning_item.content[0].text)
print("answer:", message_item.content[0].text)

多轮往返

对于多轮对话,请在下一次请求中包含上一次响应中的推理文本。服务器会将其传递给聊天模板,该模板可以将其渲染为先前助手轮次的一部分,从而使模型能够看到自己之前的思考过程。

v1/chat/completions
v1/responses

将 reasoning_content 附加到上一条助手消息。

first = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[{"role": "user", "content": "What is 17 * 23?"}],
).choices[0].message

second = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[
        {"role": "user", "content": "What is 17 * 23?"},
        {
            "role": "assistant",
            "content": first.content,
            "reasoning_content": first.reasoning_content,
        },
        {"role": "user", "content": "Now multiply that result by 2."},
    ],
)
print(second.choices[0].message.content)

端口转发

端口转发允许您从远程服务器提供模型服务。请确保您拥有该服务器的 SSH 访问权限,然后在您的本地机器上运行此命令。

ssh -N -f -L 8000:localhost:8000 your_server_account@your_server_IP -p port_to_ssh_into_your_server

可复现性

将模型作为位置参数传入,以避免每次请求时的模型提示,从而产生稳定且可重复的运行结果。

transformers serve Qwen/Qwen2.5-0.5B-Instruct \
  --continuous-batching \
  --dtype "bfloat16"
Update on GitHub