Skip to main content

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.tomldependencies 字段声明了 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

预期输出:

  1. 8 个 hooks 注册成功
  2. 6 个 span(1 session + 1 turn + 2 API call + 2 tool)按 JSON 格式打印
  3. 7 个 metrics 按 JSON 格式打印
  4. 所有 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_tokensgen_ai.usage.output_tokens 都是 0。

可能原因

  • LLM 提供商没有返回 usage 信息
  • hermes-agent 的 post_api_request hook 没有传递 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

数据流:

  1. hermes-agent 通过 OTLP HTTP 协议将 traces 和 metrics 发送到 OTel Collector(端口 4318)
  2. OTel Collector 将 traces 转发到 Jaeger,将 metrics 暴露给 Prometheus
  3. 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

  1. 左侧 Service 下拉框选择 hermes-agent(或你设置的 OTEL_SERVICE_NAME
  2. 点击 Find Traces
  3. 你会看到类似这样的 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_.*"}

如果没有数据,检查:

  1. OTel Collector 日志:docker logs hermes-otel-collector
  2. Prometheus targets 页面:http://localhost:9090/targets(确认 otel-collector 状态为 UP)

第 6 步:在 Grafana 中创建 Dashboard

6.1 登录 Grafana

浏览器访问:http://localhost:3000

  • 用户名:admin
  • 密码:hermes123

6.2 验证数据源

  1. 左侧菜单 → ConnectionsData sources
  2. 确认 JaegerPrometheus 两个数据源都显示绿色 ✅

6.3 一键导入 Dashboard(推荐)

我们提供了预配置好的 Dashboard JSON 文件,包含所有推荐的 Panel,一键导入即可使用

  1. 登录 Grafana(admin / hermes123
  2. 左侧菜单 → DashboardsNewImport
  3. 点击 Upload dashboard JSON file,选择 plugins/otel_tracing/grafana-dashboard.json
  4. Prometheus 下拉框中选择 Prometheus
  5. 点击 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(可选)

如果你想自己从零创建,步骤如下:

  1. 左侧菜单 → DashboardsNewNew Dashboard
  2. 点击 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)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hermes_plugin_otel_tracing-0.1.1.tar.gz (33.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl (23.3 kB view details)

Uploaded Python 3

File details

Details for the file hermes_plugin_otel_tracing-0.1.1.tar.gz.

File metadata

File hashes

Hashes for hermes_plugin_otel_tracing-0.1.1.tar.gz
Algorithm Hash digest
SHA256 683c4b8df6cbd6d8e344ebd7ff84b382132c0c414a7b6f9a93c95f689f5e6fa4
MD5 820fc8ed38f05bdefbcf1935e48843c1
BLAKE2b-256 755f0ae97a7e41ab2dd613921b2e940b316dcaac4593eadf7d454281c5005c53

See more details on using hashes here.

File details

Details for the file hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for hermes_plugin_otel_tracing-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0404e84a4492d3a26a3d2e20250a707ed8a9f98dac7fcc7616c6f4383b7fbe45
MD5 ec9e9a698ac04dba6f92b92971e9f61f
BLAKE2b-256 d0a5e2214f15684519d4f4f2c3542007c791e9bd98aeaab6d449595cfb239be0

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page