Hermes 与 ReAct 模式对比分析
结论先行
Hermes有Thought → Action → Observation 的经典过程,但它不是用文本标记Thought:/Action:/Observation:显式表达的 ReAct,而是用API 原生的结构化消息隐式完成了相同的三段循环。
一、经典 ReAct 的三段式
Thought: 我需要查找北京的天气 ← 推理(文本) Action: search("北京天气") ← 行动(文本,需解析) Observation: 北京今天晴,32°C ← 观察(文本,拼回上下文) Thought: 找到了,可以回答用户 Answer: 北京今天晴天,32°C关键特征:
- Thought / Action / Observation都是纯文本,在同一条输出流中
- Action 需要正则/JSON 解析器从文本中提取
- Observation 由外部注入到下一段文本
- 三者之间没有结构化关联,靠格式约定维系
二、Hermes 的三段式:结构化隐式 ReAct
Hermes 没有显式的Thought:/Action:/Observation:标记,但在每一轮循环中,三段过程完整存在,只是换了载体:
2.1 一轮循环的消息流
messages 列表的真实结构: ┌─────────────────────────────────────────────────────┐ │ {"role": "system", "content": "You are Hermes..."} │ ← 系统提示 │ {"role": "user", "content": "北京今天天气怎样?"} │ ← 用户输入 │ │ │ ── 第 1 轮循环 ────────────────────────────────── │ │ {"role": "assistant", │ │ "content": "让我查一下天气", ← Thought │ │ "reasoning": "用户问天气,需要调用web_search", │ │ "tool_calls": [{ │ │ "id": "call_abc", │ │ "function": { │ │ "name": "web_search", ← Action │ │ "arguments": '{"query":"北京天气"}' │ │ } │ │ }]} │ │ {"role": "tool", │ │ "tool_call_id": "call_abc", │ │ "content": "北京今天晴,32°C"} ← Observation │ │ │ │ ── 第 2 轮循环 ────────────────────────────────── │ │ {"role": "assistant", │ │ "content": "北京今天晴天,32°C。"} ← 最终 Answer│ │ "tool_calls": null} │ └─────────────────────────────────────────────────────┘2.2 Thought → Action → Observation 的对应关系
| ReAct 阶段 | ReAct 表现形式 | Hermes 表现形式 | 代码位置 |
|---|---|---|---|
| Thought | 文本Thought: ... | assistant.content(正文推理)+assistant.reasoning/reasoning_content/<REASONING_SCRATCHPAD>(结构化推理) | _extract_reasoning()方法提取,支持 6 种格式 |
| Action | 文本Action: search("北京天气") | assistant.tool_calls[].function结构化对象(name + JSON args) | _execute_tool_calls()→_invoke_tool() |
| Observation | 文本Observation: 北京今天晴 | {"role": "tool", "tool_call_id": "xxx", "content": "..."}结构化消息 | 工具执行结果 append 到 messages |
2.3 Thought 的 6 种格式
Hermes 通过_extract_reasoning()方法统一提取推理内容,支持以下格式:
| # | 格式 | 来源 | 示例 |
|---|---|---|---|
| 1 | message.reasoning | DeepSeek、Qwen 等模型直接返回 | assistant_message.reasoning |
| 2 | message.reasoning_content | Moonshot AI、Novita 等替代字段 | assistant_message.reasoning_content |
| 3 | message.reasoning_details | OpenRouter 统一格式,含 summary | [{type: "reasoning.summary", summary: "..."}] |
| 4 | <thinking>...</thinking> | 内联标签 | 模型在 content 中嵌入 |
| 5 | <REASONING_SCRATCHPAD>...</REASONING_SCRATCHPAD> | 旧式推理标签 | convert_scratchpad_to_think()转换 |
| 6 | thinking...thinking | Anthropic/Claude 风格 | 正则提取 |
代码原文:
def_extract_reasoning(self,assistant_message)->Optional[str]:# 1. message.reasoning — Direct reasoning field (DeepSeek, Qwen, etc.)# 2. message.reasoning_content — Alternative field (Moonshot AI, Novita, etc.)# 3. message.reasoning_details — Array of {type, summary, ...} objects# 4~6. Fallback: inline tags in content# ཱུ...thinking># <thinking>...</thinking># <reasoning>...</reasoning># <REASONING_SCRATCHPAD>...</REASONING_SCRATCHPAD>三、循环过程对比
3.1 经典 ReAct 循环
while not done: output = LLM(prompt + history) thought, action = parse(output) # 正则解析文本 if action is None: return thought # 纯文本就是最终答案 observation = execute(action) # 执行动作 history += [output, f"Observation: {observation}"]3.2 Hermes 循环
while api_call_count < max_iterations: response = API(messages) # 结构化 API 调用 assistant_msg = response.choices[0].message # Thought: 推理内容(结构化提取) reasoning = _extract_reasoning(assistant_msg) # Action: 工具调用(结构化对象,无需解析) if assistant_msg.tool_calls: # Observation: 工具结果(结构化消息) for tool_call in assistant_msg.tool_calls: result = _invoke_tool(tool_call.function.name, tool_call.function.arguments) messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result}) continue # 继续循环 else: # 无 Action → 最终答案 return assistant_msg.content3.3 逐步对照
| 步骤 | ReAct | Hermes |
|---|---|---|
| 1. 推理 | LLM 输出Thought: ...文本 | API 返回reasoning+content字段 |
| 2. 决策 | 正则匹配Action: xxx(yyy) | API 返回tool_calls数组(或 null) |
| 3. 执行 | eval(action_string)或自定义解析器 | _invoke_tool(name, args),JSON schema 校验 |
| 4. 观察 | 拼接Observation: result文本 | 追加{"role": "tool", "content": result} |
| 5. 终止 | 输出不含 Action → 结束 | tool_calls为 null → 结束 |
四、Hermes 比 ReAct 多了什么
| 能力 | ReAct | Hermes |
|---|---|---|
| 并行 Action | 不支持,一次一个 | 同一轮多个 tool_calls 可并行 ThreadPoolExecutor |
| 子任务委派 | 不支持 | delegate_task创建子 Agent,独立 budget |
| Action 校验 | 无,靠模型自觉 | 工具名白名单 + JSON schema 校验 + 自动修复 |
| Action 去重 | 无 | _deduplicate_tool_calls() |
| 格式容错 | 无,格式错误直接失败 | 无效工具名重试 3 次 + 无效 JSON 重试 3 次 |
| 上下文管理 | 无,历史无限增长 | 3 层压缩 + 压力预警 + Grace Call |
| Fallback | 无 | 多 Provider 自动切换 |
| Checkpoint | 无 | 文件变更和破坏性命令前自动快照 |
| 记忆 | 无 | 内置 memory + session_search + 外部 Provider |
五、总结构图
ReAct 经典三段式 ──────────────── Thought ─────→ "Thought: 我需要查天气" (文本) │ Action ──────→ "Action: search(北京天气)" (文本,需解析) │ Observation ─→ "Observation: 晴,32°C" (文本,拼回上下文) Hermes 结构化三段式 ────────────────── Thought ─────→ assistant.reasoning (结构化字段) assistant.reasoning_content (6 种格式统一提取) assistant.content (正文推理) │ Action ──────→ assistant.tool_calls[0].function (结构化对象) { (name + JSON args) "name": "web_search", "arguments": '{"query":"北京天气"}' } │ Observation ─→ {"role": "tool", (结构化消息) "tool_call_id": "call_abc", (带 ID 关联) "content": "晴,32°C"}一句话总结:Hermes 有完整的 Thought → Action → Observation 循环,但把 ReAct 的"文本约定协议"升级成了"API 结构化协议"——逻辑同构,工程实现更可靠。