你构建了一个带有可用工具调用的智能体,然后更换了其背后的模型,工具调用就开始失败。你的工具定义没有改变。模型期望的请求和响应格式变了,因为每个提供商对工具定义、工具调用响应、参数编码和工具结果都使用自己的结构。如果你的技术栈中没有任何东西在这些结构之间进行转换,那么更换模型就会变成编写新的解析代码和引入新的故障模式。
本文比较了六个智能体框架如何定义工具 schema 并在不同提供商之间进行转换,以及每种情况下转换在何处运行。然后展示了我们如何在 API 层规范化工具调用,使你的应用程序发送和接收的结构在 OpenRouter 上每个支持工具调用的模型上都保持一致。
简而言之
- OpenAI、Anthropic 和 Google 各自以不同的传输格式定义工具并返回工具调用。为其中一家编写的工具定义无法原样在另一家上使用。
- LangChain 和 LangGraph 为每个提供商转换一个工具定义。CrewAI 将转换委托给它所路由到的客户端。OpenAI Agents SDK、Claude Agent SDK 和 Google ADK 都围绕单一提供商的格式构建。Microsoft Agent Framework 委托给所配置的模型连接器。
- OpenRouter 接受 OpenAI 风格的
tools数组,并为每个支持工具调用的模型返回标准的tool_calls响应,因此切换模型只需更改模型字符串。 - 在包含工具的请求上,Auto Exacto 默认使用吞吐量、工具调用成功率和基准测试数据对提供商重新排序,其背后的工具调用错误率指标可在每个模型的 Performance 标签页中查看。
为什么工具调用 schema 在不同提供商之间有所不同
各提供商对工具是什么达成了一致。一个工具有一个名称、一个描述和一个参数 schema,说明它接受哪些参数。模型读取这三部分,决定是否调用该工具,并生成参数。
共识到此为止。每个提供商将这些部分包装在自己的请求和响应格式中,而这些格式无法互换。
在OpenAI 的 Chat Completions API中,你传入一个tools数组,其中每个条目都有type: "function"以及一个function对象,包含名称、描述和 JSON Schemaparameters. 当模型想要调用工具时,assistant 消息会携带一个tool_calls数组,每个调用的arguments字段是一个 JSON 编码的字符串。
{
"tools": [
{
"type":"function",
"function": {
"name":"get_weather",
"description":"Get the current weather for a city",
"parameters": {
"type":"object",
"properties": {"city": {"type":"string"} },
"required": ["city"]
}
}
}
]
}
Anthropic 的 Messages API使用更扁平的定义,schema 位于input_schema之下。模型的请求会作为 assistant 消息中的tool_usecontent block 返回,参数已经解析为一个input对象,并且该轮以stop_reason: "tool_use"结束。
{
"tools": [
{
"name":"get_weather",
"description":"Get the current weather for a city",
"input_schema": {
"type":"object",
"properties": {"city": {"type":"string"} },
"required": ["city"]
}
}
]
}
Google 的 Gemini API将工具定义嵌套在function_declarations请求中的generateContent数组之下,模型的请求会作为响应内容中的functionCallpart 返回,而不是作为单独的顶层字段。Google 较新的 Interactions API 又接受一种不同的工具条目结构,type: "function"位于每个工具的顶层。
{
"tools": [
{
"function_declarations": [
{
"name":"get_weather",
"description":"Get the current weather for a city",
"parameters": {
"type":"object",
"properties": {"city": {"type":"string"} },
"required": ["city"]
}
}
]
}
]
}
这就是一个工具、三种请求结构和三种响应结构。开源权重模型还带来另一种情况。一个未被训练为输出工具调用格式的模型,只能以文本形式产生工具调用。对此类模型的任何工具调用支持,都取决于服务层将工具定义放入提示词,并把模型的输出解析回调用。这种解析不属于任何提供商的线上格式,而且它的失败方式与原生格式不同。
你的应用定义了一个工具,但它必须生成的确切请求结构取决于请求背后的模型。要么你技术栈中的某个组件把这一份定义重写成正确的按模型区分的格式,要么你自己编写并维护这种转换。
schema 转换可以放在哪里
转换可以运行在三个地方。
- 在你的应用中。你自己编写按提供商区分的请求和响应映射,并在提供商变化时维护它。
- 在框架中。智能体框架,或它所委托的提供商客户端,接收一个工具定义并生成每个提供商的格式。
- 在 API 层。每个模型前面的网关接受一种格式,并在发往每个提供商的过程中进行转换。我们的工具调用和结构化输出文档完整展示了规范化后的形态。
下面这些框架的主要区别在于它们承担了第二种方案的多少工作,以及它们以哪个提供商作为原生格式。
每个框架如何处理工具 schema
这里的每个框架都支持工具调用。区别在于转换逻辑位于何处,以及框架自身承担了多少。
LangChain 和 LangGraph
你只需使用@tool装饰器将一个工具定义为带类型提示的 Python 函数,类型提示即定义了工具的输入 schema。你通过bind_tools()将工具附加到模型上,而create_agent负责运行工具调用循环。LangChain 的聊天模型集成会将定义转换为每个提供商的格式,因此同一份智能体代码可以通过init_chat_model在 OpenAI、Anthropic 和 Google 模型上运行。
这一抽象层将线路格式的差异对你的代码隐藏起来。任何给定转换的质量取决于具体的提供商集成,因此请在你计划上线的确切模型上进行测试。LangChain 还记录了OpenRouter 聊天模型集成位于langchain-openrouter包中,因此你可以通过init_chat_model选择任意 OpenRouter 模型,并将各提供商的转换交给我们的 API。
对于 MCP,LangChain 记录了MCPAdapter,它构建在 FastMCP 之上,可发现服务器的工具并将其适配为 LangChain 工具。langchain.mcp命名空间需要langchain[mcp]>=1.4.0,且被标注为 beta。
CrewAI
CrewAI 围绕角色和任务进行组织。你将工具分配给智能体,由 crew 协调工作。CrewAI 自身不实现针对特定提供商的工具格式化。
CrewAI 的LLM 文档描述了针对 OpenAI、Anthropic、Google 的 Gemini API、Azure、AWS Bedrock 和 Snowflake Cortex 的原生 SDK 集成,通过你配置的provider/model-id字符串进行选择。所有其他提供商都通过 LiteLLM 运行。无论哪种情况,schema 处理都归属于 CrewAI 路由到的客户端,而非 CrewAI 本身。
对于 MCP,crewai-tools包提供了MCPServerAdapter以及用于 stdio、SSE 和可流式 HTTP 服务器的传输类。
OpenAI Agents SDK
TheOpenAI Agents SDK是围绕 OpenAI 的工具格式构建的。你用@function_tool装饰器定义函数工具,SDK 会根据函数签名和 docstring 生成参数的 JSON Schema。它在 OpenAI 模型上提供最完整的支持,包括在 OpenAI 侧运行的托管工具。
它并不局限于 OpenAI。该 SDK 的模型文档描述了通往其他提供商的三种内置路径。set_default_openai_client通过设置base_url和api_key将 SDK 指向一个 OpenAI 兼容的端点,ModelProvider将自定义提供商应用于单次运行,而Agent.model为单个智能体设置模型。Any-LLM 和 LiteLLM 被记录为尽力而为的 beta 第三方适配器,用于内置路径未覆盖的情况。当你通过 Chat Completions 而非 Responses API 进行路由时,SDK 会丢弃仅 Responses 支持的字段,文档还警告说某些提供商不支持 JSON Schema 结构化输出。你离 OpenAI 的模型越远,就越依赖兼容层而非第一方支持。
MCP 支持是内置的,涵盖通过 OpenAI 的 Responses API 使用托管 MCP 服务器工具,以及通过 stdio 和 HTTP 传输直接连接 MCP 服务器。
Claude Agent SDK
TheClaude Agent SDK是 Claude Code 背后的智能体框架,打包为 Python 和 TypeScript 库。与 Anthropic Messages API 不同,它为你运行工具执行循环,内置工具、上下文管理、权限、钩子和子智能体。你在 Python 中用@tool装饰器、在 TypeScript 中用tool()定义自定义工具,用create_sdk_mcp_server将它们包装进进程内 MCP 服务器,并将该服务器传给查询。
它面向 Claude 模型,因此工具调用直接使用 Anthropic 的原生tool_use格式。没有跨提供商转换层。将其指向非 Anthropic 模型并非它的设计用途。
Microsoft Agent Framework
Microsoft 将Agent Framework描述为 AutoGen 和 Semantic Kernel 的直接继任者,结合了 AutoGen 的智能体抽象与 Semantic Kernel 的企业功能,并增加了显式工作流。其概览将 Microsoft Foundry、Anthropic、Azure OpenAI、OpenAI 和 Ollama 列为支持的模型提供商,工具调用和 MCP 服务器通过智能体抽象处理。
工具作为带类型的函数附加到智能体,框架会将其转换为带 JSON Schema 的函数工具。工具调用的转换归属于你所配置的模型连接器,因此更换提供商也会随之改变 schema 的处理方式。Microsoft 发布了一份从 AutoGen 迁移的指南供从旧框架迁移的团队使用。
Google ADK
Google 的Agent Development Kit专为 Gemini 及其原生function_declarations格式而构建。当你把一个 Python 函数传入智能体的工具列表时,ADK 会将其包装为一个FunctionTool并根据函数的签名和 docstring 生成 schema。
对于 Gemini 之外的模型,ADK 提供了连接器文档,其中包括一个LiteLLM 连接器。在这条路径上,对 Claude 或 GPT 模型的调用会经过 LiteLLM 的转换,而不是通过 ADK 原生的任何机制,因此这些模型上的 schema 行为归属于该集成。请测试你计划使用的确切模型。
对于 MCP,ADK 提供了McpToolset,它会连接到一个 MCP 服务器并将其工具暴露给智能体。
框架对比
在选择框架之前,请按工具调用支持筛选我们的模型目录。框架列出某个提供商适配器,与模型支持原生工具调用,是两回事,而两者之间的差距正是意外所在。
| 框架 | 你如何定义工具 | 谁负责跨提供商转换 schema | MCP 支持 |
|---|---|---|---|
| LangChain 和 LangGraph | 带类型提示的 Python 函数以及@tool装饰器 | LangChain 的各提供商聊天模型集成 | MCPAdapter位于langchain.mcp命名空间中,文档标注为 beta |
| CrewAI | 按角色分配给智能体的工具 | 路由客户端,要么是原生提供商 SDK,要么是 LiteLLM | MCPServerAdapter位于crewai-tools |
| OpenAI Agents SDK | @function_tool带有生成的 JSON Schema | 没有第一方转换。其他提供商通过 OpenAI 兼容端点或 beta 版 Any-LLM 和 LiteLLM 适配器接入 | 内置,支持托管和直连 MCP 服务器 |
| Claude Agent SDK | @tool位于进程内 MCP 服务器中 | 无。设计上仅支持单一提供商 | 内置,带进程内服务器的 MCP 客户端 |
| Microsoft Agent Framework | 带类型的函数转换为函数工具 | 所配置的模型连接器 | 内置,通过智能体抽象接入 MCP 服务器 |
| Google ADK | Python 函数包装为FunctionTool | 没有原生跨提供商路径。非 Gemini 模型通过 LiteLLM 等连接器接入 | McpToolset |
把这张表当作起点,在正式采用前先查阅最新文档。这些框架发布频繁,而工具调用行为正是会变化的部分之一。
切换模型时哪些地方会出问题
这些故障有几种形态,而上面提到的框架都无法消除它们。
某个提供商接受的 schema 会被更严格的提供商拒绝。一个深度嵌套的参数对象、一个不常见的类型,或是一条被某个提供商忽略、却被另一个提供商强制执行的约束,都足以在新模型上引发请求错误。
没有原生工具调用能力的模型会把调用作为纯文本返回。流水线中没有任何东西会把它识别为tool_call,所以没有错误可捕获,只有一个不符合你循环预期的响应。除非框架或你自己的代码加上文本解析兜底,否则智能体会停止推进,而不是抛出错误。
修复行为各不相同。有些代码路径在调用返回格式错误时会抛出可捕获的错误。另一些则把检测和恢复留给你。如果你的智能体循环假设了其中一种行为,而框架提供的是另一种,那么换模型就可能让错误悄无声息地溜过去。
还有一种更隐蔽的版本,无需切换模型家族就会出现。同一个模型由两个不同的提供商提供服务时,返回有效工具调用的比率可能不同。自 2025 年 8 月起,我们对 OpenRouter 上的每一次工具调用响应进行了评分,在我们的Auto Exacto 公告中,我们报告称,在我们开始将工具调用流量从较弱的端点转移开之后,受影响提供商上 GLM-5 和 GLM-4.7 的工具调用错误率从约 8% 降至接近 1%。
我们把每一次失败的工具调用归为三类,同样的三项检查也适用于你自己的日志记录。InvalidJson表示参数无法解析为 JSON。UnknownName表示被调用的函数名不在请求的工具列表中。SchemaMismatch表示参数无法通过该工具参数 schema 的校验。关于循环机制,包括重试、停止条件和逐轮控制,请参阅我们的构建指南工具调用智能体循环。
在 API 层规范化工具调用
翻译可以存在的第三个地方是 API 层,位于框架之下。我们在每个模型前面统一做一次工具调用规范化,这样它之上的任何东西都看不到各家提供商的差异。
你发送一个 OpenAI 风格的请求,带有一个tools数组。我们将其转换为目标提供商的格式,运行它,并返回一个标准的tool_calls响应。对于每个支持工具调用的模型,你发送和接收的结构都是一样的,所以切换模型只是改一下模型字符串。
下面的示例用 OpenAI 格式定义一次工具,并从message.tool_calls读取结果,无论请求是由哪个模型运行的。
importjson
importos
fromopenaiimportOpenAI
client=OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
defget_weather(city:str) ->dict:
return{"city":city,"forecast":"sunny","temperature_c":24}
tools=[{
"type":"function",
"function":{
"name":"get_weather",
"description":"Get the current weather for a city",
"parameters":{
"type":"object",
"properties":{"city":{"type":"string"}},
"required":["city"],
},
},
}]
messages=[{"role":"user","content":"What's the weather in Lisbon?"}]
# 把这个字符串改成 anthropic/claude-sonnet-4.6 或 google/gemini-3.5-flash
# 这个文件里其他任何东西都不用改。
response=client.chat.completions.create(
model="openai/gpt-5.5",
messages=messages,
tools=tools,
)
choice=response.choices[0]
messages.append(choice.message)
# 每个支持工具的模型都会返回相同的 tool_calls 结构。
forcallinchoice.message.tool_callsor[]:
args=json.loads(call.function.arguments)# arguments 是一个 JSON 字符串
result=get_weather(**args)
messages.append({
"role":"tool",
"tool_call_id":call.id,
"content":json.dumps(result),
})
有两样东西在每个模型之间都是通用的。工具定义和你解析的响应保持不变。tool_calls始终是一个数组,每个arguments值都是一个 JSON 字符串,所以要遍历这个数组并解析每个条目,而不是假设只有一次调用。
这并不是智能体框架的替代品。你仍然需要某个东西来运行循环、保存状态并决定何时停止。它消除的是你应用程序中针对每个模型的工具调用适配代码。schema 转换从你维护的层转移到了你调用的层。

