跳到正文
原文
OpenRouter:Announcements(RSS)·· 1 小时前精选AI 评分61

OpenRouter 对比六大 Agent 框架的工具调用 schema 处理方式

Agent Frameworks Compared: Tool-Calling Schema Handling

AI 导读

OpenRouter 发布长文,对比 LangChain、LangGraph、CrewAI、OpenAI Agents SDK、Claude Agent SDK、Microsoft Agent Framework 和 Google ADK 如何定义工具 schema 以及跨 provider 的翻译位置。

推荐理由

原文逐家拆解六个 Agent 框架的工具调用 schema 翻译位置,并给出在 API 层归一化的可迁移做法。

正文 · AI 翻译

你构建了一个带有可用工具调用的智能体,然后更换了其背后的模型,工具调用就开始失败。你的工具定义没有改变。模型所期望的请求和响应格式变了,因为每个提供商对工具定义、工具调用响应、参数编码和工具结果都使用自己的格式。如果你的技术栈中没有任何东西在这些格式之间进行转换,那么更换模型就会变成编写新的解析代码和引入新的故障模式。

本文比较了六个智能体框架如何定义工具模式并在不同提供商之间进行转换,以及每种情况下转换发生在哪里。然后展示了我们如何在 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 选项卡中查看。

为什么工具调用模式在不同提供商之间有所不同

各提供商对工具是什么的看法一致。一个工具有一个名称、一个描述和一个参数模式,说明它接受哪些参数。模型读取这三部分,决定是否调用该工具,并生成参数。

共识到此为止。每个提供商将这些部分包装在自己的请求和响应格式中,而这些格式无法互换。

在 OpenAI 的 Chat Completions API 中,你传入一个 tools 数组,其中每个条目都有一个 type: "function" 和一个 function 对象,该对象包含名称、描述和 JSON Schema parameters。当模型想要使用工具时,助手消息携带一个 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 使用更扁平的定义,模式位于 input_schema 下。模型的请求以助手消息内的 tool_use 内容块形式返回,参数已经解析为 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 将工具定义嵌套在 generateContent 请求中的 function_declarations 数组下,模型的请求以响应内容中的 functionCall 部分返回,而不是作为单独的顶层字段。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 转换可以放在哪里

转换可以运行在三个地方。

  • 在你的应用中。你自行编写并维护各提供商的请求与响应映射,并随着提供商的变化持续维护。
  • 在框架中。Agent 框架,或它委托的提供商客户端,接收一份工具定义并生成每个提供商的格式。
  • 在 API 层中。位于所有模型之前的网关接受一种格式,并在发往各提供商时进行转换。我们的 tool calling 和 structured outputs 文档完整展示了归一化后的结构。

下面这些框架的主要区别在于它们承担了第二种方案的多少工作,以及它们以哪个提供商作为原生格式。

各框架如何处理工具 schema

这里的每个框架都支持工具调用。区别在于转换发生在哪里,以及框架承担了多少转换工作。

LangChain 和 LangGraph

你使用 @tool 装饰器,以带类型提示的 Python 函数定义一次工具,类型提示即定义了工具的输入 schema。你通过 bind_tools() 将工具附加到模型,由 create_agent 运行工具调用循环。LangChain 的 chat model integrations 会把该定义转换为各提供商的格式,因此同一份 agent 代码可以通过 init_chat_model 在 OpenAI、Anthropic 和 Google 模型上运行。

这层抽象将线格式差异对你的代码隐藏起来。任何具体转换的质量取决于对应的提供商集成,因此请在你计划上线的确切模型上进行测试。LangChain 还在 langchain-openrouter 包中记录了 OpenRouter chat model integration,因此你可以通过 init_chat_model 选择任意 OpenRouter 模型,把各提供商的转换交给我们的 API。

对于 MCP,LangChain 记录了 MCPAdapter,它基于 FastMCP 构建,可发现服务器的工具并将其适配为 LangChain 工具。langchain.mcp 命名空间需要 langchain[mcp]>=1.4.0,且文档标注为 beta。

