🔧 Hermes 工具调用机制解析

为什么 [TOOL_CALL] 标签没有被解析,反而直接显示给了用户?

一、完整调用流程

用户发送消息 → Hermes 接收
用户在飞书群里 @Hermes,发起一个需要调用工具的请求(例如"帮我查看进程")
Hermes 调用 MiniMax API
Hermes 将用户消息 + 系统提示词 + 历史记录 发送给 MiniMax(通过 minimax-cn provider)
MiniMax 返回工具调用指令
MiniMax 模型决定调用工具,输出如下格式:
MiniMax 返回的原始内容(text)
[TOOL_CALL] {tool => "terminal", args => { --command "tasklist | findstr python" --timeout 5 }} [/TOOL_CALL]
conversation_loop.py 处理响应
收到 API 响应后,在 post_api_request hook 里检查是否需要解析工具调用格式
调用 bracket_parser 解析器
检测到内容中有 [TOOL_CALL],调用 get_parser("bracket") → BracketToolCallParser.parse()
解析器检查 START_TOKEN
解析器内部检查 if self.START_TOKEN not in text —— 这里是 bug 所在
解析失败 / 返回原始内容
因为 START_TOKEN = "***"(三颗星),而实际内容是 [TOOL_CALL](方括号),检查失败 → 直接返回原始文本,不提取工具调用
原始内容被发送给用户
未经处理的 [TOOL_CALL]... 标签直接显示在飞书消息里,用户看到的就是原始代码块

二、核心Bug详解

Bug 位置

文件:D:\Hermes Agent\hermes-agent-main\environments\tool_call_parsers\bracket_parser.py

解析器代码(有bug)

bracket_parser.py 第 88-100 行
class BracketToolCallParser(ToolCallParser): START_TOKEN = "***" ← 这里是BUG!配置成了***而不是[TOOL_CALL] END_TOKEN = "***" 同上 def parse(self, text: str) -> ParseResult: if self.START_TOKEN not in text: ← 检查的是"***",实际内容是"[TOOL_CALL]" → 检查失败! return text, None ← 直接返回原始内容,不解析 tool_calls: List[ChatCompletionMessageToolCall] = [] pattern = re.compile(r'\[TOOL_CALL\](.*?)\[/TOOL_CALL\]', re.DOTALL) ← 正则本身是对的 matches = pattern.findall(text) ...

时序图

MiniMax输出 → conversation_loop收到 → 检查[TOOL_CALL]存在 → get_parser("bracket") → BracketToolCallParser.parse() → ❌ if "***" not in text → TRUE(没找到***) → return text, None(原始内容原封不动返回)

为什么正则本身没问题,但解析还是失败了?

步骤代码逻辑实际值结果
1. 正则匹配pattern.findall(text)能找到 [TOOL_CALL]...[/TOOL_CALL]✅ 能匹配到
2. START_TOKEN检查if self.START_TOKEN not in textSTART_TOKEN = "***",文本里没有 ***❌ 条件为True,直接return,不执行到正则

conversation_loop.py 里的调用逻辑

conversation_loop.py 第 2984-3014 行
# Parse [TOOL_CALL] format from assistant message content = assistant_message.content or "" if not assistant_message.tool_calls and content: try: from environments.tool_call_parsers import get_parser # Try hermes parser first (XML format: <tool_call>) if "<tool_call>" in content: fallback_parser = get_parser("hermes") ...
(续)
# Try bracket parser (bracket format: [TOOL_CALL]) elif "[TOOL_CALL]" in content: ← conversation_loop 检查的是"[TOOL_CALL]"字符串 ✓ fallback_parser = get_parser("bracket") ← 调用了 bracket 解析器 parsed_content, parsed_calls = fallback_parser.parse(content) if parsed_calls: assistant_message.tool_calls = parsed_calls ← 但 parsed_calls 是 None!解析失败了 if parsed_content is not None: assistant_message.content = parsed_content

三、问题根因总结

应该检查的内容

START_TOKEN = "[TOOL_CALL]"

实际配置的内容

START_TOKEN = "***"

🐛 Bug 描述

BracketToolCallParser 的 START_TOKEN 和 END_TOKEN 配置成了 "***"(三颗星),而不是实际的 "[TOOL_CALL]" 和 "[/TOOL_CALL]"。

解析器的 parse() 方法第一行就检查 if self.START_TOKEN not in text,由于文本里根本没有 ***,这个条件为 True,函数直接 return text, None,根本不执行后面的正则解析逻辑。

结果:原始文本(含 [TOOL_CALL] 标签)原封不动返回,被当成普通消息发给了用户。

四、修复方案

✅ 修复方法

修改 bracket_parser.py 第 88-89 行:

修复前(bug)
class BracketToolCallParser(ToolCallParser): START_TOKEN = "***" END_TOKEN = "***"
修复后
class BracketToolCallParser(ToolCallParser): START_TOKEN = "[TOOL_CALL]" END_TOKEN = "[/TOOL_CALL]"

修复后,parse() 方法里的 if self.START_TOKEN not in text 检查就会正确命中 [TOOL_CALL],继续执行正则解析,提取出工具名称和参数,正常执行工具调用。

五、相关文件索引

文件作用
D:\Hermes Agent\hermes-agent-main\agent\conversation_loop.py:2984-3014检测 [TOOL_CALL] 并调用 bracket_parser
D:\Hermes Agent\hermes-agent-main\environments\tool_call_parsers\bracket_parser.pybracket 格式解析器(有 bug)
D:\Hermes Agent\hermes-agent-main\environments\tool_call_parsers\__init__.py解析器注册表(bracket 已注册)
D:\Hermes Agent\hermes-agent-main\.hermes\config.yamlHermes 配置(provider: minimax-cn)

六、另外两个次要问题

1. 跨平台命令不兼容(Linux 命令在 Windows 上不存在)

模型生成的命令用了 Linux 语法(如 grep、/dev/null),但 Hermes 跑在 Windows 上,这些命令不存在。

问题示例
tasklist | grep -i python ← grep 是 Linux 命令,Windows 应用 findstr 2>/dev/null ← /dev/null 是 Linux,Windows 应用 2>NUL

2. 系统提示词问题(推测)

系统提示词可能没有明确告知模型当前运行环境是 Windows,模型默认生成了 Linux 风格的命令。建议在系统提示词里加一句:

建议添加到系统提示词
当前运行环境:Windows PowerShell。请生成 Windows 兼容的命令,不要使用 Linux/Unix 特有命令(如 grep、head、tail、/dev/null 等)。

📋 总结