DeepAI Paper Hermes Agent 教程 Hermes 只剩 MCP 工具,terminal 全没了:一次 disabled_toolsets 连环误伤

Hermes 只剩 MCP 工具,terminal 全没了:一次 disabled_toolsets 连环误伤

有一种工具链故障很烦:你问模型“能不能用 terminal / write_file / memory”,它不是调用失败,而是压根不知道这些工具存在。

NousResearch/hermes-agent issue #22573 记录的就是这种情况。用户在 Hermes v0.13.0 里发现:

Telegram 和 CLI session 里,native tools 全部消失。
只剩 MCP tools。

更诡异的是,单独跑 Python 测试时,工具注册链路一切正常;真正进入 Gateway / CLI live runtime 后,工具列表就被削成了 MCP-only。

这篇文章想拆的不是“怎么重启服务”,而是一个更隐蔽的配置陷阱:**一个 hermes-* 平台工具集被写进 agent.disabled_toolsets 后,为什么会把所有 native tools 一锅端。**


现象:模型不是不会用 terminal,而是根本看不到 terminal

issue 里的现场非常具体。

用户安装 Hermes v0.13.0,并配置了 MCP servers:

obsidian + gdrive + notion + tavily

这些 MCP server 合起来提供 42 个工具。

理论上,session 里还应该有 Hermes native tools,例如:

terminal
write_file
read_file
memory
send_message
web_search
cronjob

预期工具数量大致是:

28 native tools + 42 MCP tools = 70 tools 左右

但 live session 文件里看到的是:

Tool count: 42
Has terminal: False
Platform: telegram

也就是说,模型调用层只收到了 MCP 工具定义;Hermes 自己的 native tools 没有进入 session。

这类问题很容易被误判成模型问题。实际上模型能不能调用工具,第一步取决于 runtime 传给模型的 tool schemas。如果 terminal 根本没在 schemas 里,换模型也不会 magically fix。


最容易误导人的线索:standalone 测试全是好的

这个 issue 有意思的地方在于:单独测每一段,结果都正常。

例如:

from tools.registry import registry, discover_builtin_tools

discover_builtin_tools()
print('terminal' in registry.get_all_tool_names())

结果显示 native tools 注册正常。

再测平台工具集解析:

from hermes_cli.tools_config import _get_platform_tools

result = _get_platform_tools(config, 'telegram')

也能解析出 terminal、file、memory、web、messaging 等工具集。

再测:

from model_tools import get_tool_definitions

result = get_tool_definitions(['hermes-telegram'], [], quiet_mode=True)

同样能返回 native tools。

这就是最难排的点:

孤立链路正确,live runtime 错误。

当你只看 registry、只看 config、只看 get_tool_definitions() 的干净调用,很容易得出“代码没问题,是环境坏了”的结论。

但 live runtime 里的输入参数,才是真正决定 session 工具列表的东西。


关键线索:agent.disabled_toolsets 里混进了 hermes-yuanbao

后续评论里,用户找到了真正触发点。

原始配置里有这样一段:

agent:
  disabled_toolsets:
    - discord
    - discord_admin
    - homeassistant
    - moa
    - rl
    - hermes-yuanbao
    - spotify
    - browser-cdp

问题就藏在这一行:

- hermes-yuanbao

它看起来像“禁用 Yuanbao 平台相关工具”,但在 Hermes 的工具系统里,hermes-* 不是普通工具分类,而是平台 composite toolset。

这些平台 composite toolset 会展开到一组核心 native tools。

换句话说:

hermes-yuanbao
不是一个小工具。
它解析出来可能是一整套 _HERMES_CORE_TOOLS。

于是,_compute_tool_definitions() 处理 disabled toolsets 时发生了连锁反应。


一行配置如何把所有 native tools 删掉

可以把过程简化成这样:

1. enabled_toolsets 里包含 telegram 需要的 native tools
2. disabled_toolsets 里出现 hermes-yuanbao
3. resolve_toolset("hermes-yuanbao") 展开为 _HERMES_CORE_TOOLS
4. tools_to_include.difference_update(resolved)
5. 所有 native tools 被移除
6. MCP tools 不在 _HERMES_CORE_TOOLS 里,所以留下

这就解释了为什么最终 live session 里只剩 42 个 MCP tools。

不是 MCP 特别坚强,而是它们的命名和来源不在这次 difference_update 的打击范围内。

用 issue 里的模拟结果表达,就是:

disabled_with_bug = [..., 'hermes-yuanbao', ...]
_compute_tool_definitions(...)
-> count=42, has_terminal=False

移除 hermes-yuanbao 后:

disabled_fixed = [...]
_compute_tool_definitions(...)
-> has_terminal=True

这就把“只有 MCP tools load”的谜底串起来了。


为什么换模型没有用?

issue 里用户测试过多个模型或 provider,包括 Claude、Kimi、Gemini 等,现象不变。

这是符合预期的。

因为问题发生在模型调用前:

Hermes runtime 组装 tool schemas
↓
native tools 已经被 disabled_toolsets 过滤掉
↓
provider 收到的工具列表只有 MCP tools
↓
模型自然无法调用 terminal/write_file/memory

如果工具 schema 没传进去,模型不可能调用一个它看不见的函数。

所以这类问题排查顺序应该是:

session JSON / tool schemas
→ enabled_toolsets / disabled_toolsets
→ toolset resolve 结果
→ provider 请求参数
→ 模型行为

不要一开始就纠结模型“会不会用工具”。


两个真正该修的点

