Skip to main content

第10章:测试与工程化 —— 用组合测试托住复杂 Agent

一、测试规模告诉我们项目的真正难点​

Hermes 快照包含约 2,625 个 Python 测试文件,覆盖 tests/agent、tests/gateway、tests/acp、tests/cli、tests/tools、tests/cron、tests/security 和大量 provider/platform 专项。测试分布本身就是架构地图:

provider × model × credential × tool × platform
× context × concurrency × operating system

Agent 的 bug 很少是“函数返回值错了”这么单纯,更多是“某个边界组合后,状态顺序错了”。

二、测试按边界而不是按文件组织​

从文件名可以读出几类契约:

  • test_context_compressor_*:压缩边界、来源、游标、回退和抗抖;
  • test_file_safety_*:路径、凭证、容器 mirror 和跨 profile 隔离;
  • test_credential_pool_*:路由、轮换、OAuth 写回和 provider boundary;
  • test_gateway_*:stream、turn lease、session sidecar、delivery;
  • test_acp_*:auth、approval、session、provenance、MCP;
  • test_memory_*:异步/同步、session switch、write bridge 和 provider;
  • test_moa_*、test_auxiliary_*:辅助模型、并发和上下文预算。

最值得学习的是测试命名方式:它们把 bug 的业务概念写进文件名,让回归测试成为设计文档的一部分。

三、项目元数据也是安全设计​

pyproject.toml 的依赖被分成 core 和 optional extras:provider-specific、搜索、图像、TTS、voice、wake、memory、messaging、MCP 等能力按需安装;默认依赖使用精确版本,lockfile 再固定传递依赖。

这不是纯粹的发布偏好:

  • 核心安装更小,供应链暴露面更少;
  • 不使用的 provider 不会在 import 阶段破坏启动;
  • lazy dependency 能在能力真正启用时安装;
  • 测试可以验证 extra 与 lazy dependency 的一致性。

四、启动和更新的自愈策略​

hermes_cli/main.py 的早期 bootstrap/recovery、hermes_bootstrap.py、_early_recovery.py 和 update/install 逻辑都说明 Hermes 把“升级中断”当成正常故障模式处理。

当 git pull 或依赖安装只完成一半时,入口仍要尽量做到:

  1. 先用 stdlib-only 模块确认恢复标记;
  2. 修复 venv 或可恢复的缺失模块;
  3. 再导入重依赖;
  4. 将无法恢复的原因呈现给 doctor/用户。

这是长期运行工具很重要的原则:更新路径本身也要具备降级路径。

五、跨平台不是 if/else 清单​

项目同时支持 Linux、macOS、Windows、WSL2、Termux,并且命令可能在 local/Docker/SSH/cloud sandbox 执行。跨平台逻辑被分布在:

  • hermes_cli/_subprocess_compat.py、windows_ssh_runtime.py;
  • tools/environments/*;
  • tools/path_security.py、file_safety.py;
  • Docker/Nix/Termux 配置;
  • gateway/platforms/* 和声媒介适配器。

关键不是“每个函数都判断系统”,而是把平台差异压到 environment、process、filesystem、transport 和 adapter 契约。

六、观测:既看得到问题,又不泄露秘密​

hermes_logging.py、agent/monitoring/、gateway/monitoring/、trace_upload.py、relay metrics 和 session insights 共同提供运行观测。可观测性需要回答:

  • 现在卡在模型、工具、网络还是投递?
  • 这个 turn 消耗了多少 token/时间/费用?
  • 哪个 credential/profile/backend 失败?
  • 是否发生压缩、重试、failover 或权限阻断?

但日志不能直接复制原始 prompt、API key、用户 memory 或 tool arguments。redact.py、message sanitization、provenance 和 bounded projection 是必要的安全边界。

七、给新增功能的工程检查表​

新增一个能力时,至少问:

  1. 它属于哪一个生命周期阶段?
  2. 它拥有哪种状态,状态存在哪里?
  3. 它是否会产生外部副作用?副作用前是否有授权/审计/回滚?
  4. 它进入 prompt、transcript、日志还是 sidecar?
  5. 它如何在 CLI、Gateway、ACP 和 Cron 中复用或明确不复用?
  6. 失败时是 retry、fallback、degrade 还是 user-visible error?
  7. session 重启、进程终止、线程复用和跨平台会发生什么?
  8. 是否添加了对应边界命名的测试?

八、最终结论:Hermes 的工程主题​

Hermes Agent 的复杂度不是偶然堆出来的;它来自一个明确目标:让 Agent 变成可以长期运行、跨入口、可回滚、可观察、可学习的系统。

十章合起来可以压缩成一句话:

用统一的 Agent Loop 产生行动,用工具/环境边界控制副作用,用 Context Engine 控制有限注意力,用 Session/Gateway 保证长期连续性,再用 Memory/Skills/Cron/Delegation 把一次性对话扩展成可复用工作系统。