CrewAI

CrewAI 围绕角色和任务进行组织。你把工具分配给 agent,由 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 和 streamable HTTP 服务器的传输类。

OpenAI Agents SDK

OpenAI 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 则为单个 agent 设置模型。Any-LLM 和 LiteLLM 被记录为尽力而为的 beta 第三方适配器,用于内置路径未覆盖的情况。当你通过 Chat Completions 而非 Responses API 进行路由时,SDK 会丢弃仅 Responses 支持的字段,文档还警告说,一些提供商不支持 JSON Schema 结构化输出。你离 OpenAI 的模型越远,就越依赖兼容层而非第一方支持。

MCP 支持是 内置的,涵盖通过 OpenAI 的 Responses API 使用托管的 MCP 服务器工具,以及通过 stdio 和 HTTP 传输直接连接 MCP 服务器。

Claude Agent SDK

Claude Agent SDK 是 Claude Code 背后的 agent 框架,以 Python 和 TypeScript 库的形式打包。与 Anthropic Messages API 不同,它为你运行工具执行循环,内置工具、上下文管理、权限、钩子和子 agent。你可以用 Python 中的 @tool 装饰器或 TypeScript 中的 tool() 定义自定义工具,用 create_sdk_mcp_server 将它们包装进进程内 MCP 服务器,并将该服务器传递给查询。

它面向 Claude 模型,因此工具调用直接使用 Anthropic 原生的 tool_use 格式。没有跨提供商的转换层。将它指向非 Anthropic 模型并非它的设计用途。

Microsoft Agent Framework

Microsoft 将 Agent Framework 描述为 AutoGen 和 Semantic Kernel 的直接继任者,结合了 AutoGen 的 agent 抽象与 Semantic Kernel 的企业功能,并增加了显式工作流。其概述将 Microsoft Foundry、Anthropic、Azure OpenAI、OpenAI 和 Ollama 列为支持的模型提供商,工具调用和 MCP 服务器通过 agent 抽象处理。

工具作为类型化函数附加到 agent,框架将其转换为带 JSON Schema 的函数工具。工具调用转换属于你所配置的模型连接器,因此更换提供商会随之改变 schema 处理方式。Microsoft 发布了 从 AutoGen 迁移的指南,供从旧框架迁移的团队参考。

Google ADK

Google 的 Agent Development Kit 专为 Gemini 及其原生 function_declarations 格式构建。当你将 Python 函数传递给 agent 的工具列表时,ADK 会将其包装为 FunctionTool,并根据函数的签名和 docstring 生成 schema。

对于 Gemini 之外的模型,ADK 提供了连接器页面,包括 LiteLLM 连接器。在该路径上,对 Claude 或 GPT 模型的调用通过 LiteLLM 的转换运行,而非通过 ADK 原生的任何东西,因此这些模型上的 schema 行为属于该集成。请测试你计划使用的确切模型。

对于 MCP,ADK 提供了 McpToolset,它连接到 MCP 服务器并将其工具暴露给 agent。

框架对比

在选择框架之前,用我们的 模型目录按工具调用支持筛选。框架列出提供商适配器与模型支持原生工具调用是两回事,两者之间的差距正是意外之处。

框架如何定义工具谁跨提供商转换 schemaMCP 支持
LangChain 和 LangGraph带类型提示和 @tool 装饰器的 Python 函数LangChain 的各提供商聊天模型集成MCPAdapter 位于 langchain.mcp 命名空间中,文档标注为 beta 版
CrewAI由角色分配给 agent 的工具路由后的客户端,可以是原生 provider SDK 或 LiteLLMMCPServerAdapter 位于 crewai-tools 中
OpenAI Agents SDK@function_tool 带有生成的 JSON Schema没有第一方翻译。其他 provider 通过 OpenAI 兼容端点或 beta 版 Any-LLM 和 LiteLLM 适配器接入内置,支持托管和直连 MCP 服务器
Claude Agent SDK@tool 位于进程内 MCP 服务器中无。设计上仅支持单一 provider内置,MCP 客户端支持进程内服务器
Microsoft Agent Framework类型化函数转换为 function tools配置好的模型连接器内置,通过 agent 抽象层接入 MCP 服务器
Google ADKPython 函数包装为 FunctionTool没有原生跨 provider 路径。非 Gemini 模型通过 LiteLLM 等连接器接入McpToolset

