第 17 期 | 日志系统与可观测性实践
好的,作为技术教育专家,我将为你撰写 Hermes Agent 教程的第 17 期文章。
日志系统与可观测性实践
副标题:揭秘暗箱——对话轨迹回放、技能追踪和性能分析。集成专业可观测平台提升可控度。
学习目标
在本期课程中,你将深入 Hermes Agent 的内部运作,学习如何从一个“黑箱”使用者转变为一个能洞察其内部状态的“白箱”开发者。完成本章后,你将能够:
- 掌握 Hermes Agent 的内置日志系统:理解其日志结构、级别和配置,并利用日志进行基础的故障排查。
- 实现对话轨迹回放与技能追踪:通过精细化日志分析,精确复现 Agent 的每一次决策路径,诊断问题根源。
- 进行基础性能分析:从日志中提取关键性能指标(Metrics),如响应延迟、Token 消耗等,量化 Agent 的表现。
- 集成专业可观测性平台:学习如何将 Hermes Agent 与 OpenTelemetry (OTel) 生态集成,实现分布式追踪 (Tracing),将可观测性提升到工业级水平。
核心概念讲解
在复杂的 AI Agent 应用中,当用户报告“它没有按我说的做”或“它反应很慢”时,我们面临的最大挑战是不可见性。Agent 的决策链条(接收信息 -> 检索记忆 -> 思考 -> 选择技能 -> 调用 LLM -> 执行技能 -> 生成回复)隐藏在代码执行的背后。可观测性 (Observability) 正是解决这一问题的关键。它不仅仅是“监控 (Monitoring)”,监控告诉我们系统是否在工作,而可观测性则让我们能提出任意问题,并从系统产生的数据中找到为什么的答案。
可观测性通常建立在三大支柱之上:
日志 (Logging)
- 是什么:记录离散的、带有时间戳的事件。每一行日志都是一个事实的快照,例如“用户输入了消息”、“Skill
get_weather被调用”、“LLM API 返回错误”。 - 在 Hermes 中的体现:Hermes Agent 使用 Loguru 库提供强大且易于配置的日志功能。默认情况下,它会记录核心组件的运行状态、收到的消息、执行的技能等关键信息。通过配置,我们可以输出结构化日志 (Structured Logging),通常是 JSON 格式,这使得日志可以被机器轻松解析、查询和分析。
- 是什么:记录离散的、带有时间戳的事件。每一行日志都是一个事实的快照,例如“用户输入了消息”、“Skill
追踪 (Tracing)
- 是什么:记录单个请求(在 Agent 场景下,即一次完整的对话交互)在系统中流经不同服务或组件的完整路径。一个 Trace 由多个 Span 组成,每个 Span 代表一个工作单元(如一次函数调用、一次 API 请求)。
- 为什么重要:对于 Agent 而言,一次对话交互可能涉及多个内部环节:消息解析、记忆查询、LLM 推理、工具(技能)调用等。分布式追踪能将这些环节串联起来,形成一个完整的调用链。这让我们能清晰地看到:
- 对话轨迹回放:请求的完整生命周期是怎样的?
- 技能追踪:哪个 Skill 被调用了?传入了什么参数?执行了多久?
- 性能瓶颈定位:整个交互过程中,是 LLM 调用慢,还是某个 Skill 的外部 API 调用慢?
指标 (Metrics)
- 是什么:在一段时间内聚合的、可量化的数据。例如:每分钟处理的消息数、平均响应延迟、LLM API 调用成功率、Token 消耗总数等。
- 为什么重要:指标为我们提供了系统健康状况的宏观视图。通过观察指标的变化趋势,我们可以设置告警(例如,当延迟超过 2 秒时),进行容量规划,或评估模型/技能变更带来的性能影响。
本期教程将从最基础的日志实践开始,逐步引导你进入使用 OpenTelemetry 实现高级追踪的工业级实践。
💻 实战演示
Part 1: 玩转 Hermes Agent 内置日志系统
Hermes Agent 的日志配置位于项目根目录的 config.yml 文件中。让我们从这里开始。
1.1 日志级别与格式调整
默认配置可能如下:
# config.yml
log:
level: "INFO"
path: "hermes.log"
rotation: "10 MB"
retention: "7 days"
level: 日志的详细程度。常见的级别有DEBUG,INFO,WARNING,ERROR,CRITICAL。INFO: 默认级别,记录常规操作信息。DEBUG: 最详细的级别,用于开发和调试。它会打印出 LLM 的完整 Prompt、模型的思考过程(Thought Process)等海量信息。
实战操作:开启 DEBUG 模式
为了深入观察 Agent 的“思考过程”,我们将日志级别调整为 DEBUG。
# config.yml
log:
level: "DEBUG"
# ... 其他配置保持不变
现在,启动 Hermes Agent 并与之交互:
# 确保你在 Hermes Agent 项目的根目录
hermes run
在另一个终端或通过消息网关(如 Telegram)向 Agent 发送一个需要调用 Skill 的指令,例如:“今天北京天气怎么样?”
然后,查看日志文件 hermes.log:
tail -f hermes.log
你会看到比 INFO 级别详细得多的输出,其中包含关键的调试信息:
...
DEBUG | hermes.core.agent:process_message:123 - Received new message from user: "今天北京天气怎么样?"
DEBUG | hermes.core.brain:think:45 - Generating thought process for the user query...
DEBUG | hermes.core.brain:_construct_prompt:67 - Constructed prompt for LLM:
"""
... (这里会显示完整的、发送给 LLM 的 Prompt) ...
"""
DEBUG | hermes.providers.openai:chat_completion:89 - Calling OpenAI API with model gpt-4-turbo...
INFO | hermes.core.agent:execute_skill:234 - Agent decided to use skill: get_weather
DEBUG | hermes.core.agent:execute_skill:235 - Skill arguments: {'city': '北京'}
INFO | hermes.skills.weather:get_weather:56 - Executing get_weather skill for city: 北京
... (技能执行的内部日志) ...
DEBUG | hermes.core.agent:execute_skill:250 - Skill `get_weather` returned: "北京今天晴,气温25度..."
DEBUG | hermes.core.brain:think:90 - LLM call took 1.234 seconds.
INFO | hermes.core.agent:process_message:150 - Sending final response to user.
...
通过 DEBUG 日志,我们清晰地完成了:
- 对话轨迹回放:从收到消息到最终回复的每一步都记录在案。
- 技能追踪:我们看到了
get_weather被调用,以及传入的参数{'city': '北京'}。 - 性能分析:我们看到了 LLM 调用的耗时
1.234 seconds。
1.2 启用结构化日志 (JSON)
对于自动化分析,纯文本日志难以处理。结构化日志是更好的选择。
实战操作:切换到 JSON 格式
修改 config.yml:
# config.yml
log:
level: "DEBUG"
path: "hermes.log"
rotation: "10 MB"
retention: "7 days"
json: true # 新增此行
重启 Hermes Agent 并再次交互。现在查看 hermes.log,内容会变成这样:
{"text": "...", "record": {"elapsed": {"repr": "...", "seconds": ...}, "exception": null, "extra": {}, "file": {"name": "agent.py", "path": "..."}, "function": "process_message", "level": {"icon": "...", "name": "DEBUG", "no": 10}, "line": 123, "message": "Received new message from user: \"今天北京天气怎么样?\"", "module": "agent", "name": "hermes.core.agent", "process": {"id": ..., "name": "..."}, "thread": {"id": ..., "name": "..."}, "time": {"repr": "...", "timestamp": ...}}}
虽然可读性变差,但现在我们可以用 jq 等工具轻松地查询和分析日志。
实战操作:用 jq 查询所有技能调用
# 筛选出记录技能执行的日志,并提取技能名称和参数
cat hermes.log | jq 'select(.record.message | test("Agent decided to use skill:")) | .record.extra'
这将输出类似这样的结果,非常适合脚本处理:
{
"skill_name": "get_weather",
"skill_args": {
"city": "北京"
}
}
Part 2: 集成 OpenTelemetry 实现高级追踪
日志分析虽然强大,但在复杂、高并发的场景下,手动关联日志来构建一个完整的请求链路非常困难。这正是 OpenTelemetry (OTel) 的用武之地。我们将使用 OTel 将 Hermes Agent 的内部调用转换为可视化的分布式追踪数据。
我们将集成一个开源的 OTel 后端 SigNoz(你也可以使用 Jaeger、Zipkin 等)。SigNoz 的优势是它将日志、追踪和指标整合在一个平台。
2.1 部署 SigNoz
我们将使用 Docker Compose 在本地快速部署一个 SigNoz 实例。
创建一个 docker-compose.yml 文件:
# docker-compose.yml for SigNoz
version: '3'
services:
signoz-otel-collector:
image: signoz/otel-collector:0.93.0
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
depends_on:
- signoz-clickhouse
signoz-query-service:
image: signoz/query-service:0.41.0
ports:
- "8080:8080"
command: [ "-config=/etc/query-service/config.yaml" ]
volumes:
- ./query-service-config.yaml:/etc/query-service/config.yaml
depends_on:
- signoz-clickhouse
signoz-clickhouse:
image: clickhouse/clickhouse-server:23.10.2-alpine
ports:
- "8123:8123"
- "9000:9000"
volumes:
- signoz-clickhouse-data:/var/lib/clickhouse
healthcheck:
test: ["CMD", "clickhouse-client", "-q", "SELECT 1"]
interval: 30s
timeout: 10s
retries: 5
signoz-frontend:
image: signoz/frontend:0.41.0
ports:
- "3301:3000"
depends_on:
- signoz-query-service
volumes:
signoz-clickhouse-data:
同时,你还需要 otel-collector-config.yaml 和 query-service-config.yaml。为简化起见,你可以从 SigNoz 官方文档 获取这些配置文件。
下载配置文件后,启动 SigNoz:
docker-compose up -d
等待几分钟,然后访问 http://localhost:3301,你应该能看到 SigNoz 的 UI。
2.2 为 Hermes Agent 安装 OTel 依赖
在你的 Hermes Agent Python 环境中,安装必要的 OTel 库。
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp
2.3 在 Hermes Agent 中植入追踪代码
现在,我们需要修改 Hermes Agent 的代码,让它在关键路径上创建和管理 Span,并将数据发送到 SigNoz。
创建一个新的 Python 文件,例如 hermes/utils/telemetry.py:
# hermes/utils/telemetry.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
def setup_tracer(service_name="hermes-agent"):
"""初始化 OpenTelemetry Tracer"""
resource = Resource(attributes={
"service.name": service_name
})
# 配置 OTLP Exporter,将数据发送到 SigNoz Collector
# 默认地址是 localhost:4317,与我们的 docker-compose 配置匹配
otlp_exporter = OTLPSpanExporter(endpoint="localhost:4317", insecure=True)
# 设置 Tracer Provider 和 Span Processor
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
# 设置全局 Tracer Provider
trace.set_tracer_provider(provider)
# 返回一个 tracer 实例
return trace.get_tracer(__name__)
# 在模块级别初始化一次
tracer = setup_tracer()
现在,我们可以在 Agent 的核心逻辑中“植入”追踪代码。一个理想的植入点是处理用户消息的主函数 Agent.process_message。
修改 hermes/core/agent.py:
# hermes/core/agent.py
# ... 其他 import ...
from hermes.utils.telemetry import tracer # 导入我们创建的 tracer
class Agent:
# ... 其他方法 ...
async def process_message(self, message: str, user_id: str):
# 创建一个顶层 Span,代表整个对话交互
with tracer.start_as_current_span("process_user_message") as parent_span:
# 为 Span 添加有用的属性 (Attributes),便于查询
parent_span.set_attribute("user.id", user_id)
parent_span.set_attribute("user.message", message)
try:
# ... 原有的 process_message 逻辑 ...
# 1. 记忆检索
with tracer.start_as_current_span("memory_retrieval") as memory_span:
relevant_memory = self.memory.retrieve(user_id, message)
memory_span.set_attribute("memory.found_count", len(relevant_memory))
# 2. 思考与技能选择
with tracer.start_as_current_span("brain_think") as brain_span:
thought_process, skill_name, skill_args = await self.brain.think(message, user_id)
brain_span.set_attribute("llm.thought", thought_process)
brain_span.set_attribute("skill.selected", skill_name or "None")
# 3. 技能执行
if skill_name:
with tracer.start_as_current_span("skill_execution") as skill_span:
skill_span.set_attribute("skill.name", skill_name)
skill_span.set_attribute("skill.args", str(skill_args))
skill_result = await self.execute_skill(skill_name, **skill_args)
skill_span.set_attribute("skill.result_length", len(str(skill_result)))
# 如果技能执行出错,可以记录异常
# skill_span.record_exception(e)
# skill_span.set_status(trace.Status(trace.StatusCode.ERROR, "Skill execution failed"))
# ... 后续生成最终回复的逻辑 ...
final_response = "..." # 假设这是最终回复
parent_span.set_attribute("agent.response", final_response)
return final_response
except Exception as e:
# 如果整个过程发生异常,在顶层 Span 中记录
parent_span.record_exception(e)
parent_span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
raise
最后,在 Hermes Agent 的主入口 main.py 或 __main__.py 中,确保 OTel 初始化代码被调用。
# 在应用启动时调用
from hermes.utils.telemetry import setup_tracer
setup_tracer()
# ... 后续的启动代码 ...
2.4 分析追踪数据
重启 Hermes Agent,并再次与之交互。然后打开 SigNoz UI (http://localhost:3301)。
- 点击左侧的 "Traces" 标签页。
- 你应该能看到名为
hermes-agent的服务。 - 在列表中,你会看到名为
process_user_message的 Trace。点击其中一个。
你将看到一个火焰图 (Flame Graph) 或甘特图 (Gantt Chart),它完美地可视化了整个对话处理流程:
process_user_message -------------------------------------------------- [2500ms]
|
|- memory_retrieval --- [50ms]
|
|- brain_think --------------------------------------- [1500ms]
|
|- skill_execution (get_weather) -------------------- [800ms]
|
|- ... (response_generation) ... --- [150ms]
通过这个视图,你可以:
- 一目了然地回放轨迹:清晰地看到代码执行的顺序和父子关系。
- 精确定位性能瓶颈:上图中,
brain_think(LLM 调用) 和skill_execution是最耗时的部分。如果skill_execution耗时异常,你可以进一步下钻,查看是哪个外部 API 调用慢了。 - 深入分析细节:点击任何一个 Span(如
skill_execution),在右侧的详情面板中,你可以看到我们设置的所有属性,如skill.name、skill.args等,这对于调试至关重要。
至此,你已经将 Hermes Agent 的可观测性从“文本日志”时代带入了“分布式追踪”时代,极大地提升了对这个智能体的理解和控制能力。
涉及命令
# 查看实时日志
tail -f hermes.log
# 使用 jq 查询结构化日志
cat hermes.log | jq '...'
# 启动 SigNoz (或其他 OTel 后端)
docker-compose up -d
# 安装 OpenTelemetry Python 库
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp
# 启动 Hermes Agent
hermes run
要点回顾
- 可观测性三支柱:日志 (Logging)、追踪 (Tracing) 和指标 (Metrics) 是理解和调试复杂系统的基石。
- Hermes 内置日志:通过调整
config.yml中的log.level(尤其是DEBUG) 和log.json,可以获得丰富的诊断信息,并为自动化分析做好准备。 - 日志是基础:
DEBUG级别的日志可以让你手动完成对话轨迹回放和技能追踪,是排查问题的首选工具。 - 追踪是进阶:当系统变得复杂或需要精确定位性能瓶颈时,集成 OpenTelemetry 进行分布式追踪是最佳实践。
- Span 与 Attributes:OTel 的核心是创建代表工作单元的 Span,并为其附加丰富的上下文信息 (Attributes),这使得追踪数据具有极高的分析价值。
- 从黑箱到白箱:善用可观测性工具,能将 Agent 的内部决策过程透明化,让你从一个被动的用户,转变为一个主动的、能够深度掌控和优化 Agent 的开发者。