函数调用深入:OpenAI、Anthropic、Gemini


文档摘要

函数调用深入:OpenAI、Anthropic、Gemini 本节摘要:三家前沿厂商在 2024 年收敛到了同一个工具调用循环,然后又在其他一切上分道扬镳。OpenAI 用 和 ;Anthropic 用 与 块;Gemini 用 加唯一 id 关联。本节把三者并排 diff,让你写在一个厂商上的代码,移植到别的厂商时不会崩——同一个循环,不同的声明信封、不同的参数类型约定(字符串 vs 对象)、不同的关联机制。我们会建一个把三种格式统一成一份规范工具声明的翻译器,并标注每家真正会撞上的硬限制(工具数、schema 深度、参数长度)。 学习目标 阅读完本节,你应当能够: 说清 OpenAI、Anthropic、Gemini 三家函数调用 payload 的三处形状差异(声明、调用、结果)。

函数调用深入:OpenAI、Anthropic、Gemini

本节摘要:三家前沿厂商在 2024 年收敛到了同一个工具调用循环,然后又在其他一切上分道扬镳。OpenAI 用 toolstool_calls;Anthropic 用 tool_usetool_result 块;Gemini 用 functionDeclarations 加唯一 id 关联。本节把三者并排 diff,让你写在一个厂商上的代码,移植到别的厂商时不会崩——同一个循环,不同的声明信封、不同的参数类型约定(字符串 vs 对象)、不同的关联机制。我们会建一个把三种格式统一成一份规范工具声明的翻译器,并标注每家真正会撞上的硬限制(工具数、schema 深度、参数长度)。

学习目标

阅读完本节,你应当能够:

  1. 说清 OpenAI、Anthropic、Gemini 三家函数调用 payload 的三处形状差异(声明、调用、结果)。
  2. 把一份工具声明翻译成三家厂商格式,并预测 strict 模式约束会在哪里不同。
  3. 在每家厂商里用 tool_choice 强制 / 禁止 / 自动选择工具调用。
  4. 知道每家的硬限制(工具数、schema 深度、参数长度)以及越界时各自的错误签名。

一、问题与直觉

函数调用请求的形状,因厂商而异。三个 2026 年生产栈里的具体例子:

OpenAI Chat Completions / Responses API:你传 tools: [{type: "function", function: {name, description, parameters, strict}}]。响应里 choices[0].message.tool_calls: [{id, type: "function", function: {name, arguments}}],其中 arguments 是一个你必须自己解析的 JSON 字符串。strict 模式(strict: true)靠受限解码强制 schema 合规。

Anthropic Messages API:你传 tools: [{name, description, input_schema}]。响应以 content: [{type: "text"}, {type: "tool_use", id, name, input}] 形式返回,input 已被解析过(对象而非字符串)。你用一条新的 user 消息回复,内含 {type: "tool_result", tool_use_id, content} 块。

Google Gemini API:你传 tools: [{functionDeclarations: [{name, description, parameters}]}](嵌在 functionDeclarations 下)。响应以 candidates[0].content.parts: [{functionCall: {name, args, id}}] 抵达,其中 id 在 Gemini 3 及以上版本里唯一,用于并行调用关联。你用 {functionResponse: {name, id, response}} 回复。

同一个循环,不同的字段名、不同的嵌套、不同的字符串-vs-对象约定、不同的关联机制。一个在 OpenAI 上写好天气 Agent 的团队,光是把管线移植到 Anthropic 就要花两天,再花一天到 Gemini。

本节建一个翻译器:把三种格式统一成一份规范工具声明,在边缘处路由。第 17 节会把这个模式泛化成 LLM 网关。

二、从零实现

共同结构

每家厂商都需要五样东西:

  1. 工具列表:每个工具的名字、描述、输入 schema。
  2. 工具选择(tool_choice):强制某个工具、禁止工具、或让模型自己决定。
  3. 调用产出:点出工具与参数的结构化输出。
  4. 调用 id:把响应关联到正确的调用(并行时关键)。
  5. 结果注入:把结果绑回调用的消息或块。

逐字段 diff

