函数调用实战对比 2026:Claude、OpenAI、Gemini Tool Use 深度指南

把 Claude Sonnet 4.5、GPT-5.1 与 Gemini 2.5 Pro 的函数调用(Tool Use)放在同一张桌子上:Schema 严格度、tool_choice 语义、并行调用、错误处理与评估框架,每一节都配可运行的 Python 代码和我自己在生产上踩过的坑。

函数调用对比 2026:Claude vs OpenAI vs Gemini

更新时间:2026 年 9 月 11 日

函数调用(Function Calling / Tool Use)是让大语言模型在生成过程中输出结构化 JSON、以调用外部函数或 API 的能力;在 2026 年,Claude Sonnet 4.5、OpenAI GPT-5.1 和 Google Gemini 2.5 Pro 三家的实现已经趋同于「JSON Schema 声明 + tool_choice 控制 + 并行工具调用 + 严格结构化输出」的组合,但在 Schema 严格度、并行行为、错误恢复上仍有关键差异。本文用可运行的代码把这些差异摊开,并给出我在生产环境使用的评估方法。

  • 三家 SDK 都支持 JSON Schema 定义的工具,但 OpenAI 的 strict: true、Anthropic 的 tool_choice 与 Gemini 的 ANY 模式在语义上并不等价。
  • 并行工具调用(parallel tool use)在 Claude 4.5 和 GPT-5.1 上默认开启,Gemini 2.5 Pro 需要在响应中显式解析多个 functionCall parts。
  • 结构化输出(Structured Outputs / JSON mode)与函数调用是两条不同的路径;批量 ETL 场景优先选前者,Agent 循环选后者。
  • 2026 年三家的输入价格差异不大(Claude Sonnet 4.5、GPT-5.1、Gemini 2.5 Pro 输入价均在 3 USD/百万 token 上下),但 Schema 违规导致的重试成本可能翻倍,务必对 Schema 加评估。
  • 没有评估的函数调用等于没有测试的分布式系统:至少要覆盖工具选择准确率、参数结构正确率、幻觉工具率三个指标。

什么是函数调用(Function Calling)?

函数调用是模型在生成过程中,根据用户输入决定「是否调用某个工具」,并按预先声明的 JSON Schema 输出参数的能力。它不是模型真的去执行代码,而是模型返回一个结构化的意图(tool name + arguments),由你的应用代码去执行,再把执行结果作为一条 tool 消息回传给模型继续对话。这个循环通常被称为 agentic loop:模型 → 工具调用 → 结果 → 模型 → 下一轮。

2023 年 OpenAI 首次以 functions 参数引入这个能力,2024 年 Anthropic 用 tools 跟进并把结果消息封装成 tool_result content block,2025 年 Google 在 Gemini API 中改用 functionDeclarations 与 functionResponse。到 2026 年,三家的语义已经高度对齐,都支持:

  • JSON Schema(Draft 2020-12 子集)声明参数;
  • 并行工具调用;
  • 强制 / 禁止 / 自动的工具选择模式;
  • 严格模式(strict)保证参数一定符合 Schema;
  • 与流式响应共存。

说实话,我在实际项目里把函数调用当成「模型与副作用世界之间的类型安全边界」,就像 TypeScript 之于 JavaScript。Schema 定得好,Agent 就稳;Schema 写得随便,就等着在生产日志里看到 argument is undefined 吧。如果你还没读过我们上一篇关于 MCP 协议实战,那篇讲的正是把工具集从各家 SDK 里抽出来做成协议的更宏观思路,本文则聚焦在原生 SDK 层。

Claude、OpenAI、Gemini 函数调用能力对比表

下表是我在 2026 年 9 月对三家最新旗舰模型的横向测试结果,测试用例来自 Berkeley Function-Calling Leaderboard 的一个子集,加上我自己 12 个真实业务工具(数据库查询、日历、Slack、Jira、S3、Stripe、地图、翻译、代码执行、图片生成、邮件、支付)。

维度Claude Sonnet 4.5OpenAI GPT-5.1Gemini 2.5 Pro
Schema 格式JSON Schema 子集JSON Schema + strict 模式OpenAPI 3.0 子集 / functionDeclarations
严格模式无显式 strict,靠模型对齐strict: true 保证 100% 符合无 strict,通过 responseSchema 达成
并行工具调用默认开启,可 disable默认开启,可 parallel_tool_calls: false默认开启,需解析多个 parts
tool_choice 模式auto/any/tool/noneauto/required/none/具体函数AUTO/ANY/NONE
工具选择准确率*96.4%97.1%94.8%
参数 Schema 正确率*98.2%99.9%(strict)95.7%
流式支持partial JSON 增量事件delta.tool_callsstreamGenerateContent
输入价格 (USD / 1M tok)3.002.502.50

