1. 从一次“工具明明注册了却调不到”说起
如果你正在给 Hermes agent 接入自定义工具链,大概率踩过这个坑:明明在tools/目录里写好了函数,也调用了registry.register(...),可模型就是“看不见”这个工具,tool call 永远不触发。问题不在你的函数写错了,而在于 Hermes 的 tools 架构是注册表 + toolset 暴露 + agent 执行器的三层结构,不是把目录里所有函数一股脑塞给模型。理解这三层怎么协作,是 Hermes agent tools 落地应用的第一步。
Hermes agent 的 tools 系统本质上是一套“给 LLM 建立可控行动空间”的运行时协议层。它解决的不是“Python 怎么调用函数”,而是:这个 agent 此刻允许做什么、模型看到的动作空间如何被压缩过滤、模型发来的 tool call 如何变成可信可审计可中断的真实操作、外部插件和 MCP 如何接进来但不污染核心、工具结果如何返回而不撑爆上下文。这条链路可以抽象成:工具实现 → Registry(系统知道有哪些能力)→ Toolset(本轮/本平台允许暴露哪些)→ Schema(翻译成模型能理解的 JSON Schema)→ 模型决策输出 tool_call → 执行器(校验、审批、并发、拦截、调用)→ 返回(结果预算、持久化、写回 conversation)。
适合谁读:需要为 agent 接入自定义工具链的开发者、正在调试 Hermes 工具不触发的人、想把 MCP 或插件工具统一进 registry 的工程师。下面我会按“注册 → 暴露 → schema 生成 → 执行 → 排障”的完整路径拆开讲,每一步都给可复制的配置和验证动作。核心检索词先记住三个:Hermes agent tools registry、toolset schema 设计、tool call 执行链路。
2. TaoToken 前置:给 Hermes agent 准备可调用的模型入口
在拆 registry 之前,得先让 Hermes agent 有一个能正常返回 tool_call 的模型后端。Hermes 的 tools 链路再完整,如果模型侧不支持 function calling 或返回格式不对,schema 生成得再漂亮也没用。我实测下来,用 TaoToken 作为模型接入层比较省事,它兼容 OpenAI 风格的 tools 参数,Hermes 的get_tool_definitions()生成的 schema 可以直接喂进去。
你需要准备三件套:Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,API Key 在控制台创建,Model ID 选一个支持 function calling 的模型。这三样在 Hermes 的配置里对应模型客户端的初始化参数。如果你用的是 Claude Code 这类工具做辅助开发,也可以在它的 settings 里配同一套 Base URL 和 Key,方便对照调试 schema。
具体操作路径:先到 TaoToken 控制台 创建 API Key,然后在 API Keys 页面 管理你的密钥。拿到 Key 后,模型对话能力可以先在 模型对话 里验证一下 function calling 是否正常返回。如果你打算长期跑编码类 agent 任务,Coding Plan 会更划算。
这里要强调一个顺序问题:很多人一上来就改tools/目录里的核心工具,结果发现模型还是看不到。正确做法是先确认模型后端能正常返回 tool_call,再回头调 registry 和 toolset。因为 Hermes 的 schema 生成依赖模型后端的兼容性,如果后端不支持 tools 参数,get_tool_definitions()返回的 schema 会被静默忽略。你可以先用一个最简单的read_file工具做端到端验证,确认模型能正确输出tool_calls字段,再往下走。
配置模型客户端时,注意 Base URL 不要带多余路径,Hermes 的模型客户端会自己拼接/chat/completions。Key 建议放在环境变量里,不要硬编码进tools/*.py,因为check_fn会检查 env 需求,硬编码会导致工具在缺少 env 时被错误隐藏。这一步做完,你就有了一条可用的模型入口,接下来才是 registry 的注册实践。
3. 可复制配置:registry 注册 + toolset 暴露 + schema 设计
这一节是全文的核心,直接给可复制的代码和配置。Hermes 的工具落地分三步:文件自注册、toolset 解析、schema 生成。缺任何一步,模型都看不到工具。
3.1 工具文件自注册:顶层 registry.register
先看最标准的写法,参考tools/file_tools.py的结构。新建tools/my_tool.py:
from tools.registry import registry, tool_error, tool_result MY_TOOL_SCHEMA = { "type": "function", "function": { "name": "my_search", "description": "在指定数据源中检索关键词,返回匹配条目", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "检索关键词"}, "limit": {"type": "integer", "description": "返回条数上限", "default": 10} }, "required": ["query"] } } } def _check_my_reqs(): import os return bool(os.environ.get("MY_TOOL_API_KEY")) def _handle_my_search(args): query = args.get("query") limit = args.get("limit", 10) if not query: return tool_error("query 不能为空") # 你的真实逻辑 results = [{"title": f"match-{i}", "score": 1.0 - i * 0.1} for i in range(limit)] return tool_result({"items": results, "count": len(results)}) registry.register( name="my_search", toolset="my_toolset", schema=MY_TOOL_SCHEMA, handler=_handle_my_search, check_fn=_check_my_reqs, emoji="", max_result_size_chars=100_000, )关键点:registry.register(...)必须写在模块顶层。discover_builtin_tools()只识别模块顶层的注册调用,如果你把注册藏在函数里,自动发现不会导入它。handler 必须返回 JSON 字符串,用tool_error()和tool_result()辅助函数构造,不要直接返回 dict。
3.2 toolset 暴露:模型看不看得到由它决定
工具文件自注册只表示 registry 知道它,模型能不能看到,取决于 toolset。在toolsets.py里把你的 toolset 加进平台集合:
_HERMES_CORE_TOOLS = { # ... 已有工具 "my_search", } PLATFORM_TOOLSETS = { "hermes-cli": { "toolsets": ["file", "web", "terminal", "my_toolset"], }, "gateway": { "toolsets": ["file", "web", "my_toolset"], }, }用户侧实际是通过hermes tools或setup保存到platform_toolsets,再由_get_platform_tools()转成 enabled toolsets。如果你只是本地调试,可以直接在配置里把my_toolset加进当前平台的启用列表。
3.3 schema 生成:get_tool_definitions 的裁剪逻辑
model_tools.py的get_tool_definitions()不是简单返回 registry 里的 schema,而是在做“模型可见世界”的最终裁剪。它会:把 enabled toolsets 解析成具体工具名、对 disabled toolsets 做减法、隐藏check_fn失败的工具、根据实际可用工具重建execute_code的 schema、在 web 工具不可用时移除 browser schema 里的误导描述、根据 bot 权限动态调整 Discord schema、最后做 schema sanitizer 兼容不同模型后端。
这意味着 tool schema 是行为约束,不只是说明书。对 LLM 来说,它看到的 schema 就是它能采取行动的边界。如果 schema 里说“你可以调用 web_search”,但实际不可用,模型就会被误导。所以你在设计自定义工具的 schema 时,description 要写清楚“什么时候该用、什么时候不该用”,parameters 的 required 要严格,避免模型传空参数。
3.4 插件与 MCP:同一套能力模型的外延
自定义或本地工具,按项目规则不应该优先改核心tools/,而应该走插件。插件通过hermes_cli/plugins.py的ctx.register_tool()注册,底层仍然进入同一个 registry。MCP 工具也是动态注册进 registry,用mcp-<server>作为 toolset,并注册别名。
一个重要变化:MCP discovery 不再是model_tools.py的导入副作用,各入口要自己显式发现,避免 gateway 事件循环被慢 MCP 卡住。如果你在写插件,注册同名工具默认会被拒绝,要有意覆盖必须传override=True。
4. 验证请求:确认工具在 agent 中可被检索与触发
配置写完,必须做端到端验证。分三步:registry 层验证、schema 层验证、agent 层验证。
第一步,registry 层。在 Python 里直接导入并查询:
from tools.registry import registry import tools.my_tool # 触发顶层注册 entry = registry.get("my_search") print(entry.name, entry.toolset, entry.schema["function"]["name"]) # 输出: my_search my_toolset my_search如果这里报None,说明模块没被导入或注册不在顶层。检查discover_builtin_tools()是否扫描到了你的文件。
第二步,schema 层。调用get_tool_definitions()看最终给模型的 tools 列表:
from model_tools import get_tool_definitions defs = get_tool_definitions(enabled_toolsets=["my_toolset"]) names = [d["function"]["name"] for d in defs] print("my_search" in names) # 输出: True如果这里是False,说明 toolset 没被正确解析,或者check_fn返回了 False。检查MY_TOOL_API_KEY环境变量是否存在。
第三步,agent 层。启动一个最小 agent 会话,发一条会触发工具的 prompt:
from agent.agent_init import init_agent agent = init_agent(platform="hermes-cli") print("my_search" in agent.valid_tool_names) # 输出: True resp = agent.run("帮我用 my_search 检索关键词 hermes") print(resp.tool_calls) # 输出: [{'name': 'my_search', 'arguments': {'query': 'hermes'}}]看到tool_calls里有my_search,说明工具从声明到执行整条链路通了。如果模型返回的是普通文本而不是 tool_call,检查模型后端是否支持 function calling,以及 schema 的 description 是否足够清晰。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节对照真实报错,逐个拆。
401 Unauthorized:模型后端 Key 无效或没带上。检查 Base URL 是否为https://taotoken.net/api,Key 是否放在正确的环境变量里。Hermes 的模型客户端初始化时如果 Key 为空,会直接返回 401。注意不要用带 UTM 的 API 地址,API 调用只认https://taotoken.net/api。
local proxy failed:通常是本地网络层拦截或代理配置冲突。检查你的环境变量里有没有残留的HTTP_PROXY/HTTPS_PROXY,Hermes 的模型客户端会读取这些变量。清掉后重启 agent 进程。如果用的是容器环境,确认容器内 DNS 能解析到模型服务地址。
reading choices 报错:模型返回的 JSON 结构里choices字段缺失或格式不对。这多半是模型后端不兼容 OpenAI 的 tools 返回格式。换一个明确支持 function calling 的 Model ID,或者检查get_tool_definitions()生成的 schema 是否被后端接受。有时候 schema 里parameters的嵌套太深,某些后端会截断,简化 schema 结构能解决。
OAuth 相关报错:如果你用的是 Claude Code 或 Codex 这类需要 OAuth 的工具做辅助,注意它们的 auth.json 或 settings 里的 Base URL 要和 Hermes 保持一致。Codex 的auth.json里填 Base URL、Key、Model ID 三件套,缺一不可。CC Switch 或 Cline MCP 场景下,同样要写全这三项,否则 OAuth 流程会走到错误的端点。
还有一个高频坑:check_fn有 30 秒 TTL 缓存。改 env 或 config 后不是每次立即生效,等 30 秒或重启进程再验证。另外todo、memory、session_search、delegate_task虽然注册在 registry,但属于 agent-level tools,会被 agent loop 特殊处理,直接走普通handle_function_call()会被拦。如果你自定义的工具名和这些冲突,会被拒绝注册。
大输出不是简单截断。执行器会把超大结果持久化到 sandbox 文件,再给模型预览,整轮预算控制在tools/tool_result_storage.py。TOOL_TO_TOOLSET_MAP在model_tools.py是 discovery 后构建的兼容常量,动态 MCP/插件刷新后,优先信 registry 实时查询,不要过度依赖旧常量。
6. 继续往下走:把工具链跑成长期能力
工具能触发只是起点。真正要落地,得考虑结果预算、并发执行、插件 hook 和审计。Hermes 的 tool call 不是普通函数调用,而是一个事务化的 agent action:解析模型参数、修复错误工具名、检查 interruption、触发插件pre_tool_call、做 guardrail、文件修改前 checkpoint、destructive terminal command 前 checkpoint、多个 tool call 并发执行、执行中回调 UI 显示状态、结果预算控制、最后 append 成 tool message 回 conversation。
如果你要把这套工具链跑成长期编码或 Agent 能力,建议用 Coding Plan 做模型侧支撑,配合 接入文档 把 Base URL、Key、Model ID 三件套配齐。Claude Code 场景下参考 ClaudeCodeAnthropic 接入说明,把 OAuth 和 auth.json 的字段对齐。
读 Hermes 源码时不要从browser_tool.py这种大文件开始,会被淹没。按这个顺序:先看注册协议tools/registry.py,再看暴露策略toolsets.py,然后 schema 生成model_tools.py,接着 tool call 执行agent/agent_runtime_helpers.py,再看并发和结果处理agent/tool_executor.py,最后才看具体工具。你可以把它想成一个操作系统小内核:registry 是设备表,toolsets 是权限配置,schema 是系统调用 ABI,tool call 是用户态请求,executor 是内核调度器,handler 是驱动实现,result store 是 I/O 缓冲和分页,plugins/MCP 是外接设备。这个类比比“工具函数列表”更接近 Hermes tools 的真实结构。