把这张表当作起点,在正式采用前先查阅最新文档。这些框架发布频繁,而工具调用行为正是会变化的部分之一。

切换模型时哪些地方会出问题

这些故障有几种形态,而且上述框架都无法消除它们。

某个 provider 接受的 schema 会被更严格的 provider 拒绝。一个深层嵌套的参数对象、一种不常见的类型,或某个 provider 忽略而另一个 provider 强制执行的约束,都足以在新模型上产生请求错误。

不支持原生工具调用的模型会把调用当作纯文本返回。流水线中没有任何东西会把它识别为 tool_call,所以没有错误可捕获,只有一个不符合你循环预期的响应。除非框架或你自己的代码加入文本解析回退,否则 agent 会停止推进,而不是抛出错误。

修复行为各不相同。有些代码路径在调用返回格式错误时会抛出可捕获的错误。另一些则把检测和恢复留给你自己。如果你的 agent 循环假设了某种行为,而框架提供的是另一种,那么换模型可能会让错误悄无声息地溜过去。

还有一种更隐蔽的情况,无需切换模型家族就会出现。同一个模型由两个不同的 provider 提供服务时,返回有效工具调用的比率可能不同。自 2025 年 8 月起,我们对 OpenRouter 上的每一次工具调用响应进行了评分,在 Auto Exacto 公告中我们报告称,在我们开始将工具调用流量从较弱的端点转移出去之后,受影响 provider 上 GLM-5 和 GLM-4.7 的工具调用错误率从约 8% 降至接近 1%。

我们将每次失败的工具调用分为三类,同样的三项检查也适用于你自己的日志记录。InvalidJson 表示参数无法解析为 JSON。UnknownName 表示被调用的函数名不在请求的工具列表中。SchemaMismatch 表示参数未通过工具参数 schema 的校验。关于循环机制,包括重试、停止条件和逐轮控制,请参阅我们关于构建 工具调用 agent 循环的指南。

在 API 层规范化工具调用

翻译可以存在的第三个位置是 API 层,位于框架之下。我们在每个模型前面统一规范化一次工具调用,这样它上面的任何东西都看不到各 provider 之间的差异。

你发送一个带有 tools 数组的 OpenAI 风格请求。我们将其转换为目标 provider 的格式,运行它,并返回标准的 tool_calls 响应。对于每个支持工具调用的模型,你发送和接收的结构都是相同的,因此切换模型只是更改模型字符串。

下面的示例以 OpenAI 格式定义一次工具,并从 message.tool_calls 读取结果,无论请求由哪个模型运行。

import json
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)


def get_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?"}]

# Change this string to anthropic/claude-sonnet-4.6 or google/gemini-3.5-flash
# and nothing else in this file changes.
response = client.chat.completions.create(
    model="openai/gpt-5.5",
    messages=messages,
    tools=tools,
)

choice = response.choices[0]
messages.append(choice.message)

# The same tool_calls shape comes back from every tool-capable model.
for call in choice.message.tool_calls or []:
    args = json.loads(call.function.arguments)  # arguments is a JSON string
    result = get_weather(**args)
    messages.append({
        "role": "tool",
        "tool_call_id": call.id,
        "content": json.dumps(result),
    })

有两点在所有模型之间都保持一致。工具定义和你解析的响应保持不变。tool_calls 始终是一个数组,每个 arguments 值都是一个 JSON 字符串,因此要遍历该数组并解析每个条目,而不是假定只有一次调用。

这并不是对 agent 框架的替代。你仍然需要某种东西来运行循环、保存状态并决定何时停止。它消除的是你应用程序中针对每个模型的工具调用适配器代码。schema 转换从你维护的层转移到了你调用的层。