工具调用请求的提供商路由
规范化格式只是可靠性问题的一半。另一半是哪个提供商来执行调用。由于我们对每个工具调用响应都进行评分,我们可以将工具调用请求路由到最常返回有效工具调用的端点。
Auto Exacto默认对每个包含工具的请求运行,无需配置。它使用三个信号为你所选模型重新排序可用的提供商。吞吐量是每个端点的实时每秒 token 数测量值。工具调用成功率源自工具调用错误率指标。基准测试数据来自我们自己的基准测试框架,该框架按周期对已注册的提供商端点运行 GPQA Diamond 和 Tau2-Bench Airline。
工具调用错误率指标位于每个模型页面的 Performance 标签页上。对于每个包含工具的请求,我们检查模型返回的每个工具调用,并验证其arguments是否符合tools[].function.parameters你提供的 schema,使用 JSON Schema Draft 7。schema 缺失或无法编译的工具调用计为有效,因此当调用方的 schema 格式错误时,该指标保持保守。该指标按请求聚合,而非按工具调用聚合,因此一个包含多个错误调用的请求只计一次。parametersschema is absent or fails to compile counts as valid, so the metric stays conservative when a caller’s schema is malformed. The metric is aggregated per request rather than per tool call, so one request with several bad calls counts once.
Auto Exacto 可能与保持提示缓存热度的粘性提供商路由冲突,因为它可能在会话中途将请求移动到不同的端点。如果对于你的智能体循环来说缓存命中率比提供商重新排序更重要,文档描述了如何通过sort: "price"在provider对象或:floor模型变体中来禁用 Auto Exacto。
在你依赖任一机制之前,有两项检查适用。工具调用并非普遍支持,因此请通过上方的目录筛选器或模型页面确认你路由到的每个模型都支持它。如果你在多个模型上运行同一个智能体来比较质量,请同时比较成本。当前的各模型费率位于定价页面。
结论
工具调用并非只有一种格式。有些框架是翻译器,有些原生只支持单一提供商,还有些把这个问题交给底层的连接器或客户端。如果你需要在某一个提供商上获得最深入的支持,像 OpenAI Agents SDK 或 Claude Agent SDK 这样的原生 SDK 是最贴合的选择。如果你需要跨提供商迁移,LangChain 和 LangGraph 在框架层承担了最多的翻译工作,或者你也可以通过在 API 层规范化工具调用来把翻译移出框架。在开始构建之前先决定 schema 翻译发生在哪里,这样更换模型就只是一行代码的改动。
常见问题
各大智能体框架在处理工具调用 schema 时有何不同?
它们的差异在于 schema 翻译在哪里运行。LangChain 和 LangGraph 通过各提供商的聊天模型集成,把一份工具定义翻译成每个提供商的格式。CrewAI 把翻译委托给它所路由到的提供商 SDK 或 LiteLLM 客户端。OpenAI Agents SDK、Claude Agent SDK 和 Google ADK 各自围绕单一提供商的格式构建,并通过兼容端点、适配器或连接器触达其他提供商。Microsoft Agent Framework 把翻译交给你所配置的模型连接器。在 API 层规范化工具调用则把翻译移到它们所有之下。
进行工具调用时应该使用哪个智能体 SDK?
让 SDK 匹配你所需的那组模型。如果你打算一直使用某个提供商的模型,那么像 OpenAI Agents SDK 或 Claude Agent SDK 这样的提供商原生 SDK 是最贴合的选择。LangChain 和 LangGraph 在跨提供商迁移时承担了最多的翻译工作。CrewAI 把翻译委托给它所路由到的客户端。通过 OpenRouter 路由可以让工具定义和响应结构在不同模型间保持一致,因此 SDK 的选择不再限制模型的选择。
哪些智能体 SDK 支持 MCP?
OpenAI Agents SDK、Claude Agent SDK、Microsoft Agent Framework、Google ADK、LangChain 和 CrewAI 都记录了支持将 Model Context Protocol 服务器作为工具来源。集成深度各不相同,LangChain 当前的 MCP 命名空间被标注为 beta。MCP 标准化了工具的发现和描述方式。它并未标准化模型的工具调用传输格式,这是本文所讨论的另一个独立问题。
如何让一个长时间运行的工具调用循环在不同模型之间保持正常工作?
调用模型,检查结束原因,运行每个请求的工具,按工具调用 ID 追加结果,然后重复,直到模型不再请求工具或达到步数上限。在执行工具参数之前先验证它们,因为模型可能返回不符合你 schema 的参数。规范化工具调用格式,使循环没有针对每个模型的分支,并路由到工具调用错误率较低的提供商。
参考资料
- 工具调用,OpenRouter
- 结构化输出,OpenRouter
- Auto Exacto,OpenRouter
- Auto Exacto:自适应质量路由,默认开启,OpenRouter
- 在 OpenRouter 上构建可靠的工具调用智能体循环,OpenRouter
- 函数调用,OpenAI
- 工具使用概览,Anthropic
- 使用 Gemini API 进行函数调用,Google
- 模型,工具,以及MCP,LangChain
- LLM和将 MCP 服务器作为工具,CrewAI
- 模型,工具,以及MCP,OpenAI Agents SDK
- Agent SDK 概览和自定义工具,Anthropic
- Agent Framework 概览和从 AutoGen 迁移指南,Microsoft
- 模型,LiteLLM 连接器,函数工具,以及MCP 工具,Google ADK