Agent 昨晚到底干了什么?给 Hermes 接上 OpenTelemetry


早上打开电脑,你发现昨晚那个“就改一下配置”的任务,账单比平时多了三十块,而 /status 只告诉你一共用了多少 token。你真想知道的是:哪一次模型调用慢、哪一个工具重试了三遍、哪一次审批卡了两分钟等人点确认。Hermes 本身不提供这种逐次调用的时间线,而社区里的 hermes-otel 插件正好补上这一块——它给 Hermes 接上 OpenTelemetry,把每个 turn 变成一条可以逐层展开的 trace。

Hermes 自带的两层遥测,覆盖不到“这一次调用”

Hermes 内置了两套可观测面:一层是 gateway monitoring(monitoring.export.otlp),关注网关和 cron 的健康状态,设计上不含内容——不记 prompt、不记工具调用、不记 token;另一层是随仓库附带的 Langfuse 插件,按 turn 记录 trace,但只能发到 Langfuse。

hermes-otel 是第三层,按 run 记录,并且可以和前两层同时开:它的探针挂在宿主的生命周期钩子上,自己安装独立的 TracerProvider,不碰内置导出器用的私有 provider。两者的数据互不覆盖。

安装:一条命令,重启一次网关

插件已在官方插件目录里(community 档,目录里钉的版本是 1.18.2):

hermes plugins install hermes_otel
hermes plugins enable hermes_otel

Hermes 0.21 及以后会自动把这个插件依赖的三个 opentelemetry-* 包装进它自己的虚拟环境,并在每次 hermes update 后重新应用。装完重启正在跑的网关才会生效。

如果你的 Hermes 低于 0.21、或者安装时用了 --no-deps,就自己补依赖:

<hermes venv>/bin/pip install -r ~/.hermes/plugins/hermes_otel/requirements.txt

一个 turn 展开是什么样

插件把宿主触发的事件映射成 span,结构大致是这样:

agent / cron                       <- 整个 turn 的根 span
├── skill.{name}                   <- 加载过的技能,从加载点到 turn 结束
└── llm.{model}                    <- 一次 run_conversation
    ├── api.{model}                <- 一次 HTTP 往返,带 token 与结束原因
    │   ├── tool.{name}            <- 每次工具调用:参数、结果、结局
    │   └── approval.{pattern}     <- 等人或等审阅模型的审批
    └── subagent.{role}            <- delegate_task,子代理自己的 trace 嵌在这里

api.* span 会带一个 hermes.query_source 属性,用来区分这次请求是主循环发的(main)、委派子代理发的(subagent),还是 Hermes 替 agent 发的辅助请求(auxiliary,例如起标题、压缩上下文、MoA 汇总),后者还会标出具体任务。工具 span 带 hermes.tool.outcome:completed / error / timeout / blocked / cancelled。属性同时按 OpenInference 和 OTel GenAI 两套命名输出,所以接 Phoenix 或 Langfuse 都不需要自己写映射。

配置:一个 YAML,或者一个环境变量

只接一个后端时,连配置文件都不用写,例如:

export OTEL_PHOENIX_ENDPOINT=http://localhost:6006/v1/traces

要接多个后端,就写 ~/.hermes/hermes_otel.yaml:

project_name: hermes-agent
backends:
  - type: phoenix
    endpoint: http://localhost:6006/v1/traces      # 只收 traces
  - type: lgtm
    endpoint: http://localhost:4318/v1/traces      # traces + metrics + logs
    metrics: true
  - type: honeycomb
    api_key: ${HONEYCOMB_API_KEY}                  # ${VAR} 在加载时展开
capture_logs: true

可以同时喂多个后端,每个后端有自己的导出队列。仓库的 docker-compose/ 里给每个可自托管的后端都备了一套现成编排,本地起一个 Phoenix 或 Jaeger 试水是最省事的起点。

隐私:默认全量,记得改成 preview

这是必须先看的一段。默认 content_capture: full,会把每次模型调用的完整 prompt 和响应、以及裁到 1200 字符的工具参数与输出发给你配置的后端。三个可选值:

值 行为
full 完整 prompt 与响应,工具内容裁到 1200 字符
preview 只留裁剪后的预览
off 完全不带内容,只保留结构

一个后端都没配时,插件只在 $HERMES_HOME 下留一个本地 SQLite 库给面板用,不发送任何数据。多 profile 的场景要留意:每个 profile 有自己的安装、设置和遥测,多路复用网关会按各自的名字分别上报。

不离开聊天就能查

插件自带一个技能 hermes_otel:observability,带一个终端查询脚本。装好之后直接在对话里问“我上一个 turn 为什么慢”、“这个会话花了多少钱”,它会去查本地实时库(不需要任何后端):

python3 ~/.hermes/plugins/hermes_otel/skills/observability/scripts/otel.py trace last

输出就是一封可读的瀑布图,按 span 列出耗时、token、缓存命中和结束原因。子命令还有 status、traces、span、sessions、stats、metrics、logs 和只读的 sql,都支持 --json、--since 和 --source。

什么时候不必上它

  • 只想知道网关活着没有:内置的 gateway monitoring 更轻,而且设计上不碰内容。
  • 只在用 Langfuse:内置的 Langfuse 插件已经够用。
  • 机器本身很小、又跑着大量短任务:每条 turn 都建 span 有成本,先用 preview 加采样观察一周再决定。

另外两点现实限制:MCP 服务器的 span 要并进 agent 的 trace,插件这边已经写好,但还在等宿主支持;插件本身的故障是“失败开放”的(导出队列不阻塞 agent),所以别把它当审计日志用——真要留证据,用本地库或自己的后端落盘。

如果你还在挑插件,先看一遍插件目录的使用方式,hermes plugins search 能直接告诉你某个插件钉在哪个 commit 上。想在自己的机器上把这些 span 顺手看成一个网页工作台,Hermes Workspace 也能一起用;命令细节见hermes plugins 命令参考。