hermes-agent/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/tools-runtime.md
Teknium febc4cfec0
remove Vercel AI Gateway and Vercel Sandbox (#33067)
* remove Vercel AI Gateway provider and Vercel Sandbox terminal backend

Both Vercel-hosted integrations are removed end-to-end. Users on the AI
Gateway should switch to OpenRouter or one of the other aggregators
(Nous Portal, Kilo Code). Users on the Vercel Sandbox backend should
switch to Docker, Modal, Daytona, or SSH.

What's removed:
- `plugins/model-providers/ai-gateway/` provider plugin
- `hermes_cli/vercel_auth.py` Vercel-Sandbox auth helper
- `tools/environments/vercel_sandbox.py` terminal backend
- `ai-gateway` provider wiring across auth, doctor, setup, models,
  config, status, providers, main, web_server, model_normalize, dump
- `vercel_sandbox` backend wiring across terminal_tool, file_tools,
  code_execution_tool, file_operations, approval, skills_tool,
  environments/local, credential_files, lazy_deps, prompt_builder,
  cli, gateway/run
- `AI_GATEWAY_BASE_URL` constant, `_AI_GATEWAY_HEADERS` auxiliary-client
  header set, run_agent base-URL header/reasoning special-cases
- `[vercel]` pyproject extra and `vercel`/`vercel-workers` from uv.lock
- env vars: `AI_GATEWAY_API_KEY`, `AI_GATEWAY_BASE_URL`, `VERCEL_TOKEN`,
  `VERCEL_PROJECT_ID`, `VERCEL_TEAM_ID`, `VERCEL_OIDC_TOKEN`,
  `TERMINAL_VERCEL_RUNTIME`
- Tests: deletes test_ai_gateway_models.py and
  test_vercel_sandbox_environment.py; scrubs references across 23
  surviving test files (no entire tests deleted unless they were
  dedicated to AI Gateway / Sandbox)
- Docs: provider tables, env-var reference, setup guides, security
  notes, tool config, terminal-backend tables — English plus zh-Hans
  i18n parity
- `hermes-agent` skill: provider table entry and remote-backend list

What stays (intentional):
- `popular-web-designs/templates/vercel.md` — CSS design reference,
  unrelated to Vercel-the-AI-product
- `x-vercel-id` in `stream_diag.py` headers — generic Vercel CDN
  response header, useful diag signal on any Vercel-hosted endpoint
- `vercel-labs/agent-browser` URL in browser config — lightpanda
  browser project, different OSS effort
- `userStories.json` historical contributor entry mentioning Vercel
  Sandbox — archive, not active docs

Validation:
- 1153 tests in the 22 targeted files pass (`scripts/run_tests.sh`)
- Full repo `py_compile` clean
- Live import of every touched module + invariant check (no
  `ai-gateway` in `PROVIDER_REGISTRY`, no `_AI_GATEWAY_HEADERS`, no
  `vercel_sandbox` in `_REMOTE_TERMINAL_BACKENDS`)

* test: convert profile-count check from change-detector to invariant

The hardcoded "== 34" assertion broke when ai-gateway was removed.
Per AGENTS.md change-detector-test guidance, assert the relationship
(registry count >= number of plugin dirs) instead of a literal count.
Counts shift when providers are added/removed; that's expected.
2026-05-27 00:43:32 -07:00

9.7 KiB
Raw Blame History

sidebar_position title description
9 工具运行时 工具注册表、toolset、调度及终端环境的运行时行为

工具运行时

Hermes 工具是自注册函数,按 toolset工具集分组并通过中央注册表/调度系统执行。

主要文件:

  • 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",            # 该工具所属的 toolset
    schema={...},                  # OpenAI function-calling schema描述、参数
    handler=handle_terminal,       # 工具被调用时执行的函数
    check_fn=check_terminal,       # 可选:返回 True/False 表示是否可用
    requires_env=["SOME_VAR"],     # 可选:所需的环境变量(用于 UI 显示)
    is_async=False,                # handler 是否为异步协程
    description="Run commands",    # 人类可读的描述
    emoji="💻",                    # 用于 spinner/进度显示的 emoji
)

每次调用都会创建一个 ToolEntry,以工具名称为键存储在单例 ToolRegistry._tools 字典中。若不同 toolset 之间出现名称冲突,会记录警告,后注册的条目覆盖前者。

发现机制: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):  # AST 检查顶层 registry.register()
            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 密钥是否存在 — 例如,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() 方法检查某个 toolset 的 check_fn 是否通过,用于 UI 显示和 toolset 解析。

Toolset 解析

