跳到主要内容
SecondHome

同一 schema,三套私语:Qwen、Mistral、DeepSeek 的工具调用序列化

8 分钟进阶1bL2 p.20-23 ↗录像 ↗支撑材料 3 篇

工具调用(tool calling)在 API 层是 OpenAI tools 的统一形状,落到开源模型的输入序列里却是三套互不相通的私语。本篇对照 Qwen、Mistral、DeepSeek 的序列化原文,讲清 chat template 这层翻译发生在哪里、换模型时的迁移成本花在哪。

同一份 get_weather 的 JSON Schema,Qwen 权重上跑得好好的,换成 Mistral 突然就不会调工具了。问题多半出在模型各自的序列化方言上,业务代码几乎不用动。

同一 schema,三套私语。本篇对照 Qwen、Mistral、DeepSeek 的序列化原文,把这层翻译拆开看。

行业的公约数:OpenAI tools API 的形状

先看大家都同意的部分。行业事实标准是 OpenAI tools API 的形状(slides L2 p.20):同一个 JSON Schema 放进请求的 tools 字段,模型吐出结构化调用,执行结果按 call ID 配对回填。

OpenAI Function Calling 指南 定下的这套契约,几乎被所有推理框架与 harness 原样采纳。

请求侧,你声明工具的参数 schema:

{
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "parameters": {
        "type": "object",
        "properties": { "city": { "type": "string" } }
      }
    }
  }]
}

模型侧返回一条带 ID 的结构化调用。你执行完工具,把结果按同一个 ID 回填进对话:

[
  { "role": "assistant",
    "tool_calls": [{ "id": "call_01",
      "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } }] },
  { "role": "tool", "tool_call_id": "call_01", "content": "(工具执行结果)" }
]

对 harness 来说,世界到此为止:schema 进、调用出、ID 配对。

但这份统一只停留在 API 层。往下再挖一层,每家开源模型都在说自己的方言(slides L2 p.20-23)。

三套私语:同一调用的三种展开

把上面那条 get_weather 调用分别交给三家的模板,得到的文本完全不同(slides L2 p.20-23)。Qwen3.8 走 XML 风格标签:

<tool_call>
<function=get_weather>
<parameter=city>北京</parameter>
</function>
</tool_call>

Mistral Small 4 用控制串 [TOOL_CALLS] 领起一段 JSON,以控制 token </s> 收尾,call ID 直接写在 JSON 里:

[TOOL_CALLS] [{"name": "get_weather", "arguments": {"city": "北京"}, "id": "call_01"}]</s>

DeepSeek V3.2 则把调用包进 <|DSML|function_calls> 专用块,块内负载同样是 JSON:

<|DSML|function_calls>
{"name": "get_weather", "arguments": {"city": "北京"}}
模型调用语法私语出处
Qwen3.8<tool_call><function=…><parameter=…> 标签tokenizer_config.json(模板内嵌)
Mistral Small 4[TOOL_CALLS] [{…}]</s>(控制 token + call ID)Mistral cookbook 的 tokenization 指南
DeepSeek V3.2<|DSML|function_calls> 块encoding_dsv32.py

这三套语法都不是秘密,出处就标在上表右列。想确认某个模型到底说什么方言,第一步永远是翻它的 tokenizer 配置。别猜。

模板层:统一与私语的和解点

两层的矛盾在一处和解:聊天模板(chat template)。按 Transformers Chat Templates 文档 的机制,服务端用 Jinja 模板把 messages 与 tools 渲染成模型的单条输入序列。

你交给 API 的结构化请求,进模型前先被模板压扁成一段文本。模型吐出的私语,也由模板 / 推理层反向解析回结构化调用。

你在 Qwen2.5 输入序列里看到的 <|im_start|>,就是模板渲染的产物。不是谁手敲的字符串。

一份 JSON Schema 进 tools,一条带 call_01 的结构化调用出,工具结果按 ID 回填。全程只跟结构化对象打交道。

一整段被模板压扁的文本:控制 token、标签、JSON 负载混在自然语言里。模型只在这门方言上训练过。

私语为何存在,又为何与你无关

训练语料的格式就是推理的格式。模型在什么格式上学会调工具,推理时就必须在什么格式上看到工具,换了格式等于让它讲一门没学过的语言。

控制 token 也需要特殊 token id:[TOOL_CALLS]、</s>、<|DSML| 这类控制串在词表里有专属 id,独立于普通文本切分,不会被用户的自然语言误触发。解析器也无需猜测「这是不是一次调用」。

这套复杂性对应用层是透明的:harness 只见 API 形状,序列化发生在模板 / 推理层。换模型时的迁移成本不在业务代码,而在两处配套:

  • 模板:tools 怎么渲染进序列,私语怎么解析回来
  • 采样参数(sampling parameters)

业务代码里那份 JSON Schema,原封不动。

私语不等于合法性保证

私语只约定调用长什么格式,不保证模型吐出来的一定合法:参数漏字段、括号不闭合都可能发生。要硬保证输出合法,得靠文法约束解码(constrained decoding),那是下一节的内容。

与本讲的连接

本篇下钻的是讲次页「工具调用」板块的序列化层。伴读笔记里 ReAct 的推理与行动交替定义了调用的行为节奏,本篇补的是每次 Action 在开源模型词表里被写成什么字符。

资料卡里的 Transformers Chat Templates 文档 对应模板源码,OpenAI Function Calling 指南 对应 API 契约,工具调用与 MCP 的长文则把这套序列化接到更广的工具生态。下一节的约束解码接手保证那一半,和本篇的格式合成闭环。

换模型前,把三套方言再对一遍:

  • Qwen:XML 风格标签
  • Mistral:控制串领起一段 JSON
  • DeepSeek:专用块包住 JSON

本节论文与规范

本节资料

  • 讲清 Jinja 模板如何把 messages 与 tools 序列化成模型实际输入:工具定义塞进 system、调用与结果按角色回填。这是理解 Qwen、Mistral、DeepSeek 三家工具调用「私语」的底层说明书,本讲三家 token 化对照的公共底座。

  • OpenAI Function Calling 指南 规范入门官方引用

    tools=[{type:function,…}] 进请求、模型返回 tool_calls、以 call_id 配对回填结果的形状,已成行业事实标准。指南给出从定义工具 schema 到拿到结构化输出的完整链路,是动手第一站,本讲调用机制板块多次对照此文档。

  • 中文系列长文:从 Function Calling 演进史讲到解码层实现技巧(特殊 token、语法掩码),再到 MCP 生态全貌。中文技术社区里少见的深度与准确度兼备的工具调用综述,适合作为英文材料的中转站——先读它建立框架,再啃英文原文。

AI 生成 · 仅供参考,与课程方无关。选中正文里的任何一段也能直接问我(解释一下 / 举个例子)。

本详解以 CMU 11-768 AI Agents (Fall 2026) 课程大纲为参考框架独立撰写,AI 辅助起草、人工抽审。 涉及的官方课件、论文与课程材料版权归原所有方,讲义为站内镜像、其余外链原文出处; 观点与文字(除标注引用外)为作者原创。