hermes-agent OTel 可观测性插件使用手册
版本:0.1.0
日期:2026-04-15
适用范围:hermes-agent 用户 + 运维排查 + 可视化部署
目录
第一部分:用户使用指南
1. 插件简介
otel_tracing 是一个 hermes-agent 插件,通过 hermes 的 8 个 plugin hooks 自动生成 多层 Trace 树 和 丰富的 Metrics,将 hermes-agent 的运行状态导出到 OpenTelemetry 生态。
Span 层级结构
hermes.session ← 整个会话(根 span)
└── hermes.turn ← 一轮对话(用户发一条消息 → agent 回复)
├── chat deepseek-v3-0324 ← 第 1 次 LLM API 调用(返回 tool_calls)
├── hermes.tool.terminal ← 工具执行:terminal
├── hermes.tool.read_file ← 工具执行:read_file
└── chat deepseek-v3-0324 ← 第 2 次 LLM API 调用(返回最终文本)
AI SpanKind(hermes.span.kind 属性)
OTel 官方 SpanKind 枚举只有 5 种(INTERNAL / SERVER / CLIENT / PRODUCER / CONSUMER),不包含 AI 领域的语义。本插件参考 OpenInference 规范,通过自定义属性 hermes.span.kind 标识 AI 操作类型:
hermes.span.kind |
OTel SpanKind |
对应 Span | 说明 |
|---|---|---|---|
AGENT |
INTERNAL | hermes.session |
整个 agent 会话,代表一个 agent 执行 |
CHAIN |
INTERNAL | hermes.turn |
一轮对话,包含多步 LLM + Tool 调用链 |
LLM |
CLIENT | chat {model} |
单次 LLM API 调用(向远程模型发请求) |
TOOL |
INTERNAL | hermes.tool.{name} |
工具执行(agent 调用外部工具) |
EMBEDDING |
— | 预留 | 向量化操作(未来支持) |
RETRIEVER |
— | 预留 | RAG 检索操作(未来支持) |
RERANKER |
— | 预留 | 结果重排序操作(未来支持) |
为什么需要两层 Kind?
OTel 的SpanKind是协议级别的(描述 span 在分布式系统中的角色),而hermes.span.kind是业务级别的(描述 span 在 AI 工作流中的角色)。例如 LLM 调用同时是SpanKind.CLIENT(因为它向远程 API 发请求)和hermes.span.kind = LLM(因为它是一次大模型调用)。
Metrics 列表
| 指标名称 | 类型 | 单位 | 说明 |
|---|---|---|---|
gen_ai.client.operation.duration |
Histogram | 秒 | 每次 LLM API 调用的耗时 |
gen_ai.client.token.usage |
Histogram | token | 每次 LLM 调用的 token 用量(按 input/output 分类) |
hermes.tool.duration |
Histogram | 秒 | 每次工具执行的耗时 |
hermes.turn.duration |
Histogram | 秒 | 每轮对话的总耗时 |
hermes.turn.api_call_count |
Histogram | 次 | 每轮对话中 LLM API 的调用次数 |
hermes.turn.tool_call_count |
Histogram | 次 | 每轮对话中工具的调用次数 |
hermes.session.turn_count |
Counter | 次 | 每个 Session 的 turn 总数 |
2. 安装
有两种安装方式,推荐使用 方式一(pip 安装),自动处理所有依赖。
方式一:pip 安装(推荐)
通过 pip 安装,OTel 依赖会自动安装,hermes 启动时自动发现插件,无需手动复制文件:
# 从本地源码安装(开发阶段)
cd plugins/otel_tracing/
pip install -e .
# 或者发布到 PyPI 后直接安装(生产阶段)
pip install hermes-plugin-otel-tracing
安装完成后验证:
# 确认包已安装
pip list | grep hermes-plugin-otel-tracing
# 确认 OTel 依赖自动安装
pip list | grep opentelemetry
# 预期至少有:
# opentelemetry-api 1.x.x
# opentelemetry-sdk 1.x.x
# opentelemetry-exporter-otlp-proto-http 1.x.x
pip 安装原理详解
1. 包的源码目录结构
plugins/otel_tracing/
├── pyproject.toml ← pip 包的构建配置
├── src/
│ └── hermes_plugin_otel_tracing/ ← pip 包的源码(安装后的 Python 包名)
│ └── __init__.py ← 插件核心代码(包含 register 函数)
├── __init__.py ← 手动安装方式使用的同一份代码
├── plugin.yaml ← 手动安装方式的插件描述文件
└── ...
src/hermes_plugin_otel_tracing/ 是 pip 包的实际源码目录。执行 pip install 后,这个目录会被安装到 Python 的 site-packages/hermes_plugin_otel_tracing/ 下,成为一个可 import 的 Python 包。
2. pyproject.toml 中的 entry_points 声明
# pyproject.toml 关键配置
[project.entry-points."hermes_agent.plugins"]
otel_tracing = "hermes_plugin_otel_tracing"
这行配置的含义是:
- group =
hermes_agent.plugins— 这是 hermes 约定的插件 entry point 组名 - name =
otel_tracing— 插件名称(hermes 用这个名字标识和去重) - value =
hermes_plugin_otel_tracing— 指向的 Python 包名(即src/hermes_plugin_otel_tracing/)
pip 安装时,这个声明会被写入包的 metadata(dist-info/entry_points.txt),Python 的 importlib.metadata 模块可以在运行时读取到它。
3. hermes 启动时的插件发现流程
hermes 启动
│
▼
PluginManager.discover_and_load()
│
├── ① 扫描用户目录: ~/.hermes/plugins/ ← 手动安装的插件
├── ② 扫描项目目录: ./.hermes/plugins/ ← 项目级插件(需开启)
└── ③ 扫描 entry_points: ← pip 安装的插件 ✅
│
▼
importlib.metadata.entry_points()
.select(group="hermes_agent.plugins")
│
▼
找到 otel_tracing → hermes_plugin_otel_tracing
│
▼
ep.load() → import hermes_plugin_otel_tracing
│
▼
调用 hermes_plugin_otel_tracing.register(ctx)
│
▼
插件注册 8 个 hooks,OTel SDK 初始化完成 ✅
核心流程:hermes 的 PluginManager 通过 Python 标准库 importlib.metadata.entry_points() 扫描所有声明了 hermes_agent.plugins 组的 pip 包,找到后调用 ep.load() 导入模块,再调用模块的 register(ctx) 函数完成插件注册。
4. 依赖自动安装
pyproject.toml 的 dependencies 字段声明了 OTel 依赖:
dependencies = [
"opentelemetry-api >= 1.20.0",
"opentelemetry-sdk >= 1.20.0",
"opentelemetry-exporter-otlp-proto-http >= 1.20.0",
]
执行 pip install 时,pip 会自动安装这些依赖,用户无需手动安装 OTel 相关包。
总结:pip 安装方式的本质是利用 Python 的 entry_points 机制实现插件的自动发现。用户只需
pip install,hermes 启动时就能自动找到并加载插件,无需手动复制文件到任何目录。
如果需要 OTLP gRPC 导出器(可选):
pip install hermes-plugin-otel-tracing[grpc]
方式二:手动安装
如果不想通过 pip 安装,也可以手动复制插件目录并自行安装依赖:
# 1. 手动安装 OTel 依赖
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
# 2. 将插件目录复制到 hermes 的用户插件目录
cp -r otel_tracing ~/.hermes/plugins/otel_tracing
最终目录结构:
~/.hermes/plugins/otel_tracing/
├── plugin.yaml
├── __init__.py
└── test_plugin.py # 可选,仅用于测试
或者如果你是从源码运行 hermes-agent,直接放在源码的 plugins/ 目录下:
hermes-agent/
└── plugins/
└── otel_tracing/
├── plugin.yaml
├── __init__.py
└── test_plugin.py
两种安装方式对比
| 特性 | pip 安装(推荐) | 手动安装 |
|---|---|---|
| 自动安装 OTel 依赖 | ✅ | ❌ 需手动 pip install |
| hermes 自动发现 | ✅ entry_points 机制 | ✅ 目录扫描 |
| 版本管理 | ✅ pip show / pip upgrade |
❌ 手动管理 |
| 卸载 | pip uninstall hermes-plugin-otel-tracing |
手动删除目录 |
| 适用场景 | 生产环境、分发给用户 | 快速调试、临时使用 |
⚠️ 注意:两种方式不要同时使用。如果你已经通过 pip 安装了插件,就不需要再手动复制到
~/.hermes/plugins/。hermes 的PluginManager会通过插件名去重,不会重复加载,但为了避免混淆建议只保留一种方式。
hermes-agent 启动时会自动发现并加载该插件。
3. 配置
所有配置通过 环境变量 控制,无需修改任何代码:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
HERMES_OTEL_ENABLED |
true |
是否启用插件。设为 false 可完全禁用 |
HERMES_OTEL_EXPORTER |
console |
导出器类型:console(打印到终端)或 otlp(发送到 OTLP 端点) |
HERMES_OTEL_CAPTURE_CONTENT |
false |
是否捕获用户消息和工具参数/结果(注意隐私) |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4318 |
OTLP HTTP 端点地址 |
OTEL_SERVICE_NAME |
hermes-agent |
服务名称,会出现在 Jaeger/Grafana 中 |
3.1 快速开始:Console 模式
最简单的方式,直接在终端看到 span 和 metrics 输出:
# 不需要任何额外配置,默认就是 console 模式
hermes
启动后,每次对话都会在终端打印 JSON 格式的 span 和 metrics。
3.2 推荐:OTLP 模式(配合可视化)
export HERMES_OTEL_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_SERVICE_NAME=hermes-agent
hermes
3.3 调试模式:捕获消息内容
export HERMES_OTEL_CAPTURE_CONTENT=true
开启后,span 中会包含:
hermes.turn.user_message:用户消息(截断到 500 字符)hermes.tool.args:工具调用参数(截断到 2048 字符)hermes.tool.result:工具返回结果(截断到 2048 字符)
⚠️ 隐私警告:生产环境建议关闭此选项,避免敏感信息泄露到可观测性后端。
4. Span 属性详解
4.1 Session Span (hermes.session)
| 属性 | 类型 | 说明 |
|---|---|---|
hermes.session.id |
string | 会话唯一标识 |
gen_ai.request.model |
string | 使用的模型名称 |
hermes.platform |
string | 运行平台:cli / telegram / discord 等 |
gen_ai.system |
string | 固定值 hermes-agent |
hermes.session.completed |
bool | 会话是否正常完成 |
hermes.session.interrupted |
bool | 会话是否被中断 |
hermes.session.total_turns |
int | 会话中的 turn 总数 |
4.2 Turn Span (hermes.turn)
| 属性 | 类型 | 说明 |
|---|---|---|
hermes.session.id |
string | 所属会话 ID |
hermes.turn.index |
int | 第几轮对话(从 1 开始) |
hermes.is_first_turn |
bool | 是否是会话的第一轮 |
gen_ai.request.model |
string | 使用的模型 |
hermes.platform |
string | 运行平台 |
hermes.turn.api_call_count |
int | 本轮 API 调用次数 |
hermes.turn.tool_call_count |
int | 本轮工具调用次数 |
hermes.turn.duration_s |
float | 本轮总耗时(秒) |
hermes.turn.user_message |
string | 用户消息(仅 CAPTURE_CONTENT=true) |
4.3 API Call Span (chat {model})
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.operation.name |
string | 固定值 chat |
gen_ai.system |
string | 固定值 hermes-agent |
gen_ai.request.model |
string | 请求的模型名称 |
gen_ai.response.model |
string | 实际响应的模型名称 |
gen_ai.usage.input_tokens |
int | 输入 token 数 |
gen_ai.usage.output_tokens |
int | 输出 token 数 |
gen_ai.response.finish_reasons |
string[] | 结束原因:["stop"] 或 ["tool_calls"] |
hermes.provider |
string | 提供商:deepseek / openai / anthropic 等 |
hermes.api_call_number |
int | 本轮中第几次 API 调用 |
hermes.api_mode |
string | API 模式 |
hermes.message_count |
int | 发送的消息数量 |
hermes.tool_count |
int | 可用工具数量 |
hermes.approx_input_tokens |
int | 预估输入 token 数 |
hermes.cache.read_tokens |
int | 缓存读取 token 数 |
hermes.cache.write_tokens |
int | 缓存写入 token 数 |
hermes.reasoning_tokens |
int | 推理 token 数 |
hermes.assistant_content_chars |
int | 助手回复字符数 |
hermes.assistant_tool_call_count |
int | 助手发起的工具调用数 |
server.address |
string | API 服务器地址 |
4.4 Tool Span (hermes.tool.{name})
| 属性 | 类型 | 说明 |
|---|---|---|
hermes.tool.name |
string | 工具名称 |
hermes.session.id |
string | 所属会话 ID |
hermes.tool.args |
string | 工具参数 JSON(仅 CAPTURE_CONTENT=true) |
hermes.tool.result |
string | 工具结果(仅 CAPTURE_CONTENT=true) |
如果工具返回的 JSON 中包含
"error"字段,span 状态会自动设为ERROR。
5. 运行测试脚本
插件自带一个测试脚本,可以在不启动 hermes-agent 的情况下验证插件是否正常工作:
cd plugins/otel_tracing/
pip install opentelemetry-api opentelemetry-sdk
python test_plugin.py
预期输出:
- 8 个 hooks 注册成功
- 6 个 span(1 session + 1 turn + 2 API call + 2 tool)按 JSON 格式打印
- 7 个 metrics 按 JSON 格式打印
- 所有 span 共享同一个
trace_id,父子关系正确
第二部分:故障排查手册
🔧 本部分面向运维/开发人员,用于排查插件不工作或数据异常的问题。
1. 快速诊断流程图
flowchart TD
A[插件不工作?] --> B{hermes 启动日志中<br>有 otel_tracing 字样吗?}
B -->|没有| C[插件未被加载]
B -->|有| D{日志中有 ✅ 还是 ❌?}
C --> C1[检查插件目录位置]
C1 --> C2[检查 plugin.yaml 格式]
C2 --> C3[检查 __init__.py 有无语法错误]
D -->|❌ OTel 初始化失败| E[检查 OTel SDK 依赖]
D -->|✅ 注册完成| F{有 span 输出吗?}
E --> E1[pip install opentelemetry-api opentelemetry-sdk]
E1 --> E2[如果用 OTLP: pip install opentelemetry-exporter-otlp-proto-http]
F -->|Console 模式有输出| G{OTLP 模式没数据?}
F -->|完全没输出| H[检查 HERMES_OTEL_ENABLED]
G --> G1[检查 OTLP endpoint 是否可达]
G1 --> G2[检查 Jaeger/Collector 是否在运行]
G2 --> G3[检查防火墙/网络]
H --> H1[确认环境变量 HERMES_OTEL_ENABLED=true]
H1 --> H2[确认 hermes 实际触发了 hooks]
2. 常见问题及解决方案
问题 1:插件未被加载
症状:hermes 启动日志中完全没有 [otel_tracing] 字样。
排查步骤:
# 1. 确认插件目录位置正确
ls -la ~/.hermes/plugins/otel_tracing/
# 或源码模式
ls -la plugins/otel_tracing/
# 2. 确认 plugin.yaml 存在且格式正确
cat plugins/otel_tracing/plugin.yaml
# 3. 确认 __init__.py 无语法错误
python -c "import plugins.otel_tracing"
# 4. 确认 register 函数存在
python -c "from plugins.otel_tracing import register; print('OK')"
问题 2:OTel SDK 初始化失败
症状:日志中出现 [otel_tracing] ❌ OTel 初始化失败。
排查步骤:
# 1. 检查 OTel SDK 是否安装
pip list | grep opentelemetry
# 预期至少有:
# opentelemetry-api 1.x.x
# opentelemetry-sdk 1.x.x
# 2. 如果缺少,安装:
pip install opentelemetry-api opentelemetry-sdk
# 3. 如果用 OTLP 模式,还需要:
pip install opentelemetry-exporter-otlp-proto-http
# 4. 检查版本兼容性(api 和 sdk 版本必须匹配)
python -c "
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
print('TracerProvider OK')
from opentelemetry.sdk.metrics import MeterProvider
print('MeterProvider OK')
"
问题 3:Console 模式有输出,OTLP 模式没数据
症状:HERMES_OTEL_EXPORTER=console 时能看到 span,切换到 otlp 后 Jaeger 中看不到数据。
排查步骤:
# 1. 确认环境变量设置正确
echo $HERMES_OTEL_EXPORTER # 应该是 otlp
echo $OTEL_EXPORTER_OTLP_ENDPOINT # 应该是 http://localhost:4318
# 2. 确认 OTLP endpoint 可达
curl -v http://localhost:4318/v1/traces
# 应该返回 HTTP 响应(即使是 405 也说明端口通了)
# 3. 确认 OTLP exporter 已安装
python -c "from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter; print('OK')"
# 4. 检查 Jaeger/Collector 容器是否在运行
docker ps | grep -E "jaeger|otel-collector"
# 5. 检查 Jaeger 容器日志
docker logs jaeger 2>&1 | tail -20
问题 4:Span 存在但父子关系断裂
症状:Jaeger 中能看到 span,但它们不在同一个 trace 中,或者没有父子关系。
可能原因:
on_session_start没有被触发(session span 缺失)pre_llm_call没有被触发(turn span 缺失)- hermes-agent 版本过旧,不支持某些 hooks
排查步骤:
# 1. 开启 debug 日志查看 hook 触发情况
export PYTHONPATH=plugins/otel_tracing
python -c "
import logging
logging.basicConfig(level=logging.DEBUG)
# 然后启动 hermes
"
# 2. 检查日志中是否有完整的 hook 触发序列:
# ▶ Session span 开始
# ▶ Turn #1 span 开始
# ▶ API Call #1 span 开始
# ■ API Call #1 span 结束
# ■ Turn span 结束
# ■ Session span 结束
问题 5:Metrics 不显示
症状:Span 正常,但 Prometheus/Grafana 中看不到 metrics。
排查步骤:
# 1. Console 模式下确认 metrics 有输出
export HERMES_OTEL_EXPORTER=console
# 启动 hermes 并发送一条消息,等待 5 秒后应看到 metrics JSON
# 2. OTLP 模式下检查 metrics endpoint
curl -v http://localhost:4318/v1/metrics
# 3. 检查 Prometheus 是否配置了正确的 scrape target
# 注意:本插件通过 OTLP push 模式发送 metrics,不是 Prometheus pull
# 需要 OTel Collector 作为中间层转换
# 4. 检查 metrics 导出间隔
# 默认 OTLP 模式下 60 秒导出一次,Console 模式下 5 秒
# 如果刚启动就关闭,可能来不及导出
问题 6:Token 数据为 0
症状:Span 中 gen_ai.usage.input_tokens 和 gen_ai.usage.output_tokens 都是 0。
可能原因:
- LLM 提供商没有返回 usage 信息
- hermes-agent 的
post_api_requesthook 没有传递usage参数
排查步骤:
# 1. 检查 hermes-agent 版本是否支持 post_api_request 的 usage 参数
# 需要 hermes-agent 较新版本
# 2. 在 __init__.py 中临时添加日志
# 在 _on_post_api_request 函数开头加:
# logger.warning("post_api_request usage=%s", usage)
# 查看 usage 是否为 None
3. 日志级别控制
# 查看插件详细日志
export HERMES_LOG_LEVEL=DEBUG
# 或在 Python 中:
import logging
logging.getLogger("plugins.otel_tracing").setLevel(logging.DEBUG)
日志前缀说明:
| 前缀 | 含义 |
|---|---|
▶ |
Span 开始 |
■ |
Span 结束 |
✅ |
操作成功 |
❌ |
操作失败 |
4. 环境变量速查表
# 完整的环境变量配置示例
export HERMES_OTEL_ENABLED=true
export HERMES_OTEL_EXPORTER=otlp
export HERMES_OTEL_CAPTURE_CONTENT=false
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_SERVICE_NAME=hermes-agent
第三部分:可视化方案(Jaeger + Prometheus + Grafana)
🎯 目标:用 Docker 一键启动 Jaeger(看 Traces)+ Prometheus(存 Metrics)+ Grafana(看 Dashboard),确保能在可视化界面看到 hermes-agent 的数据。
架构图
graph LR
H[hermes-agent<br>otel_tracing 插件] -->|OTLP HTTP<br>:4318| C[OTel Collector<br>:4318]
C -->|Jaeger proto| J[Jaeger<br>:16686 UI]
C -->|Prometheus remote write| P[Prometheus<br>:9090]
P --> G[Grafana<br>:3000]
J --> G
style H fill:#4CAF50,color:white
style C fill:#FF9800,color:white
style J fill:#2196F3,color:white
style P fill:#E91E63,color:white
style G fill:#9C27B0,color:white
数据流:
- hermes-agent 通过 OTLP HTTP 协议将 traces 和 metrics 发送到 OTel Collector(端口 4318)
- OTel Collector 将 traces 转发到 Jaeger,将 metrics 暴露给 Prometheus
- Grafana 同时连接 Jaeger(查看 traces)和 Prometheus(查看 metrics dashboard)
第 1 步:创建配置文件
在 plugins/otel_tracing/ 目录下创建以下文件:
1.1 OTel Collector 配置
创建 otel-collector-config.yaml:
receivers:
otlp:
protocols:
http:
endpoint: "0.0.0.0:4318"
exporters:
# Traces → Jaeger
otlp/jaeger:
endpoint: "jaeger:4317"
tls:
insecure: true
# Metrics → Prometheus (Collector 自己暴露 /metrics 端点)
prometheus:
endpoint: "0.0.0.0:8889"
namespace: "hermes"
# 调试用:同时打印到日志
debug:
verbosity: basic
processors:
batch:
timeout: 5s
send_batch_size: 1024
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger, debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus, debug]
1.2 Prometheus 配置
创建 prometheus.yaml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
# 从 OTel Collector 的 prometheus exporter 拉取 metrics
- job_name: "otel-collector"
static_configs:
- targets: ["otel-collector:8889"]
labels:
source: "hermes-agent"
1.3 Grafana 数据源预配置
创建 grafana-datasources.yaml:
apiVersion: 1
datasources:
- name: Jaeger
type: jaeger
access: proxy
url: http://jaeger:16686
isDefault: false
editable: true
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
1.4 Docker Compose 文件
创建 docker-compose.yaml:
version: "3.9"
services:
# ── Jaeger:Trace 可视化 ──
jaeger:
image: jaegertracing/all-in-one:1.62
container_name: hermes-jaeger
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC (Collector → Jaeger)
environment:
- COLLECTOR_OTLP_ENABLED=true
restart: unless-stopped
# ── OTel Collector:数据中转 ──
otel-collector:
image: otel/opentelemetry-collector-contrib:0.114.0
container_name: hermes-otel-collector
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
ports:
- "4318:4318" # OTLP HTTP (hermes-agent → Collector)
- "8889:8889" # Prometheus metrics endpoint
depends_on:
- jaeger
restart: unless-stopped
# ── Prometheus:Metrics 存储 ──
prometheus:
image: prom/prometheus:v2.54.1
container_name: hermes-prometheus
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
ports:
- "9090:9090" # Prometheus UI
depends_on:
- otel-collector
restart: unless-stopped
# ── Grafana:统一 Dashboard ──
grafana:
image: grafana/grafana:11.3.0
container_name: hermes-grafana
volumes:
- ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro
- grafana-data:/var/lib/grafana
ports:
- "3000:3000" # Grafana UI
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=hermes123
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
depends_on:
- prometheus
- jaeger
restart: unless-stopped
volumes:
prometheus-data:
grafana-data:
第 2 步:一键启动
# 进入插件目录
cd plugins/otel_tracing/
# 启动所有服务(后台运行)
docker-compose up -d
# 查看启动状态
docker-compose ps
预期输出:
NAME STATUS
hermes-jaeger running 0.0.0.0:16686->16686/tcp
hermes-otel-collector running 0.0.0.0:4318->4318/tcp
hermes-prometheus running 0.0.0.0:9090->9090/tcp
hermes-grafana running 0.0.0.0:3000->3000/tcp
等待约 10 秒让所有服务完全启动。
第 3 步:配置 hermes-agent 并发送数据
# 设置环境变量
export HERMES_OTEL_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OT_SERVICE_NAME=hermes-agent
# 启动 hermes-agent
hermes
或者先用测试脚本验证数据链路:
# 修改测试脚本的 exporter 为 otlp
HERMES_OTEL_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OT_SERVICE_NAME=hermes-agent-test \
python test_plugin.py
⚠️ 注意:测试脚本默认使用 console exporter。要发送到 OTLP,需要通过环境变量覆盖。
第 4 步:在 Jaeger 中查看 Traces
4.1 打开 Jaeger UI
浏览器访问:http://localhost:16686
4.2 查找 Traces
- 左侧 Service 下拉框选择
hermes-agent(或你设置的OTEL_SERVICE_NAME) - 点击 Find Traces
- 你会看到类似这样的 trace 列表:
hermes.session | 6 spans | 2.1s
4.3 查看 Trace 详情
点击某个 trace,你会看到完整的多层 span 树:
▼ hermes.session 2.1s
▼ hermes.turn 2.0s
▼ chat deepseek-v3-0324 0.85s finish_reason=tool_calls
▼ hermes.tool.terminal 0.02s
▼ hermes.tool.read_file 0.01s
▼ chat deepseek-v3-0324 1.2s finish_reason=stop
点击任意 span 可以查看详细属性:
- API Call span:token 用量、cache 命中、推理 token、provider 等
- Tool span:工具名称、执行耗时、是否出错
- Turn span:本轮 API 调用次数、工具调用次数、总耗时
- Session span:总 turn 数、是否正常完成
4.4 Jaeger 实用功能
| 功能 | 操作 |
|---|---|
| 按耗时排序 | 点击 "Longest First" |
| 按 span 数量过滤 | 设置 "Min/Max Spans" |
| 搜索特定 session | Tags 中输入 hermes.session.id=xxx |
| 查看错误 span | Tags 中输入 error=true |
| 对比两个 trace | 选中两个 trace 后点击 "Compare" |
第 5 步:在 Prometheus 中查看 Metrics
5.1 打开 Prometheus UI
浏览器访问:http://localhost:9090
5.2 常用查询(PromQL)
在查询框中输入以下 PromQL:
# LLM API 调用平均耗时(按模型分组)
rate(hermes_gen_ai_client_operation_duration_seconds_sum[5m])
/ rate(hermes_gen_ai_client_operation_duration_seconds_count[5m])
# Token 总用量(按 input/output 分组)
sum by (gen_ai_token_type) (
rate(hermes_gen_ai_client_token_usage_sum[5m])
)
# 工具执行平均耗时(按工具名分组)
rate(hermes_hermes_tool_duration_seconds_sum[5m])
/ rate(hermes_hermes_tool_duration_seconds_count[5m])
# 每轮对话的平均 API 调用次数
rate(hermes_hermes_turn_api_call_count_sum[5m])
/ rate(hermes_hermes_turn_api_call_count_count[5m])
# Session 总 turn 数
hermes_hermes_session_turn_count_total
📝 注意:因为 OTel Collector 的 prometheus exporter 配置了
namespace: "hermes",所有指标名称会加上hermes_前缀。
5.3 验证数据到达
# 查看所有 hermes 相关的指标
{__name__=~"hermes_.*"}
如果没有数据,检查:
- OTel Collector 日志:
docker logs hermes-otel-collector - Prometheus targets 页面:http://localhost:9090/targets(确认
otel-collector状态为 UP)
第 6 步:在 Grafana 中创建 Dashboard
6.1 登录 Grafana
浏览器访问:http://localhost:3000
- 用户名:
admin - 密码:
hermes123
6.2 验证数据源
- 左侧菜单 → Connections → Data sources
- 确认
Jaeger和Prometheus两个数据源都显示绿色 ✅
6.3 一键导入 Dashboard(推荐)
我们提供了预配置好的 Dashboard JSON 文件,包含所有推荐的 Panel,一键导入即可使用:
- 登录 Grafana(
admin/hermes123) - 左侧菜单 → Dashboards → New → Import
- 点击 Upload dashboard JSON file,选择
plugins/otel_tracing/grafana-dashboard.json - 在 Prometheus 下拉框中选择
Prometheus - 点击 Import
或者通过命令行导入:
# 通过 Grafana API 导入 Dashboard
curl -X POST http://admin:hermes123@localhost:3000/api/dashboards/import \
-H "Content-Type: application/json" \
-d "{
\"dashboard\": $(cat plugins/otel_tracing/grafana-dashboard.json),
\"overwrite\": true,
\"inputs\": [
{\"name\": \"DS_PROMETHEUS\", \"type\": \"datasource\", \"pluginId\": \"prometheus\", \"value\": \"Prometheus\"}
]
}"
导入后的 Dashboard 包含以下 Panel:
| 区域 | Panel | 类型 | 说明 |
|---|---|---|---|
| 📊 概览 | 总 Turn 数 | Stat | Session 中的 Turn 总数 |
| 📊 概览 | 每轮对话统计 | Stat | 平均 API / 工具调用次数 |
| 📊 概览 | Turn 平均耗时 | Stat | 每轮对话的平均总耗时 |
| 📊 概览 | API 平均延迟 | Stat | LLM API 调用的平均耗时 |
| ⏱️ LLM API 性能 | 延迟分布 | 时序图 | P50 / P95 / P99 延迟 |
| ⏱️ LLM API 性能 | 按模型耗时 | 时序图 | 各模型的平均 API 耗时 |
| 🪙 Token 用量 | Input vs Output | 柱状图 | Token 用量速率(堆叠) |
| 🪙 Token 用量 | 每次调用平均 Token | 时序图 | 单次 API 调用的平均 Token |
| 🔧 工具执行 | 工具平均耗时 | 时序图 | 各工具的平均执行耗时 |
| 🔧 工具执行 | 工具调用频率 | 柱状图 | 各工具的调用频率 |
| 💬 对话统计 | Turn 耗时分布 | 时序图 | Turn P50 / P95 / P99 |
| 💬 对话统计 | 调用次数趋势 | 时序图 | 每轮 API / 工具调用次数 |
| 🔍 Traces | Jaeger UI 链接 | Markdown | 点击链接跳转到 Jaeger UI 查看 Traces |
💡 Dashboard 默认每 30 秒自动刷新,时间范围为最近 1 小时。
6.4 手动创建 Dashboard(可选)
如果你想自己从零创建,步骤如下:
- 左侧菜单 → Dashboards → New → New Dashboard
- 点击 Add visualization
6.5 推荐的 Panel 配置
Panel 1:LLM API 调用耗时(时序图)
- 数据源:Prometheus
- 查询:
histogram_quantile(0.95, rate(hermes_gen_ai_client_operation_duration_seconds_bucket[5m]) )
- 标题:LLM API P95 延迟
- 单位:seconds (s)
Panel 2:Token 用量(柱状图)
- 数据源:Prometheus
- 查询 A(Input):
sum(rate(hermes_gen_ai_client_token_usage_sum{gen_ai_token_type="input"}[5m]))
- 查询 B(Output):
sum(rate(hermes_gen_ai_client_token_usage_sum{gen_ai_token_type="output"}[5m]))
- 标题:Token 用量速率
- 单位:tokens/s
Panel 3:工具执行耗时(表格)
- 数据源:Prometheus
- 查询:
rate(hermes_hermes_tool_duration_seconds_sum[5m]) / rate(hermes_hermes_tool_duration_seconds_count[5m])
- 标题:工具平均执行耗时
- 单位:seconds (s)
Panel 4:Trace 查看器
- 数据源:Jaeger
- 查询类型:Search
- Service:hermes-agent
- 标题:最近的 Traces
Panel 5:每轮对话统计(Stat)
- 数据源:Prometheus
- 查询 A:
avg(rate(hermes_hermes_turn_api_call_count_sum[5m]) / rate(hermes_hermes_turn_api_call_count_count[5m]))
Legend: 平均 API 调用次数 - 查询 B:
avg(rate(hermes_hermes_turn_tool_call_count_sum[5m]) / rate(hermes_hermes_turn_tool_call_count_count[5m]))
Legend: 平均工具调用次数
Panel 6:Session Turn 总数(Counter)
- 数据源:Prometheus
- 查询:
sum(hermes_hermes_session_turn_count_total)
- 标题:总 Turn 数
6.6 保存 Dashboard
点击右上角 💾 保存按钮,命名为 Hermes Agent Observability。
💡 如果你使用了 6.3 的一键导入方式,Dashboard 已经自动保存好了,无需此步骤。
第 7 步:端到端验证清单
按以下步骤逐一验证,确保整条数据链路通畅:
# ✅ 1. 确认所有容器运行正常
docker-compose ps
# 所有 4 个容器状态应为 running
# ✅ 2. 确认 OTel Collector 接收端口可达
curl -s -o /dev/null -w "%{http_code}" http://localhost:4318/v1/traces
# 应返回 200 或 405
# ✅ 3. 确认 Prometheus 能抓到 OTel Collector
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -A2 "otel-collector"
# 应显示 "health": "up"
# ✅ 4. 发送测试数据
cd plugins/otel_tracing/
HERMES_OTEL_EXPORTER=otlp python test_plugin.py
# ✅ 5. 在 Jaeger 中查看
open http://localhost:16686
# Service 选 hermes-agent-plugin-test → Find Traces → 应看到 1 个 trace,6 个 span
# ✅ 6. 在 Prometheus 中查看
open http://localhost:9090
# 查询 {__name__=~"hermes_.*"} → 应看到 7 个指标
# ✅ 7. 在 Grafana 中查看
open http://localhost:3000
# 登录 admin/hermes123 → Data sources → 两个数据源都应为绿色
第 8 步:停止和清理
# 停止所有服务(保留数据)
docker-compose stop
# 停止并删除容器(保留数据卷)
docker-compose down
# 停止并删除所有数据(完全清理)
docker-compose down -v
附录:端口速查表
| 服务 | 端口 | 用途 |
|---|---|---|
| OTel Collector | 4318 | OTLP HTTP 接收端(hermes-agent → Collector) |
| OTel Collector | 8889 | Prometheus metrics 暴露端 |
| Jaeger | 16686 | Jaeger Web UI |
| Jaeger | 4317 | OTLP gRPC(Collector → Jaeger) |
| Prometheus | 9090 | Prometheus Web UI + API |
| Grafana | 3000 | Grafana Web UI |
附录:完整环境变量配置
# ── hermes-agent OTel 插件配置 ──
export HERMES_OTEL_ENABLED=true
export HERMES_OTEL_EXPORTER=otlp
export OTEL_RESOURCE_ATTRIBUTES="token=fspbWnBJxURffqeNdNJO,host.name=vllm-test"
export HERMES_OTEL_CAPTURE_CONTENT=false # 生产环境建议 false
#export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_EXPORTER_OTLP_ENDPOINT=http://30.171.27.189:8080
export OTEL_SERVICE_NAME=hermes-agent
# ── 启动可视化后端 ──
cd plugins/otel_tracing/
docker-compose up -d
# ── 启动 hermes-agent ──
hermes
# ── 访问可视化界面 ──
# Jaeger: http://localhost:16686
# Prometheus: http://localhost:9090
# Grafana: http://localhost:3000 (admin / hermes123)
Metadata
Release files for hermes-plugin-otel-tracing 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hermes_plugin_otel_tracing-0.1.1.tar.gz | 33.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.2 kB
Release files / hermes_plugin_otel_tracing-0.1.1.tar.gz
| Download URL | hermes_plugin_otel_tracing-0.1.1.tar.gz |
|---|---|
| Size | 33.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
683c4b8df6cbd6d8e344ebd7ff84b382132c0c414a7b6f9a93c95f689f5e6fa4
|
|
BLAKE2b-256 checksum How to use checksums |
755f0ae97a7e41ab2dd613921b2e940b316dcaac4593eadf7d454281c5005c53
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.2
|
Release files / hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl
| Download URL | hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0404e84a4492d3a26a3d2e20250a707ed8a9f98dac7fcc7616c6f4383b7fbe45
|
|
BLAKE2b-256 checksum How to use checksums |
d0a5e2214f15684519d4f4f2c3542007c791e9bd98aeaab6d449595cfb239be0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.2
|