协议层是 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 代码只用 AssistantMessageToolCall,不关心底下是哪家。这是 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,看看开源生态的完整通路是怎么走的。

相关阅读

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:09. 工具调用协议的分裂:OpenAI、Anthropic、Hermes

本文链接:https://www.sshipanoo.com/blog/ai/ai-agent/09-工具调用协议/

本文最后一次更新为 天前,文章中的某些内容可能已过时!