Day 3 补充 · Claude Messages API vs Hermes ChatML 接口格式逐层对比
创建时间:2026-08-20(Day 6 学习时补充)
关联:Day 3 对比表已覆盖功能维度,本文档专攻接口格式——请求/响应的 JSON 结构逐层对比
一、术语定义
| 术语 | 性质 | 说明 |
|---|---|---|
| Claude Messages API | 结构化 JSON API 协议 | 工具定义、调用、结果都是 API 的独立字段,类型安全 |
| Hermes ChatML | 文本标记型对话格式 | 工具定义、调用、结果都嵌入 prompt 文本中,靠标签区分 |
两者是同一层级(大模型对话交互协议)的两种不同实现范式。
二、顶层结构:分字段 vs 全塞一个字符串
这是两者最本质的区别。
Claude Messages API —— 3 个顶层参数,各司其职
POST /v1/messages
{
"model": "claude-sonnet-5",
"system": "You are a helpful assistant.", ← 独立字段:系统指令
"messages": [ ← 独立字段:对话历史
{"role": "user", "content": "..."}
],
"tools": [ ← 独立字段:可用工具
{"name": "get_weather", ...}
]
}
三个参数各司其职,互不干扰。API 后端收到后,内部拼接成模型能理解的格式,但 Harness 开发者不需要关心拼接细节。
Hermes ChatML —— 全塞进 messages,一个字符串串到底
POST /v1/chat/completions
{
"model": "hermes3:8b",
"messages": [
{"role": "system", "content": "
You are a helpful assistant.
<tools>{...}</tools> ← 工具定义也塞这里
"},
{"role": "user", "content": "..."},
{"role": "assistant", "content": "
<tool_call>...</tool_call> ← 工具调用也塞这里
"}
]
}
所有东西都在 messages 数组里,最终拼成一个巨大的 prompt 字符串:
<|im_start|>system
You are a helpful assistant.
<tools>{...}</tools>
<|im_end|>
<|im_start|>user
What's the weather in Paris?
<|im_end|>
<|im_start|>assistant
<tool_call>...</tool_call>
<|im_end|>
一图对比
┌──────────────────────────────────────────┐
│ Claude(结构化分离) │
│ │
│ system ──→ 独立字段 │
│ tools ──→ 独立字段 │
│ messages ──→ 独立字段 │
│ │
│ API 内部拼接,Harness 只操作 JSON 对象 │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ Hermes(文本拼接) │
│ │
│ messages ──→ 一个数组 │
│ ├── system(含 <tools>) │
│ ├── user │
│ ├── assistant(含 <tool_call>) │
│ └── tool(含 <tool_result>) │
│ │
│ 最终拼成一个大字符串,Harness 负责拼接 │
└──────────────────────────────────────────┘
这对 Harness 意味着什么?
| Claude | Hermes | |
|---|---|---|
| 传工具定义 | tools: [...] 直接传 |
自己拼 <tools>{...}</tools> 塞进 system content |
| 传对话历史 | messages: [...] 追加即可 |
自己维护 ChatML 全文本拼接顺序 |
| 解析工具调用 | content[].type === "tool_use" |
正则匹配 <tool_call> 标签 |
| 回传工具结果 | tool_result block 带 ID 匹配 |
拼 <tool_result> 按顺序追加 |
| 判断是否结束 | stop_reason 字段 |
正则判断有没有 <tool_call> |
Claude 的 Harness 是"操作 JSON 对象",Hermes 的 Harness 是"拼字符串 + 解析字符串"。 这就是为什么 Day 3 结论说"Claude 集成更快,Hermes 开发成本更高"——多的不是理解成本,是字符串处理代码。
三、工具定义:怎么告诉模型"你有什么能力"?
Claude Messages API —— tools 是 API 顶层参数
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"system": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "What's the weather in Paris?"}
],
"tools": [
{
"name": "get_weather",
"description": "Get the current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
]
}
关键特征:
tools是 API 的独立顶层字段(JSON 数组)- 工具定义和对话消息完全分离——模型不会混淆
- 带
input_schema(JSON Schema),支持strict: true保证参数格式
Hermes ChatML —— tools 嵌在 system prompt 里
<|im_start|>system
You are a helpful assistant.
<tools>
{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string", "description": "City name"}}, "required": ["city"]}}}
</tools>
<|im_end|>
<|im_start|>user
What's the weather in Paris?
<|im_end|>
<|im_start|>assistant
关键特征:
- 工具定义就是一个大字符串的一部分,用
<tools>...</tools>标签包裹 - 格式是 OpenAI 兼容的 function 定义(
{"type": "function", "function": {...}}) - 模型靠"读"这段文本来理解工具——而不是 API 层保证
四、工具调用:模型怎么告诉 Harness"我要调工具"?
Claude —— 结构化 content block
{
"id": "msg_xxx",
"model": "claude-sonnet-5",
"stop_reason": "tool_use",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09B3C...",
"name": "get_weather",
"input": {
"city": "Paris"
}
}
]
}
关键特征:
stop_reason: "tool_use"—— API 明确告诉你"这轮还没完,去执行工具"- 每个 tool_use 有唯一
id,用于回传结果时匹配 input是已解析好的 JSON 对象,不需要手动解析
Hermes —— 文本标签
<tool_call>
{"name": "get_weather", "arguments": {"city": "Paris"}}
</tool_call>
关键特征:
- 就是一段纯文本,Harness 需要用正则提取
<tool_call>标签内容 - 没有唯一 ID,Harness 需要自己管理"哪个结果对应哪个调用"
- JSON 可能格式错误(模型输出不稳定),需要 try-catch
五、工具结果回传:怎么把执行结果还给模型?
Claude —— 结构化 tool_result block
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09B3C...",
"content": "Paris: 22°C, partly cloudy"
}
]
}
关键特征:
tool_use_id精确匹配之前的调用,不会搞混- 结果作为一条
role: "user"的消息发给模型 - 支持一次性回传多个 tool_result(并行工具调用场景)
Hermes —— 文本标签
<|im_start|>tool
<tool_result>
{"temperature": 22, "condition": "partly cloudy"}
</tool_result>
<|im_end|>
关键特征:
- 使用 ChatML 的
tool角色(四角色体系中的第四个) - 同样靠标签解析,同样有格式错误风险
- Harness 必须自己按顺序拼接对话历史
六、并行工具调用
Claude
{
"stop_reason": "tool_use",
"content": [
{"type": "tool_use", "id": "toolu_001", "name": "get_weather", "input": {"city": "Paris"}},
{"type": "tool_use", "id": "toolu_002", "name": "get_weather", "input": {"city": "London"}}
]
}
一次返回多个 tool_use block,Harness 并行执行后一次性回传两个 tool_result。
Hermes
<tool_call>
{"name": "get_weather", "arguments": {"city": "Paris"}}
</tool_call>
<tool_call>
{"name": "get_weather", "arguments": {"city": "London"}}
</tool_call>
理论上可以输出多个 <tool_call>,但没有 stop_reason 信号,Harness 需要自己判断"模型是否还在输出"还是"这轮全部输出完了"。
七、核心差异总结
┌──────────────────────────────────────────────────────────────────┐
│ Claude Messages API │
│ │
│ tools[] ──→ API 顶层字段(独立) │
│ tool_use ──→ content block(type + id + input) │
│ tool_result ──→ content block(type + tool_use_id + content) │
│ stop_reason ──→ 明确信号(end_turn / tool_use / max_tokens) │
│ │
│ Harness 只需操作 JSON 对象,不需要解析文本 │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Hermes ChatML │
│ │
│ tools ──→ <tools> 标签嵌在 system prompt 里 │
│ tool_call ──→ <tool_call> 标签嵌在 assistant 回复里 │
│ tool_result ──→ <tool_result> 标签嵌在 tool 角色消息里 │
│ 停止判断 ──→ 无内置机制,靠文本解析 │
│ │
│ Harness 必须:正则提取 + JSON.parse + 格式错误处理 │
└──────────────────────────────────────────────────────────────────┘
| 维度 | Claude Messages API | Hermes ChatML |
|---|---|---|
| 工具定义位置 | API 顶层 tools 字段 |
system prompt 内的 <tools> 文本 |
| 工具调用返回 | 结构化 content[].type="tool_use" |
文本 <tool_call> 标签,需正则解析 |
| 工具调用 ID | 有唯一 id(如 toolu_01xxx) |
无 ID,Harness 自行管理 |
| 工具结果回传 | 结构化 tool_result block + tool_use_id 匹配 |
文本 <tool_result> 标签,按顺序拼接 |
| 停止判断 | stop_reason 字段(API 保证正确) |
无内置机制,靠解析文本判断 |
| 并行调用 | 原生支持,一次返回多个 tool_use | 标签可并列,但无明确信号 |
| 参数校验 | strict: true 保证 100% 符合 schema |
无,模型可能输出格式错误 |
| Harness 解析难度 | 低——JSON 直接取字段 | 中——正则 + JSON.parse + try-catch |
| 解析出错风险 | 极低(API 层保证) | 存在(模型输出不稳定) |
八、对 Harness 设计的影响
如果 Harness 对接 Claude API:
tools 字段直接传给 API → response.content 直接取 block
→ Harness 代码干净,不需要文本解析层
如果 Harness 对接 Hermes(通过 Ollama):
Harness 必须自己:
1. 把 tools 拼成 <tools> 标签塞进 system prompt
2. 解析 <tool_call> 提取 name + arguments
3. 处理格式错误(正则不匹配、JSON 解析失败)
4. 按顺序拼接 <tool_result> 回传
5. 管理对话历史(ChatML 全文本拼接)
结论:Claude 的 Tool Use 是 API 原生支持的"一等公民",Hermes 的 FC 是 prompt 层面的"约定"。Harness 对接 Hermes 需要额外写一个解析层。
📎 关联文件:Day03_对比ToolUse机制.md — 功能维度对比表
📎 关联记忆:[[hermes-day3-claude-tool-use]] — Tool Use 四层讲解+速查卡