方面 OpenAI Anthropic Gemini
声明信封 {type:"function", function:{...}} {name, description, input_schema} {functionDeclarations:[{...}]}
Schema 字段名 parameters input_schema parameters
响应容器 助手消息上的 tool_calls[] content[] 中类型为 tool_use 的块 parts[] 中类型为 functionCall 的条目
参数类型 JSON 字符串 已解析对象 已解析对象
id 格式 call_...(OpenAI 生成) toolu_...(Anthropic) UUID(Gemini 3+)
结果块 role tool,带 tool_call_id user 消息含 tool_resulttool_use_id functionResponse,带匹配 id
强制某工具 tool_choice:{type:"function",function:{name}} tool_choice:{type:"tool",name} tool_config:{function_calling_config:{mode:"ANY"}}
禁止工具 tool_choice:"none" tool_choice:{type:"none"} mode:"NONE"
strict schema strict:true schema 即契约(始终强制) 请求级 responseSchema

你真的会撞上的限制

  • OpenAI:每请求 128 个工具;schema 深度 5;参数串 ≤ 8192 字节;strict 模式禁止 $ref、禁止有重叠的 oneOf/anyOf/allOf、要求每个属性都列在 required 里。
  • Anthropic:每请求 64 个工具;schema 深度实际无上限但实用上限 10;无 strict 模式开关——schema 是契约,模型倾向于遵从。
  • Gemini:每请求 64 个函数;schema 类型是 OpenAPI 3.0 子集(与 JSON Schema 2020-12 有细微差异);Gemini 3 起并行调用带唯一 id。

tool_choice 的三种模式

每家都支持,但叫法不同:

  • Auto:模型自选工具或文本。默认。
  • Required / Any:模型必须至少调用一个工具。
  • None:模型绝不能调用工具。

外加每家独有的一个模式:

  • OpenAI:按名字强制某个工具。
  • Anthropic:按名字强制某个工具;disable_parallel_tool_use 标志区分单调用与多调用。
  • Gemini:mode:"VALIDATED" 把每个响应都过一遍 schema 校验器,不管模型意图。

并行调用

OpenAI 的 parallel_tool_calls:true(默认)在一条助手消息里发出多个调用,你全部跑完后用一条批量的 tool 角色消息回复,每个 tool_call_id 一条。Anthropic 历史上只做单调用;disable_parallel_tool_use:false(Claude 3.5 起默认)启用多调用。Gemini 2 允许并行调用但不给稳定 id;Gemini 3 加了 UUID,使乱序响应能干净关联。

流式

三家的流式工具调用线格式各异:

  • OpenAI:tool_calls[i].function.arguments 的增量 delta 分块抵达,你累加直到 finish_reason:"tool_calls"
  • Anthropic:block-start / block-delta / block-stop 事件;input_json_delta 分块携带部分参数。
  • Gemini:streamFunctionCallArguments(Gemini 3 新增)发射带 functionCallId 的分块,使多个并行调用能交错。

第 03 节深入并行 + 流式的重组,本节聚焦声明与单调用形状。

错误与修复

非法参数的错误形态也不同:

  • OpenAI(非 strict):模型返回 arguments:"{bad json}",你的 JSON 解析失败,你注入错误信息再重调。
  • OpenAI(strict):校验在解码期发生,非法 JSON 不可能,但可能出现 refusal
  • Anthropic:input 可能含未声明字段;schema 是建议性的,需服务端校验。
  • Gemini:OpenAPI 3.0 怪癖——对象字段上的 enum 会被静默忽略,需自行校验。

翻译器模式

你代码里的一份规范工具声明长这样(形状由你定):

from dataclasses import dataclass @dataclass class Tool: name: str description: str input_schema: dict strict: bool = True # 三个小函数把它翻译成三家厂商的形状 def to_openai(t: Tool) -> dict: return {"type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.input_schema, "strict": t.strict}} def to_anthropic(t: Tool) -> dict: return {"name": t.name, "description": t.description, "input_schema": t.input_schema} # 无 strict 标志 def to_gemini(t: Tool) -> dict: return {"functionDeclarations": [{ "name": t.name, "description": t.description, "parameters": t.input_schema}]} # OpenAPI 3.0 子集 # 一个 canonical_call 把三家的响应都抽成 {id, name, args} def canonical_call(resp: dict, provider: str) -> dict: if provider == "openai": c = resp["tool_calls"][0] return {"id": c["id"], "name": c["function"]["name"], "args": json.loads(c["function"]["arguments"])} # 字符串→对象 if provider == "anthropic": b = next(b for b in resp["content"] if b["type"] == "tool_use") return {"id": b["id"], "name": b["name"], "args": b["input"]} # 已是对象 if provider == "gemini": p = resp["parts"][0]["functionCall"] return {"id": p["id"], "name": p["name"], "args": p["args"]}

