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 命令参考。