从近期开源模型看工具调用格式的分化
本文最后更新于:2025年8月31日 下午
最近,国内外开源大模型集体把目光投向「Agent 能力」。从 SWE-Bench 到 Terminal-Bench,从 Tau-Bench 到 Tau2-Bench,再到 BrowseComp、GAIA、HLE,榜单刷分卷出了新高度,也划出了三条主赛道:Agentic Coding、Agentic Tool Using 与 Agentic Search。
与趋于一致的推理格式(使用 <think>...</think>
包裹 reasoning content)不同,各家开源模型的工具调用格式却显现出了分化的趋势。而正确使用工具调用格式是入门 Agent SFT 或 RL 训练的第一步,因此本文整理了一些近期开源模型的工具调用格式,包括:
- 以 Qwen3 为代表的单字典形式
- 以 DeepSeek-V3.1、Kimi-K2、GPT-OSS 为代表的函数名+字典形式
- 以 GLM-4.5、Seed-OSS-36B 为代表的参数格式形式
基本知识
最简单的工具调用
这里以 DeepSeek 官方 API Docs 中的「获取用户当前位置的天气信息」为例,展示了使用 Tool Calling 的完整 Python 代码。之后本文中的例子也会再次用到 get_weather
这个工具。
1 |
|
这个例子的执行流程如下:
- 用户:询问现在的天气。
- 模型:返回 function
get_weather({location: 'Hangzhou'})
- 工具:调用 function
get_weather({location: 'Hangzhou'})
,并将工具返回结果传给模型。 - 模型:返回自然语言,"The current temperature in Hangzhou is 24°C."
注 1:上述代码中
get_weather
函数功能需由用户提供,模型本身不执行具体函数。注 2:上述代码中工具返回结果带有
tool_call_id
参数,这是为了防止多个工具并行调用时顺序混淆,下文中将略去。
Agent Loop
显然,上述的代码只是硬编码了一个固定的、单轮的工具调用流程,如果我们想实现一个复杂的、多轮的工具调用流程,则需要引入「Agent Loop」这个概念 —— 说人话就是,允许 Agent 多次调用工具直到任务结束跳出循环。
其核心操作在于解析出模型回复中的 tool_call 部分,如果有 tool_call,则表示模型认为任务尚未结束,需要继续循环;如果没有 tool_call,则表示模型认为任务已经结束(此时模型的回答一般是对之前工具调用结果的总结)。
用伪代码表示这个流程:
1 |
|
完整的实现可以参考 Kimi-K2 官方仓库 的示例,这里我将其改成了 Search Agent 的常用工具集,并附上了一个需要多跳搜索才能回答的问题帮助理解:
1 |
|
Messages 与 Chat Template
在即将进入正题之前,还有最后一个概念需要厘清。
我们平常在代码中所见的 User-Assistant 消息列表,并不能直接喂给模型。语言模型只认 token_id 序列,中间还要走两步:
- apply_chat_template:把消息列表按模型规定的模板拼成一段纯文本字符串;
- tokenize:把这段文本切成 token 并转成 id。
对话模板藏在各开源仓库的 tokenizer_config.json 中,里面的 chat_template
字段精确描述了「这一段对话该怎么拼」,本文的分析也都基于各个开源模型仓库中的模板推断而来。感兴趣的朋友可以在 Huggingface 提供的 Chat Template Playground 进行实验。
下面我构造了一个多轮对话的消息列表示例,包含 System-User-Assistant-Tool-Assistant-User-Assistant-Tool 的交互过程(包括思考过程、并行工具调用等细节,可以手动删减来理解各种情况),直接复制到 Playground 右上角的 JSON Input 中即可生成纯文本字符串:
1 |
|
以 Qwen3 为代表的单字典形式
Qwen3 系列模型对话模板的特点:
- 包含三种角色:system、user、assistant,其中工具返回结果以 user 角色拼接
- 每个角色消息开始于
<|im_start|>
,结束于<|im_end|>
- 工具描述拼接在 system prompt 之后作为一部分,使用
<tools></tools>
XML tags 包裹 - 工具调用格式为
<tool_call>{"name": <function-name>, "arguments": <args-json-object>}</tool_call>
,非常经典的一种格式(似乎也叫 Hermes 风格),函数名和参数放到同一个字典中传递 - 并行工具调用也非常方便,直接串联即可:
<tool_call>...</tool_call><tool_call>...</tool_call>
,工具返回结果也按顺序串联 - 关于
<think></think>
的拼接有一个坑点:最后一次 User Query 前的历史思考内容会被省略,具体设计不在本文的讨论范围~
1 |
|
以 DeepSeek-V3.1 为代表的函数名+字典形式
DeepSeek-V3.1 对话模板的特点:
- 包含两种角色:user、assistant,其中系统消息默认放到最前方,工具返回结果无角色直接拼接!
- 基础的工具调用仅在非思考模型下进行,格式为:
<|begin▁of▁sentence|>{system prompt}\n\n{tool_description}<|User|>{query}<|Assistant|></think>
- 工具描述拼接在 system prompt 之后作为一部分,使用 Markdown 三级标题,并且会自动附上官方提示工程
- 工具调用格式为
<|tool▁call▁begin|>tool_call_name<|tool▁sep|>tool_call_arguments<|tool▁call▁end|>
,函数名和参数字典分开传递,并用一个特殊 Token 分隔 - 对于并行工具调用,则使用一个外部 Section 将整体包裹:
<|tool▁calls▁begin|>...<|tool▁calls▁end|>
,内部再串联拼接 - 注:DeepSeek-V3.1 将 Search Tool 的格式独立出来,作为一种特殊情况。本文文末会进行分析。
1 |
|
注:在使用 DeepSeek-V3.1 的 JSON Input 的时候,需要去掉思考过程,以及将工具传参 Argument 改为字符串。
与 DeepSeek-V3.1 相似,Kimi-K2 也是函数名+参数字典,这里仅展示工具调用部分:
1 |
|
值得一提的是,OpenAI 开源的 gpt-oss 也是函数名+参数字典,但有一些特殊的地方:
- 使用与 OpenAI API 模型中相同的 System 和 Developer 消息角色(Instruction Hierarchy)。通过这些角色,模型遵循基于角色的信息层级来解决指令冲突:System > Developer > User > Assistant > Tool,这种方法会更加安全
- 引入 Channels 来表明每条消息的预期可见性,例如,用于推理过程(CoT)的分析 Channels、用于函数工具调用的评论 Channels,以及用于展示给用户的最终答案 Channels
- 由于 Channels 的存在,整体会更复杂:
<|start|>assistant to=functions.name<|channel|>commentary json<|message|>function_arguments<|call|>
- 不支持并行工具调用!在对话模板中明确提到:We assume max 1 tool call per message...,不知道出于什么考虑
1 |
|
以 GLM-4.5 为代表的参数隔离形式
GLM-4.5 的技术报告中提到,函数调用模板中存在字符转义问题:
虽然在现代实现中,函数调用参数主要以 JSON 格式表示,但当这些参数包含代码段时,会出现一个重大挑战 —— 在这种情况下,代码中的大量字符需要转义,迫使模型生成大量的转义字符,从而增加了模型的学习负担。
尽管这一问题对于主要用于聊天的模型而言影响较小,但对于以函数调用为核心能力的智能体基础模型来说,却是一个非 trivial 的挑战。为了解决这一限制,我们提出了一种新的函数调用模板,该模板使用类似 XML 的特殊标记标签来封装函数调用的键和值。
这种方法显著减少了代码段中字符转义的必要性,因为大多数代码可以以原生形式表示而无需转义。实验结果表明,所提出的函数调用模板在减少转义的同时,并未影响函数调用执行的性能。
GLM-4.5 对话模板的特点:
- 包含四种角色:system、user、assistant、observation,有各自的特殊 Token,其中工具返回结果以 observation 角色拼接(不同于 Qwen 直接复用 user 角色)
- 工具描述拼接在 system prompt 之后作为一部分,使用
<tools></tools>
XML tags 包裹 - 工具调用格式为
<tool_call>{function-name}<arg_key>{arg-key-1}</arg_key><arg_value>{arg-value-1}</arg_value></tool_call>
,每个参数的键值对隔离开,省去 JSON 字典 - 并行工具调用也非常方便,直接串联即可:
<tool_call>...</tool_call><tool_call>...</tool_call>
,工具返回结果也按顺序串联
1 |
|
最近开源的 Seed-OSS-36B 也使用了类似的参数隔离形式,但是会将参数名放到 XML tags 中(不知道会不会对 tokenize 造成影响),这里仅展示工具调用部分:
1 |
|
附:DeepSeek-V3.1 的 Search Agent 特殊模式分析
DeepSeek-V3.1 声称为在思考模式下搜索工具调用设计了特定的格式,以支持 Search Agent —— 注意,其他工具则是在非思考模式中完成。现在有一些工作也探讨了 thinking effort 对于 Agent 任务表现的影响,例如 Thinking vs. Doing、WebSailor。
个人猜测 DeepSeek 这样做的动机可能是发现了一些困难的榜单如果不思考根本刷不上去(例如 BrowseComp),为了防止几种模式冲突,所以单独设置了一个 pattern 告诉模型「现在是搜索任务」。
官方仓库 assets/search_tool_trajectory.html
中提供了一个例子,可以看到:
- Search 一个工具就集成了常规的 Search+Browse 功能,在工具返回结果中包括 snippet 和 content(这合理吗?几次调用就超 128k 了)
- 虽然说的是思考模式,但并非使用常规思考的
,而是用 <|search▁begin|><|search▁end|>
包裹了整个搜索交互过程作为思考(并不是之前一些文章猜测的 Search-R1 形式的特殊 Token 包裹查询) - 在
<|search▁end|>
之后的回答会引用搜索过程中的文档,风格看起来像是 Deep Research 最后的 Report 生成
1 |
|