工具運行時
Hermes 工具是自注冊函數,按工具集分組,並通過中央註冊/分派系統執行。
主要文件:
tools/registry.pymodel_tools.pytoolsets.pytools/terminal_tool.pytools/environments/*
工具註冊模型
每個工具模塊在導入時調用 registry.register(...)。
model_tools.py 負責導入/發現工具模塊,並構建模型使用的模式列表。
registry.register() 的工作原理
tools/ 目錄中的每個工具文件在模塊級別調用 registry.register() 以聲明自身。函數簽名如下:
registry.register(
name="terminal", # 唯一的 tool 名稱(在 API 模式中使用)
toolset="terminal", # Toolset 這個tool 屬於
schema={...}, # OpenAI 函數調用架構(描述、參數)
handler=handle_terminal, # 調用工具時執行的函數
check_fn=check_terminal, # 可選:返回 True/False 表示是否可用
requires_env=["SOME_VAR"], # 可選:需要環境變量(用於 UI 顯示)
is_async=False, # 處理程序是否是異步協程
description="Run commands", # 人類可讀的描述
emoji="💻", # 旋轉器“0”顯示的表情符號
)
每次調用都會創建一個 ToolEntry,存儲在單例 ToolRegistry._tools 字典中,以工具名稱為鍵。如果不同工具集中出現名稱衝突,將記錄警告信息,後註冊的版本將覆蓋先註冊的版本。
發現:_discover_tools()
當 model_tools.py 被導入時,它會調用 _discover_tools(),按順序導入所有工具模塊:
_modules = [
"tools.web_tools",
"tools.terminal_tool",
"tools.file_tools",
"tools.vision_tools",
"tools.mixture_of_agents_tool",
"tools.image_generation_tool",
"tools.skills_tool",
"tools.skill_manager_tool",
"tools.browser_tool",
"tools.cronjob_tools",
"tools.rl_training_tool",
"tools.tts_tool",
"tools.todo_tool",
"tools.memory_tool",
"tools.session_search_tool",
"tools.clarify_tool",
"tools.code_execution_tool",
"tools.delegate_tool",
"tools.process_registry",
"tools.send_message_tool",
# "tools.honcho_tools", # 已刪除 — Honcho 現在是 memory provider 插件
"tools.homeassistant_tool",
]
每次導入都會觸發模塊中 registry.register() 的調用。對於可選工具(例如缺少 fal_client 時的圖像生成),導入錯誤會被捕獲並記錄——這不會阻止其他工具的加載。
核心工具發現完成後,還會發現 MCP 工具和插件工具:
- MCP 工具 —
tools.mcp_tool.discover_mcp_tools()讀取 MCP 服務器配置,並從外部服務器註冊工具。 - 插件工具 —
hermes_cli.plugins.discover_plugins()加載用戶/項目/Pip 插件,這些插件可能註冊額外的工具。
工具可用性檢查(check_fn)
每個工具可選擇性地提供一個 check_fn —— 一個返回 True 表示工具可用、返回 False 表示不可用的可調用對象。典型的檢查包括:
- API 密鑰存在 —— 例如
lambda: bool(os.environ.get("SERP_API_KEY"))用於網絡搜索 - 服務正在運行 —— 例如檢查 Honcho 服務器是否已配置
- 二進制已安裝 —— 例如驗證
playwright是否可用於瀏覽器工具
當 registry.get_definitions() 為模型構建模式列表時,會運行每個工具的 check_fn():
# 由 registry.py 簡化而來
if entry.check_fn:
try:
available = bool(entry.check_fn())
except Exception:
available = False # 例外=不可用
if not available:
continue # 完全跳過這個 tool
關鍵行為:
- 檢查結果是按調用緩存的 —— 如果多個工具共享相同的
check_fn,它只會運行一次。 check_fn()中的異常被視為“不可用”(安全降級)。is_toolset_available()方法用於檢查工具集的check_fn是否通過,該方法用於 UI 顯示和工具集解析。
工具集解析
工具集是工具的命名捆綁包。Hermes 通過以下方式解析它們:
- 顯式啟用/禁用的工具集列表
- 平臺預設(如
hermes-cli、hermes-telegram等) - 動態 MCP 工具集
- 精心策劃的專用工具集,如
hermes-acp
get_tool_definitions() 如何過濾工具
主要入口點是 model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode):
-
如果提供了
enabled_toolsets—— 僅包含這些工具集中的工具。每個工具集名稱通過resolve_toolset()解析,將複合工具集展開為單個工具名稱。 -
如果提供了
disabled_toolsets—— 從所有工具集開始,然後減去被禁用的工具集。 -
如果兩者均未提供 —— 包含所有已知工具集。
-
註冊表過濾 —— 解析後的工具名稱集合傳遞給
registry.get_definitions(),該函數應用check_fn過濾,並返回 OpenAI 格式的模式。 -
動態模式修補 —— 過濾後,
execute_code和browser_navigate模式會動態調整,僅引用實際通過過濾的工具(防止模型幻覺出不可用的工具)。
舊版工具集名稱
帶有 _tools 後綴的舊版工具集名稱(如 web_tools、terminal_tools)通過 _LEGACY_TOOLSET_MAP 映射到現代工具名稱,以保證向後兼容。
分派
運行時,工具通過中央註冊表進行分派,但某些 Agent 層工具(如記憶/待辦事項/會話搜索處理)除外。
分派流程:模型 tool_call → 處理程序執行
當模型返回 tool_call 時,流程如下:
Model response with tool_call
↓
run_agent.py agent loop
↓
model_tools.handle_function_call(name, args, task_id, user_task)
↓
[Agent-loop tools?] → handled directly by agent loop (todo, memory, session_search, delegate_task)
↓
[Plugin pre-hook] → invoke_hook("pre_tool_call", ...)
↓
registry.dispatch(name, args, **kwargs)
↓
Look up ToolEntry by name
↓
[Async handler?] → bridge via _run_async()
[Sync handler?] → call directly
↓
Return result string (or JSON error)
↓
[Plugin post-hook] → invoke_hook("post_tool_call", ...)
錯誤包裝
所有工具執行在兩個層級上都進行了錯誤處理:
-
registry.dispatch()—— 捕獲處理程序中的任何異常,並返回{"error": "Tool execution failed: ExceptionType: message"}作為 JSON。 -
handle_function_call()—— 將整個分派包裝在二級 try/except 中,返回{"error": "Error executing tool_name: message"}。
這確保模型始終接收到格式良好的 JSON 字符串,而不會收到未處理的異常。
Agent 循環工具
有四個工具在註冊表分派前被攔截,因為它們需要 Agent 層狀態(如 TodoStore、MemoryStore 等):
todo—— 規劃/任務跟蹤memory—— 持久化記憶寫入session_search—— 跨會話回憶delegate_task—— 啟動子 Agent 會話
這些工具的模式仍註冊在註冊表中(用於 get_tool_definitions),但如果分派意外到達它們,其處理程序將返回一個存根錯誤。
異步橋接
當工具處理器為異步時,_run_async() 會將其橋接到同步分發路徑:
- CLI 路徑(無運行中的事件循環) —— 使用持久化事件循環,以保持緩存的異步客戶端處於活躍狀態
- 網關路徑(正在運行的事件循環) —— 使用
asyncio.run()啟動一個可丟棄的線程 - 工作線程(並行工具) —— 使用存儲在線程局部存儲中的每線程持久化事件循環
DANGEROUS_PATTERNS 審批流程
終端工具集成了在 tools/approval.py 中定義的危險命令審批系統:
-
模式檢測 ——
DANGEROUS_PATTERNS是一組(正則表達式, 描述)元組,涵蓋破壞性操作:- 遞歸刪除(
rm -rf) - 文件系統格式化(
mkfs,dd) - SQL 破壞性操作(
DROP TABLE,DELETE FROM無WHERE子句) - 系統配置覆蓋(
> /etc/) - 服務操作(
systemctl stop) - 遠程代碼執行(
curl | sh) - 分叉炸彈、進程終止等
- 遞歸刪除(
-
檢測 —— 在執行任何終端命令之前,
detect_dangerous_command(command)會與所有模式進行比對。 -
審批提示 —— 若發現匹配項:
- CLI 模式 —— 交互式提示要求用戶批准、拒絕或永久允許
- 網關模式 —— 異步審批迴調將請求發送至消息平臺
- 智能審批 —— 可選地,輔助 LLM 可自動批准低風險且匹配模式的命令(例如
rm -rf node_modules/雖匹配“遞歸刪除”模式,但屬於安全操作)
-
會話狀態 —— 審批記錄按會話進行。一旦在當前會話中批准了“遞歸刪除”,後續的
rm -rf命令將不再重複提示。 -
永久白名單 —— “永久允許”選項會將該模式寫入
config.yaml的command_allowlist,實現跨會話持久化。
終端/運行時環境
終端系統支持多種後端:
- local
- docker
- ssh
- singularity
- modal
- daytona
同時支持:
- 每任務的 cwd 覆蓋
- 後臺進程管理
- PTY 模式
- 危險命令的審批迴調
併發性
工具調用可根據工具組合和交互需求,選擇順序執行或併發執行。