Skip to main content

OpenTelemetry 可观测性插件 — 为 hermes-agent 生成多层 trace 树和丰富的 metrics

Project description

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)

Project details


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.

Supported by

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