H繁中版
<!-- Source: https://hermesbible.com/docs/developer-guide/tools-runtime -->

Hermes 工具是自註冊的函式,分組為工具組並透過中央 registry/分派系統執行。

主要檔案:

  • tools/registry.py
  • model_tools.py
  • toolsets.py
  • tools/terminal_tool.py
  • tools/environments/*

工具註冊模型

每個工具模組在匯入時呼叫 registry.register(...)

model_tools.py 負責匯入/發現工具模組並建構模型使用的 schema 列表。

registry.register() 如何運作

tools/ 中的每個工具檔案在模組級別呼叫 registry.register() 來宣告自己。函式簽名為:

registry.register(
    name="terminal",               # 獨特的工具名稱(用於 API schema)
    toolset="terminal",            # 此工具所屬的工具組
    schema={...},                  # OpenAI function-calling schema(描述、參數)
    handler=handle_terminal,       # 當工具被呼叫時執行的函式
    check_fn=check_terminal,       # 選用:回傳 True/False 表示可用性
    requires_env=["SOME_VAR"],     # 選用:需要的環境變數(用於 UI 顯示)
    is_async=False,                # 處理器是否為非同步協程
    description="Run commands",    # 人類可讀的描述
    emoji="💻",                    # 用於旋轉器/進度顯示的 Emoji
)

每次呼叫都會建立一個 ToolEntry,儲存在以工具名稱為金鑰的單例 ToolRegistry._tools 字典中。如果跨工具組發生名稱衝突,會記錄警告並以後註冊的為準。

發現機制:discover_builtin_tools()

model_tools.py 被匯入時,它會從 tools/registry.py 呼叫 discover_builtin_tools()。此函式使用 AST 解析掃描每個 tools/*.py 檔案,尋找包含頂層 registry.register() 呼叫的模組,然後匯入它們:

# tools/registry.py(簡化版)
def discover_builtin_tools(tools_dir=None):
    tools_path = Path(tools_dir) if tools_dir else Path(__file__).parent
    for path in sorted(tools_path.glob("*.py")):
        if path.name in {"__init__.py", "registry.py", "mcp_tool.py"}:
            continue
        if _module_registers_tools(path):  # 頂層 registry.register() 的 AST 檢查
            importlib.import_module(f"tools.{path.stem}")

這種自動發現意味著新的工具檔案會被自動拾取 — 不需要維護手動清單。AST 檢查只匹配頂層的 registry.register() 呼叫(不匹配函式內部的呼叫),因此 tools/ 中的輔助模組不會被匯入。

每次匯入都會觸發模組的 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 key 存在 — 例如 web 搜尋的 lambda: bool(os.environ.get("SERP_API_KEY"))
  • 服務運行中 — 例如檢查 Honcho 伺服器是否已設定
  • 二進位檔已安裝 — 例如驗證瀏覽器工具的 playwright 是否可用

registry.get_definitions() 建構模型的 schema 列表時,它會執行每個工具的 check_fn()

# 簡化自 registry.py
if entry.check_fn:
    try:
        available = bool(entry.check_fn())
    except Exception:
        available = False   # 異常 = 不可用
    if not available:
        continue            # 完全跳過此工具

關鍵行為:

  • 檢查結果是每次呼叫快取的 — 如果多個工具共享同一個 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 篩選 — 已解析的工具名稱集合傳遞給 registry.get_definitions(),該函式套用 check_fn 篩選並回傳 OpenAI 格式的 schema。

  5. 動態 schema 修補 — 篩選後,execute_codebrowser_navigate 的 schema 會被動態調整為只引用實際通過篩選的工具(防止模型產生不存在工具的幻覺)。

舊式工具組名稱

帶有 _tools 後綴的舊工具組名稱(例如 web_toolsterminal_tools)透過 _LEGACY_TOOLSET_MAP 對應到它們的現代工具名稱以實現向後相容。

分派

在運行時,工具透過中央 registry 進行分派,代理程式迴圈對某些代理程式級工具(如記憶體/todo/session-search 處理)有例外處理。

分派流程:模型 tool_call → 處理器執行

當模型回傳 tool_call 時,流程為:

帶有 tool_call 的模型回應
    ↓
run_agent.py 代理程式迴圈
    ↓
model_tools.handle_function_call(name, args, task_id, user_task)
    ↓
[代理程式迴圈工具?] → 直接由代理程式迴圈處理(todo、memory、session_search、delegate_task)
    ↓
[外掛前鉤子] → invoke_hook("pre_tool_call", ...)
    ↓
registry.dispatch(name, args, **kwargs)
    ↓
按名稱查找 ToolEntry
    ↓
[非同步處理器?] → 透過 _run_async() 橋接
[同步處理器?]  → 直接呼叫
    ↓
回傳結果字串(或 JSON 錯誤)
    ↓
[外掛後鉤子] → invoke_hook("post_tool_call", ...)

錯誤包裝

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

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

  2. handle_function_call() — 將整個分派包裝在 secondary try/except 中,回傳 {"error": "Error executing tool_name: message"}

這確保模型始終收到格式正確的 JSON 字串,永遠不會收到未處理的異常。

代理程式迴圈工具

四個工具在 registry 分派之前被攔截,因為它們需要代理程式級狀態(TodoStore、MemoryStore 等):

  • todo — 規劃/任務追蹤
  • memory — 持久化記憶體寫入
  • session_search — 跨 session 召回
  • delegate_task — 產生子代理程式 session

這些工具的 schema 仍然在 registry 中註冊(用於 get_tool_definitions),但如果分派不知何故直接到達它們,它們的處理器會回傳一個佔位錯誤。

非同步橋接

當工具處理器是非同步時,_run_async() 將其橋接到同步分派路徑:

  • CLI 路徑(無運行中的迴圈) — 使用持久化事件迴圈來保持已快取的非同步客戶端存活
  • 閘道器路徑(運行中的迴圈) — 使用 asyncio.run() 啟動一個一次性執行緒
  • 工作者執行緒(並行工具) — 使用儲存在執行緒本地儲存中的每個執行緒持久化迴圈

DANGEROUS_PATTERNS 核准流程

終端機工具整合了一個在 tools/approval.py 中定義的危險命令核准系統:

  1. 模式偵測DANGEROUS_PATTERNS 是一個 (regex, description) 元組列表,涵蓋破壞性操作:

    • 遞迴刪除(rm -rf
    • 檔案系統格式化(mkfsdd
    • SQL 破壞性操作(DROP TABLE、不帶 WHEREDELETE FROM
    • 系統設定覆寫(> /etc/
    • 服務操作(systemctl stop
    • 遠端程式碼執行(curl | sh
    • Fork 炸彈、程序終止等
  2. 偵測 — 在執行任何終端機命令之前,detect_dangerous_command(command) 會對照所有模式進行檢查。

  3. 核准提示 — 如果找到匹配項:

    • CLI 模式 — 互動式提示要求使用者核准、拒絕或永遠允許
    • 閘道器模式 — 非同步核准回呼函式將請求發送到訊息平台
    • 智慧核准 — 選擇性地,輔助 LLM 可以自動核准符合模式的低風險命令(例如 rm -rf node_modules/ 是安全的但匹配「遞迴刪除」)
  4. Session 狀態 — 核准按 session 追蹤。一旦你為一個 session 核准了「遞迴刪除」,後續的 rm -rf 命令不會再次提示。

  5. 永久允許清單 — 「永遠允許」選項會將模式寫入 config.yamlcommand_allowlist,跨 session 持久化。

終端機/運行時環境

終端機系統支援多種後端:

  • local(本地)
  • docker
  • ssh
  • singularity
  • modal
  • daytona

它也支援:

  • 每個任務的 cwd 覆寫
  • 背景進程管理
  • PTY 模式
  • 危險命令的核准回呼函式

並行性

工具呼叫可能順序或並行執行,取決於工具組合和互動需求。

相關文件



Trajectory Format