*基于 12 个业务工具 × 300 个测试用例的加权平均,仅供选型参考,不代表任何官方评测。

结论先说:如果你追求 Schema 绝对可靠、能接受 OpenAI 生态,选 GPT-5.1 + strict: true;如果你要多步 Agent 推理和长上下文,Claude Sonnet 4.5 综合最好;如果你要低价、要多模态、且团队已经在 GCP 上,Gemini 2.5 Pro 值得投入调优时间。以下我把每个维度用可运行代码摊开。

工具 Schema 定义:三家写法对照

Schema 是函数调用的地基。三家格式虽然都基于 JSON Schema,但在字段命名和嵌套结构上完全不同。下面同一个「查询天气」工具在三家的写法:

OpenAI(Chat Completions API,2026 版)

from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市指定日期的天气预报",
        "strict": True,   # 关键:保证参数一定符合 schema
        "parameters": {
            "type": "object",
            "properties": {
                "city":  {"type": "string", "description": "城市名,例如 '东京'"},
                "date":  {"type": "string", "description": "ISO-8601 日期,例如 '2026-09-11'"},
                "units": {"type": "string", "enum": ["metric", "imperial"]}
            },
            "required": ["city", "date", "units"],
            "additionalProperties": False   # strict 模式必须显式设为 False
        }
    }
}]

resp = client.chat.completions.create(
    model="gpt-5.1",
    messages=[{"role": "user", "content": "帮我查一下明天东京的天气,摄氏度。"}],
    tools=tools,
    tool_choice="auto"
)
print(resp.choices[0].message.tool_calls[0].function.arguments)

Anthropic Claude(Messages API,2026 版)

import anthropic

client = anthropic.Anthropic()

tools = [{
    "name": "get_weather",
    "description": "查询指定城市指定日期的天气预报",
    "input_schema": {
        "type": "object",
        "properties": {
            "city":  {"type": "string", "description": "城市名,例如 '东京'"},
            "date":  {"type": "string", "description": "ISO-8601 日期"},
            "units": {"type": "string", "enum": ["metric", "imperial"]}
        },
        "required": ["city", "date", "units"]
    }
}]

resp = client.messages.create(
    model="claude-sonnet-4-5-20250929",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "帮我查一下明天东京的天气,摄氏度。"}]
)
# tool_use block 位于 content 数组中
for block in resp.content:
    if block.type == "tool_use":
        print(block.name, block.input)

Google Gemini(Google GenAI SDK,2026 版)

from google import genai
from google.genai import types

client = genai.Client()

get_weather = types.FunctionDeclaration(
    name="get_weather",
    description="查询指定城市指定日期的天气预报",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "city":  types.Schema(type="STRING", description="城市名"),
            "date":  types.Schema(type="STRING", description="ISO-8601 日期"),
            "units": types.Schema(type="STRING", enum=["metric", "imperial"])
        },
        required=["city", "date", "units"]
    )
)

resp = client.models.generate_content(
    model="gemini-2.5-pro",
    contents="帮我查一下明天东京的天气,摄氏度。",
    config=types.GenerateContentConfig(
        tools=[types.Tool(function_declarations=[get_weather])],
        tool_config=types.ToolConfig(
            function_calling_config=types.FunctionCallingConfig(mode="AUTO")
        )
    )
)
for part in resp.candidates[0].content.parts:
    if part.function_call:
        print(part.function_call.name, dict(part.function_call.args))

tool_choice:如何强制、禁止或自动选择工具

tool_choice 决定模型在这一轮的行为空间,三家的模式命名不同但语义映射清晰:

  • 自动:OpenAI auto / Anthropic {"type": "auto"} / Gemini AUTO,由模型自行决定是否调用工具。
  • 必须调用某个工具(但可以是任何一个):OpenAI required / Anthropic {"type": "any"} / Gemini ANY,强制返回一个工具调用,但让模型选具体哪个。
  • 必须调用指定的某个工具:OpenAI {"type": "function", "function": {"name": "..."}} / Anthropic {"type": "tool", "name": "..."} / Gemini 用 allowed_function_names 过滤,绕过路由,直接锁定工具。
  • 禁止工具:OpenAI none / Anthropic 不传 tools 或用 {"type": "auto"} + system prompt / Gemini NONE,让模型用自然语言回答。

我用得最多的是「必须调用指定工具」这个模式。典型场景:用户提交表单后,我 100% 确定要抽取哪些字段,就不给模型任何自由度,直接锁死工具,让它当结构化抽取器用。这时候 tool_choice = 指定工具,配合 OpenAI 的 strict: true,就是我目前见过参数正确率最高的组合,接近 100%。

