Function Calling(也叫 tool calling / 工具呼叫)在 2026 年已经从「演示技巧」变成 Agent 产品的預設協議:模型不再用散文假装呼叫接口,而是在约定 schema 下吐出 JSON,由你的程式碼去真正请求天气、查库、改檔案或跑测试。真正差异不在模型谁更会聊天,而在schema 是否可校驗、执行是否可稽核、副作用是否可隔離。
为什么纯文本 prompt 不够(Why)
早期做法是让模型输出「请呼叫 get_weather(city=北京)」,再用正規表示式去抠。这在 demo 里能过,上线后会遇到三类硬伤:
- 解析不稳定——模型加礼貌用语、换参数顺序、漏引号,下游 JSON.parse 直接炸。
- 无法約束类型——该传枚举却传自由文本;该必填却省略;並行呼叫时分不清哪次对应哪次。
- 副作用与对话混写——模型「声称已经删除了檔案」,其实你从未执行;或反过来,你执行了但模型不知道结果。
Function Calling 把「决策」留给模型、「执行」留给執行環境:模型只选择工具与参数,HTTP、資料庫、shell 永远不在模型进程里发生。这和 CI「一 job 一 workspace」同一思想——执行边界先于聪明程度。工具一旦能改磁盘,就要配 Agent 檔案系统沙箱;一旦要跨会话记住用户偏好,就要把状态放到 独立 Memory 层,而不是塞进下一次 tool arguments。
Function Calling 三层分类(What)
不要把「接了 OpenAI tools」当成架构。至少拆三层:
| 层级 | 职责 | 典型载体 | 谁拥有 |
|---|---|---|---|
| L1 Schema | 工具名、JSON Schema、描述、必填字段 | OpenAI tools[].function、Gemini functionDeclarations、Claude input_schema | 产品 / 平台团队 |
| L2 Runtime | 校驗参数、注入金鑰、超时、重试、稽核 | 自研 Agent loop、LangGraph、Claude Code | 你的后端或本机 Agent |
| L3 Transport | 如何發現与连接工具进程 | 直连 HTTP、MCP stdio/SSE、内部 RPC | 基础设施 |
非对称结论:厂商 API 只标准化了 L1 的「JSON 信封」;L2 的权限与 L3 的进程隔離,三家都不替你做完。MCP 解决的是 L3 發現与会话,不是「自动安全」。
完整循环如下。注意箭头方向:模型从不持有你的 API Key,金鑰只存在 Runtime。
┌──────────────┐ JSON tool request ┌──────────────────┐
│ LLM API │ ────────────────────────► │ Your runtime │
│ (OpenAI / │ name + arguments │ (Agent loop) │
│ Gemini / │ │ │
│ Claude) │ ◄──────────────────────── │ execute tool │
└──────────────┘ tool result JSON │ • HTTP APIs │
│ • DB / search │
│ • shell / MCP │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Policy + secrets │
│ allowlist, audit │
│ Cloud Mac / VPC │
└──────────────────┘核心对比:OpenAI vs Gemini vs Claude
| 工具 | 入口 | 执行能力 | 上下文 | 适合人群 |
|---|---|---|---|---|
| OpenAI tools | Chat Completions / Responses API | 由你执行;arguments 常为 JSON 字符串 | 並行 tool_calls[];可选 strict schema | 已有 OpenAI SDK、閘道统一成 OpenAI 形态 |
| Gemini functionDeclarations | Gemini API / Vertex | 由你执行;可 toolConfig.mode 强制/禁止呼叫 | Schema 类型名常用大写 OBJECT/STRING | GCP 栈、需要 mode 开关的产品 |
| Claude tool_use | Messages API / Claude Code | 由你执行;input 已是对象而非字符串 | content 块:tool_use / tool_result | Anthropic 栈、IDE Agent、MCP 生态 |
| MCP 工具 | 客户端连 MCP Server | Server 进程内执行;模型仍只出 JSON | 工具列表執行環境發現 | 本地/遠端工具目錄要热插拔 |
真正分水岭不是「谁支持 Function Calling」(2026 年三家都支持),而是参数是字符串还是对象、並行呼叫怎么对齐、以及失败结果如何写回。閘道层(OpenRouter / 自建)通常把三家歸一成内部 ToolCall{name, id, args},再分发到同一 L2 Runtime。
三家 JSON 怎么写(How)
下面用同一个 get_weather 工具对照。生产环境请把 description 写清边界(「只查公开气象,不查个人位置」),避免模型把不该暴露的字段塞进参数。
OpenAI:tools + tool_calls
请求侧把工具挂在 tools。模型若决定呼叫,助手消息带 tool_calls;arguments 是字符串化的 JSON,你必须先 parse 再校驗。
{
"model": "gpt-4.1",
"messages": [{"role": "user", "content": "What's the weather in Beijing?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}]
}模型返回形态(简化):
{
"role": "assistant",
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Beijing\",\"unit\":\"celsius\"}"
}
}]
}执行后,你再发一条 role: tool(或 Responses API 等价字段),tool_call_id 对齐,content 放结果字符串。然后模型用自然语言或再一次 tool_call 继续。
Gemini:functionDeclarations + toolConfig
Gemini 把宣告放在 tools[].functionDeclarations。类型字段习惯用大写。用 toolConfig.functionCallingConfig.mode 可设 AUTO、ANY(必须调工具)或 NONE(禁止调工具)——这是产品开关,不是安全沙箱。
{
"contents": [{"role": "user", "parts": [{"text": "What's the weather in Beijing?"}]}],
"tools": [{
"functionDeclarations": [{
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "OBJECT",
"properties": {
"city": {"type": "STRING"},
"unit": {"type": "STRING", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}]
}],
"toolConfig": {"functionCallingConfig": {"mode": "AUTO"}}
}模型侧常见 functionCall part(name + args 对象)。你执行后回 functionResponse part。不要假设字段名与 OpenAI 完全一致,SDK 版本之间也有 Chat vs Gemini 原生差异。
Claude:tools + tool_use / tool_result
Claude Messages API 的工具用 input_schema(JSON Schema)。助手回复是 content 块数组:可能先有一段 text,再跟 tool_use。input 已经是对象,少一次 parse,但仍要做 schema 校驗——模型仍可能漏字段。
{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}],
"messages": [{"role": "user", "content": "What's the weather in Beijing?"}]
}{
"role": "assistant",
"content": [{
"type": "tool_use",
"id": "toolu_01XYZ",
"name": "get_weather",
"input": {"city": "Beijing", "unit": "celsius"}
}]
}把结果写回时,用 role: user 的 tool_result,tool_use_id 必须一致:
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_01XYZ",
"content": "{\"temp_c\": 28, \"condition\": \"clear\"}"
}]
}Claude Code / Cursor 等 IDE Agent 在此之上又加了 MCP:模型看到的仍是「一组带 JSON Schema 的工具」,只是进程通过 MCP 会话热插拔。協議变了,循环没变。
场景怎么选(决策矩阵)
| 如果你是… | 就选 | 原因 |
|---|---|---|
| 已有 OpenAI 兼容閘道 | 统一成 OpenAI tools 方言 | 生态最大;下游 adapter 一次写成 |
| 主栈在 GCP / Vertex | Gemini functionDeclarations + mode | 少一层翻译;mode 适合「这一轮必须调检索」 |
| 主力是 Claude Code / Anthropic | Claude tools + MCP | content 块与 IDE 工具目錄一致 |
| 要同时接三家模型 | 内部规范化 ToolCall + 每家 adapter | 不要让业务程式碼写三套 if-else |
| 工具会改檔案 / 跑命令 | 遠端 Mac 工作区 + 路径白名单 | JSON 再漂亮也挡不住错误 rm |
| 只要只读 SaaS API | 无伺服器函式 + 金鑰托管 | 无需整机;仍要做参数 allowlist |
推薦組合(Stack)
組合 A — 个人最快(半天)
- 单模型:OpenAI 或 Claude,注册 1–3 个只读工具(搜索、天气、文档检索)。
- 参数校驗:用 JSON Schema 库拒绝额外字段;金鑰放环境变量,永不进 prompt。
- 日誌:记录
tool, args_hash, latency, ok,不要把完整金鑰或 PII 打进日誌。
組合 B — 小团队生产(推薦)
- 閘道歸一:对内
ToolCall;对外 adapter 到 OpenAI / Gemini / Claude。 - L2 Policy:按工具分超时、並行、日限额;危险工具二次确认或仅在隔離节点运行。
- 执行面:只读 API 用函式计算;写仓库 / 跑测试放到 Cloud Mac 会话,对齐 遠端 Mac 自动化 的銷毀纪律。
- 记忆与工具参数分离:用户偏好走 Memory,不塞进每次
arguments。
組合 C — 企业合规
- 工具目錄走审批;schema 变更打 PR。
- MCP / 自定义 server 跑在无出网或受控 VPC;稽核 JSONL 出工作区。
- 禁止把
bash做成万能工具而不带路径策略。
常见誤區
- 以为模型会替你鉴权——Key 在 Runtime;模型只看到 schema 与历史结果。
- 把 OpenAI 的字符串 arguments 直接当对象用——先 parse,失败当模型错误重试或降级。
- description 写成「万能助手」——描述越泛,模型越爱乱调工具;写清何时不该呼叫。
- 忽略並行 tool_calls——两个呼叫共享可变状态会竞态;只读可並行,写入要串行或加锁。
- 把失败当无结果——应回传结构化 error,让模型改参数或换工具,而不是沉默。
- MCP = 安全——MCP 是传输与發現;root 设成家目錄照样裸奔。
落地步骤(7 步 Action Plan)
- 列出工具清单——名称、副作用级别(只读 / 写入 / 不可逆)、超时、数据驻留。
- 写 JSON Schema——必填、enum、禁止 additionalProperties;description 写边界。
- 实现最小 loop——一次用户消息 → 可能的 tool_calls → 执行 → 回填 → 最终回答;先单工具。
- 加校驗与金鑰隔離——parse 失败、schema 失败、未知工具名一律拒绝并稽核。
- 接第二家模型 adapter——证明内部
ToolCall可映射 Claude content 块与 Gemini parts。 - 给写入类工具加沙箱——路径白名单或遠端工作区;对照檔案系统指南做 deny 测试。
- 观测 7 天——统计误呼叫率、平均 round-trip、並行冲突;把高频误呼叫改成更窄的 schema。
FAQ
Function Calling 和普通 JSON mode 有什么区别?
JSON mode / structured output 約束的是最终回复形状(例如抽出订单字段)。Function Calling 約束的是中间的工具请求,并且預設会进入多轮:执行后再生成。两者可組合,但解决的问题不同。
模型会不会直接访问我的資料庫?
不会。除非你的 Runtime 把「任意 SQL」做成工具且未做权限。預設循环里模型只能提出符合 schema 的参数。
三家能不能共用一份 schema?
核心 JSON Schema 可以共用,再写成各家信封(OpenAI parameters、Gemini 大写 type、Claude input_schema)。枚举与必填要回归测试,因为各家对 strict 的实现不完全一样。
Claude Code 里的工具是 Function Calling 吗?
是同一类循环:模型产出结构化呼叫,宿主执行(含 MCP)。差别在工具目錄由 IDE/MCP 动态提供,而不是你每次在 HTTP 请求里手写 tools 数组。
工具结果要不要进长期记忆?
原始 API 响通常太大且含瞬时数据。应摘要后写入 Memory,而不是把整段 tool_result 当永久状态。详见 說明中心 与 Memory 选型文。
總結
2026 年谈 Function Calling,请把它当成带 schema 的 RPC:OpenAI、Gemini、Claude 只是三种 JSON 方言,共同语法是「模型提议、執行環境执行、结果回填」。选型看你的模型閘道与工具副作用;安全看 L2 校驗与执行面隔離,而不是看哪家的营销名。
上线前自问:如果模型此刻发出 write_file,实际落盘的是哪台机器、哪个目錄? 若答不上来,先别接写入类工具。只读 API 可以今天就接;改程式碼、跑测试,请放到可銷毀的 Cloud Mac 节点,并先看清 方案定價。