Diagram of where tool-calling schema translation runs. The application sends one OpenAI-style tool definition through the agent framework to OpenRouter, which translates it into the OpenAI, Anthropic, or Gemini wire format, selects a provider, and returns a normalized tool_calls response.

工具调用请求的提供商路由

规范化格式是可靠性问题的一半。另一半是哪个提供商来运行调用。由于我们对每个工具调用响应进行评分,我们可以将工具调用请求路由到最常返回有效工具调用的端点。

Auto Exacto 默认对每个包含工具的请求运行,无需配置。它使用三个信号为你所选模型重新排列可用提供商的顺序。吞吐量是每个端点的实时每秒 token 数测量值。工具调用成功率源自 Tool Call Error Rate 指标。基准数据来自我们自己的基准测试框架,该框架按周期性计划对已注册的提供商端点运行 GPQA Diamond 和 Tau2-Bench Airline。

Tool Call Error Rate 指标位于每个模型页面的 Performance 标签页上。对于每个包含工具的请求,我们会检查模型返回的每个工具调用,并使用 JSON Schema Draft 7 根据你提供的 tools[].function.parameters schema 验证其 arguments。parameters schema 缺失或无法编译的工具计为有效,因此当调用方的 schema 格式错误时,该指标保持保守。该指标按请求聚合,而不是按工具调用聚合,因此一个包含多个错误调用的请求只计一次。

Auto Exacto 可能会与保持提示缓存热度的粘性提供商路由发生冲突,因为它可能会在会话中途将请求移动到不同的端点。如果对于你的 agent 循环来说,缓存命中率比提供商重新排序更重要,文档中描述了如何通过在 provider 对象或 :floor 模型变体中使用 sort: "price" 来禁用 Auto Exacto。

在你依赖任一机制之前,有两项检查适用。工具调用并非普遍适用,因此请通过上方的目录筛选器或模型页面确认你路由到的每个模型都支持它。如果你在多个模型上运行同一个 agent 以比较质量,请同时比较成本。当前的各模型费率在定价页面上。

结论

工具调用并非只有一种格式。有些框架是翻译器,有些原生属于单一提供商,还有些将问题委托给其下方的连接器或客户端。如果你需要在某一个提供商上获得最深入的支持,那么像 OpenAI Agents SDK 或 Claude Agent SDK 这样的原生 SDK 是最合适的选择。如果你需要跨提供商迁移,LangChain 和 LangGraph 在框架层承担了最多的转换工作,或者你也可以通过在 API 层规范化工具调用,将转换从框架中移除。在构建之前先决定 schema 转换放在哪里,这样更换模型就只需改一行。

常见问题

主要的 agent 框架在处理工具调用 schema 方面有何不同?

它们的区别在于 schema 转换在何处运行。LangChain 和 LangGraph 通过各提供商各自的聊天模型集成,将单个工具定义转换为每个提供商的格式。CrewAI 将转换委托给它所路由到的提供商 SDK 或 LiteLLM 客户端。OpenAI Agents SDK、Claude Agent SDK 和 Google ADK 各自围绕单一提供商的格式构建,并通过兼容端点、适配器或连接器接入其他提供商。Microsoft Agent Framework 将转换交给所配置的模型连接器。在 API 层规范化工具调用,则将转换下移到它们所有之下。

工具调用应该选用哪个 agent SDK?

让 SDK 匹配你所需的模型集合。如果你打算继续使用某个提供商的模型,那么像 OpenAI Agents SDK 或 Claude Agent SDK 这样的提供商原生 SDK 最为合适。LangChain 和 LangGraph 在不同提供商之间迁移时承担的转换最多。CrewAI 将转换委托给它所路由到的客户端。通过 OpenRouter 路由,可使工具定义和响应结构在不同模型间保持一致,因此 SDK 的选择不再限制模型的选择。

哪些 agent 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:Announcements(RSS) · openrouter.ai