code/main.py 里的脚手架正是这么做的,然后把一个假工具调用在每家的响应形状里往返一遍——无需联网,本节教的是形状,不是 HTTP。

生产团队把这个翻译器包成 AbstractToolset(Pydantic AI)、UniversalToolNode(LangGraph)或 BaseTool(LlamaIndex)。第 17 节会交付一个网关,在三者任一之前暴露一个 OpenAI 形状的 API。

三、框架对比

维度 OpenAI Anthropic Gemini
参数类型 JSON 字符串(需自解析) 已解析对象 已解析对象
关联 id 格式 call_... toolu_... UUID
strict 强制 显式 strict:true 始终强制 请求级 responseSchema
默认并行 开(3.5 起) 开(3 起带稳定 id)
schema 方言 JSON Schema 2020-12(strict 下受限) JSON Schema OpenAPI 3.0 子集

💡 移植心法:写一份规范 Tool,用三个翻译器输出;响应侧用一个 canonical_call 收口。这样换厂商只改边缘路由,业务逻辑不动——这是第 17 节网关的设计原型。

四、可复用产物

本节产出 outputs/skill-provider-portability-audit.md——一个可移植性审计技能。给定一段针对某家厂商的函数调用集成,它产出审计报告:依赖了哪些厂商限制、哪些字段需要改名、移植到其他两家时各自会断在哪里。

五、练习

  1. 跑翻译器:运行 code/main.py,验证三份厂商声明 JSON 序列化自同一个底层 Tool 对象。给规范工具加一个 enum 参数,确认只有 Gemini 翻译器需要处理 OpenAPI 怪癖。

  2. 写 list 响应解析器:为每家写一个 ListToolsResponse 解析器,提取模型在 list_tools 或发现调用后返回的工具列表。OpenAI 原生没有,记录这一不对称。

  3. 实现 tool_choice 转换:把规范的 ToolChoice(mode="force", tool_name="x") 映射到三家形状;再映射 mode="any"mode="none",对照本节 diff 表。

  4. 读一家的文档:挑一家,从头到尾读它的函数调用指南,找一个它支持而另两家不支持的 schema 字段。候选:OpenAI strict、Anthropic disable_parallel_tool_use、Gemini function_calling_config.allowed_function_names

  5. 写错误测试向量:构造一个参数违反声明 schema 的工具调用,过每家的校验器(第 01 节的标准库版可作代理),记录各自报什么错,并写下「生产里你会选哪家来保严格性」。

本节要点回顾

  1. 三家用同一个循环:声明 → 调用 → 结果,但字段名、嵌套、参数类型(OpenAI 字符串,另两家对象)、关联 id 格式各不相同。
  2. 五件必需:工具列表、tool_choice、调用产出、调用 id、结果注入——缺一不可。
  3. 硬限制差异:OpenAI 128 工具/深度 5/8192 字节且 strict 禁 $ref;Anthropic 64 工具、无 strict 标志但契约式;Gemini 64 函数、OpenAPI 3.0 子集。
  4. tool_choice 三公共模式:Auto / Required(Any)/ None,加上每家独有「强制某工具」与一个特异模式。
  5. 并行调用:OpenAI 默认开批量返回;Anthropic 3.5 起默认开;Gemini 3 才给稳定 UUID。
  6. 流式各异:OpenAI 增量 delta、Anthropic 块事件 + input_json_delta、Gemini streamFunctionCallArguments 带 id。
  7. 翻译器模式:一份规范 Tool + 三个翻译器 + 一个 canonical_call,是第 17 节网关的设计原型。

下一节,我们深入并行工具调用与流式重组——多个调用乱序返回时如何按 id 关联,以及如何把分块的 arguments 流正确拼回。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U