跳到主要內容

工具運行時

Hermes 工具是自注冊函數,按工具集分組,並通過中央註冊/分派系統執行。

主要文件:

  • tools/registry.py
  • model_tools.py
  • toolsets.py
  • tools/terminal_tool.py
  • tools/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 工具和插件工具:

  1. MCP 工具tools.mcp_tool.discover_mcp_tools() 讀取 MCP 服務器配置,並從外部服務器註冊工具。
  2. 插件工具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-clihermes-telegram 等)
  • 動態 MCP 工具集
  • 精心策劃的專用工具集,如 hermes-acp

get_tool_definitions() 如何過濾工具

主要入口點是 model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode)

  1. 如果提供了 enabled_toolsets —— 僅包含這些工具集中的工具。每個工具集名稱通過 resolve_toolset() 解析,將複合工具集展開為單個工具名稱。

  2. 如果提供了 disabled_toolsets —— 從所有工具集開始,然後減去被禁用的工具集。

  3. 如果兩者均未提供 —— 包含所有已知工具集。

  4. 註冊表過濾 —— 解析後的工具名稱集合傳遞給 registry.get_definitions(),該函數應用 check_fn 過濾,並返回 OpenAI 格式的模式。

  5. 動態模式修補 —— 過濾後,execute_codebrowser_navigate 模式會動態調整,僅引用實際通過過濾的工具(防止模型幻覺出不可用的工具)。

舊版工具集名稱

帶有 _tools 後綴的舊版工具集名稱(如 web_toolsterminal_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", ...)

錯誤包裝

所有工具執行在兩個層級上都進行了錯誤處理:

  1. registry.dispatch() —— 捕獲處理程序中的任何異常,並返回 {"error": "Tool execution failed: ExceptionType: message"} 作為 JSON。

  2. 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 中定義的危險命令審批系統:

  1. 模式檢測 —— DANGEROUS_PATTERNS 是一組 (正則表達式, 描述) 元組,涵蓋破壞性操作:

    • 遞歸刪除(rm -rf
    • 文件系統格式化(mkfs, dd
    • SQL 破壞性操作(DROP TABLEDELETE FROMWHERE 子句)
    • 系統配置覆蓋(> /etc/
    • 服務操作(systemctl stop
    • 遠程代碼執行(curl | sh
    • 分叉炸彈、進程終止等
  2. 檢測 —— 在執行任何終端命令之前,detect_dangerous_command(command) 會與所有模式進行比對。

  3. 審批提示 —— 若發現匹配項:

    • CLI 模式 —— 交互式提示要求用戶批准、拒絕或永久允許
    • 網關模式 —— 異步審批迴調將請求發送至消息平臺
    • 智能審批 —— 可選地,輔助 LLM 可自動批准低風險且匹配模式的命令(例如 rm -rf node_modules/ 雖匹配“遞歸刪除”模式,但屬於安全操作)
  4. 會話狀態 —— 審批記錄按會話進行。一旦在當前會話中批准了“遞歸刪除”,後續的 rm -rf 命令將不再重複提示。

  5. 永久白名單 —— “永久允許”選項會將該模式寫入 config.yamlcommand_allowlist,實現跨會話持久化。

終端/運行時環境

終端系統支持多種後端:

  • local
  • docker
  • ssh
  • singularity
  • modal
  • daytona

同時支持:

  • 每任務的 cwd 覆蓋
  • 後臺進程管理
  • PTY 模式
  • 危險命令的審批迴調

併發性

工具調用可根據工具組合和交互需求,選擇順序執行或併發執行。