协议层是 Agent 工程最容易被忽视的地基
"工具调用"这件事,从外面看起来是统一的——模型声明它想调某个函数,你执行,把结果返回。但打开三家大厂的 API,底下的协议格式完全不一样。而开源模型世界(Llama、Qwen、Mistral 等)选了第四种——Nous Research 的 Hermes 协议,作为事实标准。
理解这些协议的差别有两个实际价值:第一,你在切换模型(从 GPT 到 Claude、从闭源到开源)时,不会被底层格式差异坑;第二,你能更清楚 Agent 框架在"做什么"——很多框架的价值就是把这些协议抽象掉。
OpenAI tools:JSON 派的代表
OpenAI 2023 年推出的 Function Calling(后改名 tools)是 JSON 原教旨主义:
# 请求
{
"model": "gpt-4o",
"messages": [...],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取城市天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}
# 响应
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}]
}
}]
}
# 你要把工具结果塞回去
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "15 度,晴"
}
特点:
- 完全结构化。工具调用是 JSON 字段,不是文本。解析完全靠类型系统,不用正则。
- 带 id。每个 tool_call 有唯一 id,tool 消息用 tool_call_id 配对。支持多个并行 tool_calls。
- content 可以 null。模型决定调工具时,content 字段是空的,所有"输出"都在 tool_calls 里。
这是最"工程化"的协议。JSON 派的哲学是——既然最终要机器解析,就让它从一开始就是机器格式,不要经过文本的中间形态。
Anthropic tool_use:XML 包 JSON
Anthropic 的 Claude 用的是混合形式:
<!-- 请求里 tools 定义和 OpenAI 类似,也是 JSON Schema -->
{
"model": "claude-opus-4",
"tools": [{
"name": "get_weather",
"description": "获取城市天气",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"messages": [...]
}
<!-- 响应里 content 是数组,可以混合 text 和 tool_use -->
{
"content": [
{"type": "text", "text": "我查一下天气。"},
{
"type": "tool_use",
"id": "toolu_01",
"name": "get_weather",
"input": {"city": "北京"}
}
]
}
<!-- 工具结果回传 -->
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 度,晴"}
]
}
注意几个和 OpenAI 不同的地方:
content 是列表。一条 assistant 消息里可以同时有文字和工具调用。模型可以说"让我查一下..."然后发起 tool_use。OpenAI 的 content 和 tool_calls 互斥(非 null 即 tool)。这让 Anthropic 的 Agent 在行为上更"自然"。
工具结果用 user role。OpenAI 专门有个 role: "tool",Anthropic 没有,工具结果作为 user 消息里的 tool_result content block 返回。
input 直接是 object,不是字符串。OpenAI 的 arguments 是 JSON 字符串(要再 parse 一遍),Anthropic 的 input 已经是解析后的对象。这是个小细节但写代码时能省一步。
Claude 内部其实把工具调用编码成 XML 字符串训练,然后在 API 层转成 JSON 给用户。所以你如果看 Claude 的原始输出,能看到 <tool_use> 这种标签的痕迹。这个信息在后面讲 Hermes 时有用。
Hermes:开源世界的默认
Nous Research 的 Hermes Function Calling 是开源模型事实上的标准协议。它用的是纯 XML 标签 + JSON 内容:
<!-- System prompt 里声明工具 -->
You are a function calling AI model. You are provided with function signatures within <tools></tools> XML tags.
<tools>
{"type":"function","function":{"name":"get_weather","description":"获取城市天气","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}
</tools>
Use the following schema for function calls:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call>
<!-- 模型输出 -->
让我查一下天气。
<tool_call>
{"name": "get_weather", "arguments": {"city": "北京"}}
</tool_call>
<!-- 工具结果回传 -->
<tool_response>
{"result": "15 度,晴"}
</tool_response>
Hermes 的关键设计:
完全纯文本。模型输出的是一个字符串,里面嵌入 <tool_call> 标签。不依赖 API 层的特殊字段。这让它可以跑在任何聊天 API 上——只要模型学会了这个格式,任何只支持 {"role": "...", "content": "..."} 的端点都能跑工具调用。
Stop token 友好。Hermes 3 训练时用了专门的特殊 token(如 <|tool_call_end|>),generation 时能精确停在 tool_call 结束。
易于微调。Hermes 的数据集是公开的,任何人可以基于它微调自己的开源模型,让模型获得工具调用能力。这就是为什么 2024~2026 年几乎所有的"Tool-Use 版本"开源模型(Hermes 2 / 3 / 4、Qwen-Agent、Llama-3-Groq-Tool-Use)都采用这个格式。
这也是为什么 Ollama 里的 Hermes 3 可以直接用 OpenAI SDK 调用——Ollama 在运行时把 Hermes 的 XML 输出转译成 OpenAI tools 格式,对外看起来就和 OpenAI 一样。下一篇会详细讲这套。
为什么开源选 Hermes
OpenAI 的 tools 协议很好,但它有个致命问题——它需要 API 服务端配合。模型输出的工具调用不是文本里的 XML,而是 API 层专门的字段。这意味着部署一个"支持 tools"的开源模型,不只是训练模型会输出工具调用,还要改推理服务器(vLLM、TGI、Ollama 等)让它把模型输出解析成 API 字段。
Hermes 走的是另一条路:所有状态都在文本里,API 层不需要做任何事。你用 text-generation 端点,把 prompt 塞进去,模型会在输出里自然带着 <tool_call>,你的应用层自己解析。这对开源生态是决定性的——任何一个推理引擎都能跑 Hermes 模型,零改动。
Qwen-Agent 用的是类似思想,Groq 的 Llama 3 Tool Use 也是一个变体。虽然标签名不完全一样(Qwen 用 <function>,Groq 用 <function_call>),但结构都是"XML 标签 + JSON"。本质上是 Hermes 家族。
格式差异对应用层意味着什么
你写一个 Agent 时,这些协议差异会以什么形式影响你?
如果用闭源 API(OpenAI、Anthropic、Gemini)——几乎不用管。各家 SDK 把协议细节封装好,你写 client.chat.completions.create(tools=...) 或 client.messages.create(tools=...) 就行。偶尔的差异(content 是 string 还是 list)SDK 会帮你处理。
如果用本地/开源模型经过兼容层(Ollama、vLLM、LM Studio)——也不用管。这些运行时把 Hermes 输出转成 OpenAI 格式,你还是用 OpenAI SDK 对着 http://localhost:11434/v1 发请求。
如果你自己部署原始开源模型(直接用 transformers、llama.cpp)——要管。你需要自己写 Hermes prompt template、解析 <tool_call> 标签、把结果用 <tool_response> 包装回去。不难,但需要知道格式。
如果你在写一个跨多家模型的 Agent 框架——最要管。LangChain、Pydantic AI、Claude Agent SDK 内部都有一层"协议抽象",用同一套 API 兼容三家。第 18 篇对比框架时这个会是核心话题。
一个简易的统一抽象
生产 Agent 里经常手写一个薄抽象,把三家兼容起来:
from typing import Protocol
from dataclasses import dataclass
@dataclass
class ToolCall:
id: str
name: str
args: dict
@dataclass
class AssistantMessage:
text: str | None
tool_calls: list[ToolCall]
class ModelProvider(Protocol):
def chat(self, messages, tools) -> AssistantMessage: ...
class OpenAIProvider:
def chat(self, messages, tools):
resp = openai_client.chat.completions.create(...)
msg = resp.choices[0].message
tcs = [ToolCall(tc.id, tc.function.name, json.loads(tc.function.arguments))
for tc in (msg.tool_calls or [])]
return AssistantMessage(msg.content, tcs)
class AnthropicProvider:
def chat(self, messages, tools):
resp = anthropic_client.messages.create(...)
text_parts = [b.text for b in resp.content if b.type == "text"]
tcs = [ToolCall(b.id, b.name, b.input)
for b in resp.content if b.type == "tool_use"]
return AssistantMessage("\n".join(text_parts), tcs)
几十行就做到了 provider 无关。上层 Agent 代码只用 AssistantMessage 和 ToolCall,不关心底下是哪家。这是 LangChain 做的事情之一,只是它加了一百个其他抽象层,所以看起来更复杂。
协议分裂会统一吗
短期看不会。OpenAI、Anthropic 都有历史包袱,很难迁。开源 Hermes 因为"纯文本"特性不可替代。但工具调用的语义正在趋同——基本都是"函数名 + JSON Schema + id 配对"。所以抽象层能做得很薄,不是大问题。
长期可能出现的变化是 MCP (Model Context Protocol) 这种跨模型的工具协议抢一些份额。MCP 不解决"模型怎么输出工具调用",而解决"工具怎么以标准方式被不同 Agent 复用"——它和本篇讨论的是两件不同的事,后面专门讲 MCP 会回到这个点。
小结
到这里你应该能理解几件事:OpenAI tools 是结构化 JSON 协议,靠 API 字段;Anthropic tool_use 是 XML 思路下的 JSON 包装,一条消息能混合文本和工具;Hermes 是纯 XML-in-text 协议,最灵活、最适合开源。选哪个取决于你用哪家模型,但三者之间都能通过薄抽象层兼容。
下一篇从理论落到实操——用 Ollama 跑 Hermes 3,本地搭一个真正能用工具的 Agent,看看开源生态的完整通路是怎么走的。
相关阅读
- OpenAI Function Calling Docs
- Anthropic Tool Use Docs
- NousResearch Hermes Function Calling GitHub
- Qwen-Agent Function Calling Format
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:09. 工具调用协议的分裂:OpenAI、Anthropic、Hermes
本文链接:https://www.sshipanoo.com/blog/ai/ai-agent/09-工具调用协议/
本文最后一次更新为 天前,文章中的某些内容可能已过时!