从 issue 评论看,这个问题不是单点误操作,而是两个设计边界叠在一起。

第一,hermes tools TUI 可能把平台 composite toolset 写进:

agent.disabled_toolsets

但这个字段更适合放普通工具分类,而不是 hermes-* 这种平台级 composite。

第二,_compute_tool_definitions() 在处理 disabled set 时,没有防御 hermes-* 名称。

一旦平台 composite 被当成 disabled toolset 解析,就会展开成核心工具全集,然后把 native tools 全部扣掉。

一个防御性修法是:

if toolset_name.startswith("hermes-"):
    logger.warning(
        "Skipping platform toolset '%s' in disabled_toolsets",
        toolset_name,
    )
    continue

更完整的修法则应该让配置写入层区分:

平台开关
工具分类开关
具体工具开关

不要把平台 composite 和工具分类混在同一个 disabled list 里。


排查时可以直接看这三类证据

遇到 “Hermes terminal missing / only MCP tools load / native tools absent” 时,不要只问模型。

先看 session 里实际工具数量:

import json, glob

latest = sorted(glob.glob('~/.hermes/sessions/session_*.json'))[-1]
data = json.load(open(latest))
tools = data.get('tools', [])

print('Tool count:', len(tools))
print('Has terminal:', any(t['function']['name'] == 'terminal' for t in tools))
print('Has write_file:', any(t['function']['name'] == 'write_file' for t in tools))

再看配置里有没有危险项:

agent:
  disabled_toolsets:
    - hermes-xxx

只要 disabled_toolsets 里出现 hermes-*,就要高度怀疑它会展开成平台核心工具集。

最后确认 live runtime 读的是哪份配置、哪份代码:

from hermes_cli.config import get_config_path, load_config
import run_agent, model_tools, sys

print(get_config_path())
print(load_config().get('agent', {}).get('disabled_toolsets'))
print(run_agent.__file__)
print(model_tools.__file__)
print(sys.executable)

这一步能排除“我改了 A 配置,但 gateway 读的是 B 配置”的常见坑。


和 DeepAI API 中转站一起部署时,应该怎么分层看?

很多 Hermes 用户会把工具系统、Gateway、模型 Provider、API 中转站放在同一条链路里:

Telegram / CLI
→ Hermes Gateway / runtime
→ tool schemas
→ model provider
→ OpenAI-compatible API

如果你使用 DeepAI API 中转站,建议在模型调用层重点观察:

  • Base URL 是否指向 https://api.deepai.wang/v1
  • API Key 是否正确;
  • model id 是否存在;
  • provider 返回的错误码;
  • latency、usage、限流和账单统计。

但像 terminalwrite_filememory 这类工具是否进入 session,优先看 Hermes runtime 生成的 tool schemas。

一个实用判断是:

工具 schema 里没有 terminal → 先查 Hermes toolsets/config
工具 schema 里有 terminal,但模型拒绝/不会调用 → 再查模型能力和 prompt
provider 请求失败 → 再查 API Base URL、Key、模型名、网络和额度

这样分层,问题会清楚很多。


小结:disabled list 不是垃圾桶

#22573 的教训很直接:

不要把平台 composite toolset 当成普通工具分类塞进 disabled_toolsets。

hermes-yuanbao 这种名字看起来只是一个平台,但它解析后可能覆盖一整套 _HERMES_CORE_TOOLS。当 _compute_tool_definitions() 对它做差集时,native tools 会被成批移除。

最终表现就是:

MCP tools 还在,terminal / write_file / memory 全没了。

这类问题的排查关键,不是换模型,也不是盲目重装,而是看 live session 最终拿到了哪些 tool schemas,再倒推 enabled/disabled toolsets 的解析过程。

工具链越复杂,配置字段的语义边界越重要。一个看似合理的 disabled 项,可能就是整条工具链断掉的地方。

Related Post

DeepSeek Anthropic-compatible API 第二轮 HTTP 400:为什么 thinking blocks 不能被一刀切剥掉?DeepSeek Anthropic-compatible API 第二轮 HTTP 400:为什么 thinking blocks 不能被一刀切剥掉?

Hermes 使用 DeepSeek Anthropic-compatible Messages API 且启用 reasoning_effort 时,多轮会话第二轮可能报 HTTP 400:content[].thinking must be passed back。本文客观复盘 #22313:convert_messages_to_anthropic 如何剥掉 thinking blocks,为什么 DeepSeek/Kimi 需要保留 unsigned thinking,以及 provider-aware adapter 的修复方向。

Hermes Agent provider:auto 选错辅助模型?title_generation 走 fallback provider 报 404 排错指南Hermes Agent provider:auto 选错辅助模型?title_generation 走 fallback provider 报 404 排错指南

Hermes Agent 主模型正常,但 title_generation 报 HTTP 404?可能是 auxiliary provider:auto 没跟随主 provider,而是选到 fallback provider(如 minimax),导致 SDK、api_mode 或 endpoint path 不匹配。本文解释 provider:auto、fallback_model、WeChat 错误泄露和显式配置 workaround。

Hermes Auxiliary 任务排错:主聊天正常,为什么标题生成、压缩和后台 review 会失败?Hermes Auxiliary 任务排错:主聊天正常,为什么标题生成、压缩和后台 review 会失败?

Hermes 主聊天正常,不代表 title generation、context compression、session search、background review 都正常。本文基于 GitHub Issues 解析 auxiliary client 502、aux provider routing、compression context length 和后台 review 输出泄漏。