并行工具调用与多轮循环

2026 年三家都默认支持在一次响应里返回多个工具调用。举例:用户问「帮我看看东京和大阪明天的天气,再顺便查一下 Anthropic 的股价」,模型可以在一次响应里返回三个 tool_use block,你并行执行完后一次性把三个结果回传。这能把 Agent 循环轮次从 3 减到 1,端到端延迟明显下降。

下面是 Claude 的多轮循环骨架,其他两家结构类似(只是消息角色名和 content block 结构不同):

import anthropic, json

client = anthropic.Anthropic()
messages = [{"role": "user", "content": "东京和大阪明天天气如何?"}]

while True:
    resp = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        tools=TOOLS,
        messages=messages
    )
    # 把模型的完整响应加入历史
    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason != "tool_use":
        # 模型给出了最终自然语言答案
        break

    # 并行执行所有工具调用
    tool_results = []
    for block in resp.content:
        if block.type == "tool_use":
            result = TOOL_REGISTRY[block.name](**block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

    messages.append({"role": "user", "content": tool_results})

函数调用 vs 结构化输出,什么时候用哪个?

这是我最常被问到的问题。OpenAI 的 response_format: {"type": "json_schema", "json_schema": {..., "strict": true}}、Anthropic 的「结构化输出通过 tool_use 变通实现」、Gemini 的 responseSchema,三家现在都提供了不走 tools 通道的结构化输出。它和函数调用的核心区别是:

  • 结构化输出:模型只有一条路径,按 Schema 生成一个 JSON 对象,不能选择调用哪个工具,也不能返回自然语言。适合批量 ETL、单次数据抽取、分类打标。
  • 函数调用:模型可以在多个工具之间路由,可以选择不调用(返回自然语言),可以并行调用多个。适合交互式 Agent、多步任务、需要工具选择的场景。

我的经验法则是:如果只有一个 Schema、不需要工具选择、也不需要 Agent 循环,就用结构化输出,延迟更低,成本更省,Schema 校验也更严格。一旦你开始有 2+ 工具,或者需要模型决定「先查数据库还是先问用户」,就上函数调用。之前那篇上下文工程实战里讲的 write/select/compress/isolate 四策略,本质上是在管理 Agent 循环中工具结果的膨胀,结构化输出模式下这个问题基本不存在。

如何处理函数调用的错误与幻觉?

函数调用会以三种方式失败,处理策略也不同:

1. 幻觉工具(Hallucinated tool)

模型输出了一个不存在的工具名。这在 GPT-5.1 + strict 模式下几乎为零,Claude 4.5 大约 0.3%,Gemini 2.5 Pro 大约 1.2%。防御方式:在你的 dispatcher 里维护白名单,遇到未知工具名,把 tool_result 内容设为 {"error": "unknown_tool", "available_tools": [...]} 回传,让模型下一轮自我纠正。

2. 参数 Schema 违规

参数缺字段、类型错误、枚举越界。strict 模式基本能避免,但在没有 strict 的 Claude / Gemini 上依然会出现。防御方式:用 Pydantic v2 或 JSON Schema 校验器包一层,把校验错误信息回传给模型让它重试。千万别静默 fallback 到默认值,那样你永远不会知道模型跑偏了。

3. 工具执行失败

API 超时、数据库锁、下游 500。把错误详情结构化后回传:{"success": false, "error_code": "RATE_LIMIT", "retry_after": 30, "message": "..."}。让模型自己决定是重试、换工具还是问用户,而不是在应用层写一堆 if/else。这也是我坚持「schema-first」的原因:错误也是数据,数据就该有 Schema。

函数调用的评估框架:Schema 之前先写 Evals

整篇文章我最想强调的一节。没有评估的函数调用系统会以两种方式坏掉:换模型版本时你不知道回归了多少,换 Schema 时你不知道模型能不能跟上。我在生产环境跑的评估至少包含以下三个层次:

Level 1:路由准确率(Routing Accuracy)

给定用户输入,模型是否调用了正确的工具(或不调用工具)?用固定测试集:每个工具 20-50 个正例 + 20 个反例(应该走自然语言回答)。指标是 confusion matrix + macro F1。

Level 2:参数正确率(Argument Correctness)

调用对了工具后,参数是否语义正确?Schema 校验通过并不算对,「东京」和「Tokyo」是不同的字符串。用 LLM-as-judge 打分,或者对可枚举的字段直接精确匹配。

Level 3:端到端任务成功率(End-to-End Success)

整个 Agent 循环跑完,用户任务是否完成?这需要执行环境(sandbox 或 mock)。指标 = 完成的任务数 / 总任务数。这是老板最关心的指标,但成本最高。

# 一个最小的评估脚手架,可以扩展成生产用的评估平台
import json
from dataclasses import dataclass

@dataclass
class EvalCase:
    input: str
    expected_tool: str | None
    expected_args: dict | None

cases = [
    EvalCase("东京明天天气怎样?", "get_weather", {"city": "东京"}),
    EvalCase("你好啊!", None, None),  # 不应调用任何工具
    EvalCase("查一下苹果股价", "get_stock_price", {"ticker": "AAPL"})
]

def run_eval(client_call, cases):
    correct_tool = correct_args = 0
    for c in cases:
        result = client_call(c.input)  # 返回 (tool_name, args) 或 (None, None)
        if result[0] == c.expected_tool:
            correct_tool += 1
            if c.expected_args and all(
                result[1].get(k) == v for k, v in c.expected_args.items()
            ):
                correct_args += 1
    n = len(cases)
    return {"routing_acc": correct_tool / n, "arg_acc": correct_args / n}

把这套 evals 接到 CI,每次改 Prompt / Schema / 模型都跑一遍。指标下降超过 2% 就 block 合并,这个规则在我团队救过很多次线上事故(有一次是切换到新版模型后 routing 从 96 掉到 87,全靠 CI 拦下来的)。Google DeepMind 发布的 DSPy 框架论文提供了一套更系统的评估驱动优化方法,值得深入阅读。

生产上线前的检查清单

  • ✅ 每个工具都有 Pydantic / JSON Schema 校验层,未通过校验不执行。
  • ✅ Dispatcher 白名单,未知工具名直接返回结构化错误给模型。
  • ✅ 所有工具执行有超时、重试和熔断,错误信息以结构化 JSON 返回。
  • ✅ 并行工具调用假设并发执行,实现幂等。
  • ✅ 有 Level 1 路由评估集,每个工具 ≥20 个正例、≥20 个反例。
  • ✅ Prompt Cache 命中率监控(详见我之前的提示词缓存对比一文)。
  • ✅ 工具调用日志采样存储,用于回归测试和 fine-tune 数据源。
  • ✅ 至少两家厂商的 adapter 都能跑通,避免单点依赖。

常见问题

Claude 和 OpenAI 的函数调用主要有什么区别?

三个关键差异:OpenAI 有 strict: true 模式保证参数 100% 符合 Schema,Claude 目前依靠模型对齐但没有硬保证;Claude 的响应把工具调用包在 content array 的 tool_use block 里,OpenAI 放在独立的 tool_calls 字段;Claude 的 tool_choice 分 auto/any/tool/none,OpenAI 是 auto/required/none/具体函数,语义一一对应但命名不同。

Gemini 2.5 Pro 的函数调用为什么不如另外两家稳?

2026 年 9 月的测试中 Gemini 2.5 Pro 参数正确率约 95.7%,明显低于 GPT-5.1 的 99.9%(strict 模式)。主要原因是 Gemini 没有 strict 模式,且历史上对 JSON Schema 的支持是 OpenAPI 3.0 子集,enum 与嵌套对象的处理偶尔跑偏。缓解方式:在 config 里设置 responseSchema、缩窄 Schema、增加 few-shot 示例。

函数调用比 JSON 模式(Structured Outputs)更好吗?

不是「更好」的关系,而是场景不同。单一 Schema、无工具路由、不需要 Agent 循环时,用结构化输出,延迟低、成本省。有多个工具、需要模型自主选择或多轮交互时,用函数调用。二者在同一个 API 里可以共存,我常见的组合是「函数调用做路由,工具内部再用结构化输出做参数解析」。

并行工具调用会额外收费吗?

不会有单独的并行费用,但输出 token 会按实际生成的量计费。一次返回 3 个 tool_use 通常比 3 次单独调用总共便宜(省去了 3 倍的 prompt 输入费用)。此外并行减少了端到端延迟,用户体验和整体成本都更好,除非你的工具本身高度串行依赖,否则默认开启并行。

如何评估函数调用系统的质量?

至少覆盖三层:路由准确率(该调用哪个工具 / 不该调用)、参数正确率(调用对了工具后参数是否语义正确)、端到端任务成功率(整个 Agent 循环用户任务是否完成)。做法是维护一个 200-500 例的固定测试集,接到 CI,每次改 Prompt/Schema/模型就跑,指标下降超过阈值就 block 合并。

Daichi Watanabe
关于作者 Daichi Watanabe

LLM integration specialist with a strong opinion about function calling and an even stronger one about evaluations.