Toolset 是工具的命名集合。Hermes 通过以下方式解析它们:

  • 显式启用/禁用的 toolset 列表
  • 平台预设(hermes-clihermes-telegram 等)
  • 动态 MCP toolset
  • 精选的特殊用途集合,如 hermes-acp

get_tool_definitions() 如何过滤工具

主入口点为 model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode)

  1. 若提供了 enabled_toolsets — 仅包含这些 toolset 中的工具。每个 toolset 名称通过 resolve_toolset() 解析,将复合 toolset 展开为单个工具名称。

  2. 若提供了 disabled_toolsets — 从所有 toolset 开始,减去已禁用的。

  3. 若两者均未提供 — 包含所有已知 toolset。

  4. 注册表过滤 — 解析后的工具名称集合传递给 registry.get_definitions(),后者应用 check_fn 过滤并返回 OpenAI 格式的 schema。

  5. 动态 schema 修补 — 过滤后,execute_codebrowser_navigate 的 schema 会被动态调整,仅引用实际通过过滤的工具(防止模型幻觉出不可用的工具)。

旧版 toolset 名称

带有 _tools 后缀的旧版 toolset 名称(例如 web_toolsterminal_tools)通过 _LEGACY_TOOLSET_MAP 映射到其现代工具名称,以保持向后兼容性。

调度

运行时,工具通过中央注册表调度,但部分 agent 级别的工具(如 memory/todo/session-search 处理)由 agent 循环直接处理。

调度流程:模型 tool_call → handler 执行

当模型返回 tool_call 时,流程如下:

模型响应包含 tool_call
    ↓
run_agent.py agent 循环
    ↓
model_tools.handle_function_call(name, args, task_id, user_task)
    ↓
[Agent 循环工具?] → 由 agent 循环直接处理todo、memory、session_search、delegate_task
    ↓
[插件 pre-hook] → invoke_hook("pre_tool_call", ...)
    ↓
registry.dispatch(name, args, **kwargs)
    ↓
按名称查找 ToolEntry
    ↓
[异步 handler] → 通过 _run_async() 桥接
[同步 handler]  → 直接调用
    ↓
返回结果字符串(或 JSON 错误)
    ↓
[插件 post-hook] → invoke_hook("post_tool_call", ...)

错误包装

所有工具执行在两个层级进行错误处理:

  1. registry.dispatch() — 捕获 handler 抛出的任何异常,并以 JSON 形式返回 {"error": "Tool execution failed: ExceptionType: message"}

  2. handle_function_call() — 将整个调度包裹在次级 try/except 中,返回 {"error": "Error executing tool_name: message"}

这确保模型始终收到格式正确的 JSON 字符串,而不会遇到未处理的异常。

Agent 循环工具

以下四个工具在注册表调度之前被拦截,因为它们需要 agent 级别的状态TodoStore、MemoryStore 等):

  • todo — 规划/任务跟踪
  • memory — 持久化 memory 写入
  • session_search — 跨会话召回
  • delegate_task — 生成子 agent 会话

这些工具的 schema 仍在注册表中注册(供 get_tool_definitions 使用),但若调度以某种方式直接到达它们,其 handler 会返回一个存根错误。

异步桥接

当工具 handler 为异步时,_run_async() 将其桥接到同步调度路径:

  • CLI 路径(无运行中的事件循环) — 使用持久化事件循环以保持缓存的异步客户端存活
  • Gateway 路径(有运行中的事件循环) — 使用 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 bomb、进程终止等
  2. 检测 — 在执行任何终端命令之前,detect_dangerous_command(command) 会对所有模式进行检查。

  3. 审批提示 — 若发现匹配:

    • CLI 模式 — 交互式提示要求用户批准、拒绝或永久允许
    • Gateway 模式 — 异步审批回调将请求发送至消息平台
    • 智能审批 — 可选地,辅助 LLM 可自动批准匹配模式但风险较低的命令(例如,rm -rf node_modules/ 是安全的,但匹配"递归删除"模式)
  4. 会话状态 — 审批按会话跟踪。一旦在某个会话中批准了"递归删除",后续的 rm -rf 命令不会再次提示。

  5. 永久允许列表 — "永久允许"选项会将该模式写入 config.yamlcommand_allowlist,跨会话持久化。

终端/运行时环境

终端系统支持多种后端:

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

还支持:

  • 按任务的 cwd 覆盖
  • 后台进程管理
  • PTY 模式
  • 危险命令的审批回调

并发

工具调用可以顺序执行,也可以并发执行,具体取决于工具组合和交互需求。

相关文档