将 auto/none/required/specific 四种工具选择模式映射到 OpenAI、Anthropic、Google 等各家的字符串/对象实现差异。
每个工具调用 API 都允许你表达几种语义:"自己决定"、"不调用任何工具"、"调用某个工具"和"调用这个特定工具"。各 API 在以下问题上存在分歧:tool_choice 应该是字符串还是对象、"调用某个工具"叫什么名字、以及强制调用之后会发生什么。
从语义入手比较稳妥,因为语义是稳定的,而具体写法则不然:
Auto — 由模型决定是否调用工具。当工具存在时,几乎所有 API 的默认值都是它。
None — 工具对模型可见,但模型必须以文本回答,不能调用工具。注意这与省略 tools 数组不同:工具定义仍然占据上下文、仍然消耗 token、仍然会影响回答内容。
Required / any — 模型必须调用某个工具,具体调用哪个由模型自行选择。当某一轮对话没有有效的纯文本回答时,这个模式就派上用场了。
Specific tool — 模型必须调用指定的工具。这个模式同时充当了结构化输出的机制,所以它会同时出现在函数调用与结构化输出的讨论中。
在动手翻译之前,有两个关于默认值的细节值得先确认清楚。这个参数在所有 API 上都是可选的,但省略它并不等于在每个 API 上都发送 auto:当工具存在时,文档通常声明默认行为是 auto,但当 tools 数组为空或不存在时,其行为是单独定义的,有些 API 还会在没有工具可强制调用时拒绝一个强制值。因此,一个总是发出值的转换器,可能把一个原本合法的请求——就在你的 agent 无活可干的那几轮——变成 400 错误。只有当你的中性表示与默认值不同的时候才发出该字段,在工具列表为空时直接丢弃它。
OpenAI 的 Chat Completions API 接受 tool_choice 为裸字符串或对象。字符串形式有 "none"、"auto" 和 "required";若要指定特定工具,则需要用对象形式:
"tool_choice": "auto"
"tool_choice": "none"
"tool_choice": "required"
"tool_choice": {"type": "function", "function": {"name": "get_order_status"}}
并行调用由顶层 parallel_tool_calls 布尔值单独控制,而非放在 tool_choice 内部。文档声明的默认行为是模型想调就并行;设为 false 则约束模型每轮只做一个调用。在依赖这个行为之前,值得把该标志与强制模式之间的确切交互读一下,并发工具调用格式在 vendor 参考文档中有详细说明。
已废弃的前身 function_call 接受 "none"、"auto" 或 {"name": "..."},没有 "required" 的等价物。如果你在从它迁移,缺失的那个模式就是你曾经可能用提示词模拟过的行为,现在可以直接表达了。
Anthropic 的 Messages API 在所有情况下都将 tool_choice 作为对象接受,用 type 字段区分:
"tool_choice": {"type": "auto"}
"tool_choice": {"type": "none"}
"tool_choice": {"type": "any"}
"tool_choice": {"type": "tool", "name": "get_order_status"}
映射关系是直接的,只有一个名字的差异:any 对应的是 required,这是该参数翻译中最常见的错误,因为两个词在两种语义下都说得过去。把字符串 "required" 直接传过去的转换器,发送的是一个 API 未定义的值;把 any 映射为 auto 的转换器——因为"any tool"听起来像是允许——则完全移除了强制语义,这是静默的错误,会产生"Why tool-choice behaviour changes after a swap"中描述的恰好相反的症状。
并行控制是在同一个对象内部通过 disable_parallel_tool_use 布尔值来管理的,而不是由顶层参数管理。这是一个逐字段转换器会漏掉的结构差异:标志必须跨越层级移动,而且极性是反转的——一个说"允许",另一个说"禁止"。把同一个布尔值原封不动地传过去,得到的恰好是你配置的反面。
Google 的 Gemini API 将同样的概念表达为一个嵌套的配置对象,而非单一参数:工具配置(tool configuration)包含函数调用配置(function calling configuration),其中的 mode 遵循大写惯例——AUTO、ANY、NONE——旁边还有一个 allowedFunctionNames 列表。
这个列表是值得关注的点,在 gaps 下面会讨论。对于转换器来说,结构上的要点是:这个值并不是 tools 的顶层兄弟节点,而是存在于它自己的 config 对象中,所以扁平字段映射无处安放它。
模式名称、默认值以及强制与并行之间的交互,恰好是各 vendor 在 API 版本之间最常修订的表面部分,后续新增的模式不会出现在本文中。在依据这些 API 编写转换器之前,请分别对照 OpenAI 的 chat completions 参考、Anthropic 的 messages 参考和 Google 的 function calling 文档加以确认。
限制在某个子集内。allowed-names 列表允许你表达"调用这三个之一"。字符串或对象形态和带标签的对象形态都没有这个能力:它们只提供任意工具或命名工具,之间没有中间选项。如果要迁移一个使用了该列表的配置,选项有两个:收窄到单一强制工具,或者——通常更好——仅在那一轮的 tools 数组中传入该子集。这样做是全平台可移植的,同时还能减少 schema token 消耗。
并行控制在两种最常见的形态之间变更了层级和极性,如上所述。把它当作中性表示中的一个独立字段处理,使用明确的"允许并行"语义,让每个 emitter 在必须时自己去做反转。
none 在某些 API 上比其他模式更晚出现。如果你的目标 API 不接受它,等价的做法是该轮不发送 tools 数组——并非完全相同,因为模型再也看不到那些定义了,但通常这就是意图所在。
强制调用之后会发生什么,各 API 的规定并不一致。强制作用于某一轮,而非整个对话,所以一个 agent 循环如果设置了一次强制值然后复用请求体,就会导致后续每一轮都强制调用,永不终止。在第一轮之后将其重置为 auto。这是一个真正的无限循环来源,而测试无限循环保护正是上线前捕获它的手段。