给 Agent 加一套可观测性:trace、健康检查与遥测
echo-agent 前身为 2025 年 11 月启动的个人助理项目 fubot,最初面向长期陪伴型个人智能体,围绕认知记忆、上下文延续、用户偏好沉淀、任务闭环与持续自我优化展开。随着真实场景迭代,项目逐步形成多入口接入、统一事件模型、消息总线、Agent Loop、多模型抽象、工具调用、MCP 接入、任务调度、权限审批、运行轨迹、长期记忆和受控自演进等能力。目前已支持微信、QQ、CLI、Gateway、Webhook、Cron 等入口,服务用户超过 20 万、累计下载超过 50 万,是面向长期运行、记忆增强和可持续成长智能体的开源 Agent Runtime。

线上 Agent 出问题时,最怕听到一句话:“它刚才没回。”
没回是什么意思?请求没进来,还是进来了但被限流?模型慢,还是工具卡住?最终回答生成了,但通道投递失败?如果用户说“它用了不该用的工具”,问题又变成:工具为什么暴露给模型,审批有没有触发,执行上下文是否真的允许?
普通后端排障时,我们通常看日志、错误码、数据库和下游服务。Agent 系统不够。因为一次回答不是一个确定函数的返回值,而是一条由上下文、模型、工具、审批、通道和记忆共同组成的行动链。
本篇只讲一个点:Agent 的可观测性不是多打几行日志,而是把一次行动背后的因果链、组件健康和外部遥测建立起来。
问题入口
如果只看最终文本,很多 Agent 问题会被压扁成同一种现象。
用户觉得“慢”,可能是 ContextStage 在构造长上下文,也可能是模型 provider 降级,也可能是工具调用超时,还可能是 Gateway wait 等到了 504,但后台任务仍在继续。
用户觉得“错”,也不一定是模型生成错。可能是检索片段不对、工具结果没截断、allowed tools 暴露过宽、ApprovalGate 拒绝后没有给出清晰反馈,或者最终回复投递到了错误通道。
这就是 Agent 和普通 Web 服务的差别。
| 维度 | 普通后端服务 | Agent 系统 |
|---|---|---|
| 路径 | 代码路径相对确定 | 每轮可能重新规划 |
| 依赖 | 数据库、缓存、下游 API | 模型、工具、记忆、知识库、审批、通道 |
| 失败形态 | 错误码、异常、超时 | 系统失败与认知失败并存 |
| 排障入口 | 日志和指标通常够用 | 需要 trace、日志、审计、健康和 usage |
| 关键问题 | 哪里慢、哪里错 | 为什么这样行动、是否越界、结果是否送达 |
会调用工具只说明有行动接口;能否稳定运行,要看每次行动是否能被追踪、解释和复盘。
为了不停留在抽象层面,下面以 echo-agent 的实现为例。它的可观测性由三层组成:本地 trace logging、组件健康检查和 OpenTelemetry 遥测。
Trace
Trace 的核心不是“记录很多事件”,而是给一次处理建立因果结构。
echo-agent 使用 TraceSpan 表示一次处理中的最小观测单元。一个 span 会记录 span_id、trace_id、parent_id、name、kind、开始时间、结束时间、metadata 和 error。kind 用来区分输入、上下文、模型调用、工具调用、输出等阶段;metadata 保存模型名、provider、路由原因、finish reason、工具成功状态等业务信息。
真正关键的是 trace_id。同一次用户请求里的多个 span 共享同一个 trace_id,工程师才能按一条链路去看,而不是在日志里搜索零散关键词。

一次复杂请求可能被拆成这样的序列:
llm_0 -> tool_0_0 -> tool_0_1 -> llm_1 -> tool_1_0 -> llm_2
这条链路比“模型调用成功”“工具调用成功”更有价值。因为它告诉你:模型先做了什么判断,调了哪些工具,工具后是否再次进入模型,最终在哪一步结束。
举个工程场景:用户说“帮我修复测试失败”。最终回答只会告诉你“已修复”或“修复失败”,但 trace 应该能看到更细的链路:上下文阶段读到了哪些测试日志,第一次模型调用选择了哪个修复策略,工具阶段是否读取了文件、运行了测试、修改了代码,第二次模型调用是否根据新的报错调整方案,最后输出是否带着验证结果返回。
如果这次任务失败,trace 还能把失败拆开。是模型没有看到关键日志,还是工具执行超时;是测试命令失败,还是文件写入被权限策略阻断;是 Agent 已经生成最终回答,但通道投递失败。没有这条链,工程师只能复现;有了这条链,工程师可以定位。
最小实现并不复杂:
span = tracer.start_span(
trace_id,
name=f"llm_{iteration}",
kind="llm_call",
parent_id=current_parent,
)
try:
response = await provider.chat(messages, tools=active_tools)
tracer.end_span(span, metadata={
"model": route_decision.model,
"provider": route_decision.provider_name,
"route_reason": route_decision.reason,
"finish": response.finish_reason,
})
except Exception as exc:
tracer.end_span(span, error=str(exc))
raise
工具调用也一样。执行前创建 tool_call span,执行后记录 success;如果被审批拒绝,也要结束 span 并标记 denied。否则排障时只能看到“工具没执行”,却不知道是模型没选、权限不允许、审批拒绝,还是执行器失败。
echo-agent 的 TraceLogger 负责创建、结束、查询和落盘 trace。get_trace(trace_id) 可以查看内存 span 列表;flush_trace(trace_id) 会把 span 序列化为 trace_{trace_id}.json。这对本地调试很重要,尤其是在没有外部 OTel 后端时。
三层观测
Trace 解决单次请求的链路问题,但生产环境还需要回答另外两个问题:系统当前是否健康,这些观测数据能否进入团队已有监控体系。
echo-agent 的三层结构可以这样理解:

第一层是本地 trace。它面向开发和排障,不依赖外部系统。只要有 TraceLogger,一次模型调用、工具调用、输出投递就能被串起来。
第二层是健康检查。HealthChecker 监控 bus、agent、storage 等组件。组件状态包括 HEALTHY、DEGRADED、UNHEALTHY 和 UNKNOWN。注册检查时可以提供 check_fn 和可选的 recovery_fn;后台 loop 会按 interval 调用 check_all(),组件 unhealthy 且有恢复函数时尝试恢复。
它的汇总规则很硬:只要有 UNHEALTHY,overall 就是 unhealthy;否则只要有 DEGRADED,overall 就是 degraded;都没有才是 healthy。
Gateway 还有自己的健康视图。它面向 HTTP/WebSocket 入口:服务是否运行、是否有活跃通道、限流 bucket 状态、媒体缓存大小、活跃 session 数量、hook 数量和 delivery rule 数量。如果 Gateway 没运行,状态是 unhealthy;如果运行但没有活跃通道,状态是 degraded。
健康检查也要分层看。进程还在,只能说明系统存活;模型 provider、存储、消息总线和通道可用,才说明系统具备基础能力;工具权限、审批配置、Gateway 鉴权和日志脱敏符合部署要求,才更接近可信。echo-agent 当前已经覆盖组件状态和 Gateway 入口状态,平台级扩展可以继续把安全姿态、关键工具和代表性任务纳入 health。
这能避免一个常见误判:监控面板显示绿色,但用户仍然无法完成任务。比如 Gateway 进程正常、HTTP 也能返回 200,可是没有活跃通道,或者模型 provider 正在 cooldown,或者高风险工具全部被策略阻断。Agent 的健康不是一个灯,而是一组能力边界。
第三层是 OpenTelemetry。TelemetryManager 管理 OTel providers 和 exporters。安装了 OTel 时,它会创建包含 service.name 的 Resource,设置 tracer provider;配置了 otel_endpoint 时尝试使用 OTLP gRPC exporter,否则使用 console exporter。没有安装 OTel 时,系统优雅降级,遥测关闭但 Agent 仍可运行。
这个取舍很重要。可观测性应该增强系统,而不是成为新的硬依赖。
遥测与成本
OTel 的价值在于把本地事实接入外部观测系统。
echo-agent 在 observability/spans.py 里提供 GenAI span helper:start_llm_span() 记录模型调用,设置 gen_ai.system、gen_ai.request.model、gen_ai.operation.name 等属性;record_llm_usage() 记录 gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、cache read 和 cache creation token;start_tool_span() 记录工具名;start_agent_span() 记录 agent iteration 和 strategy。
这些 helper 在 tracer 为 None 或 OTel 不可用时都是 no-op。这让测试和轻量部署更简单:不用为了跑一个 Agent 就先搭整套观测后端。
usage 不是账单附属品,而是成本观测入口。Agent 引入记忆、知识库和技能后,上下文很容易膨胀。没有 usage,成本问题通常要到月底账单才暴露。
一个可用的 usage 视角至少要能回答:
| 问题 | 观测字段 |
|---|---|
| 输入是否过大 | input tokens、上下文阶段耗时 |
| 输出是否异常 | output tokens、finish reason |
| 缓存是否有效 | cache read / creation tokens |
| 模型是否经济 | provider/model 维度成本与成功率 |
| 任务是否失控 | 单次 trace 的迭代次数和工具调用次数 |
单点指标只能告诉你“慢了”或“贵了”,trace 才能告诉你为什么慢、为什么贵。
日志与审计
可观测性不是只有 trace。
echo-agent 使用 loguru 记录运行日志:MCP 连接失败和重试、模型 provider 降级和 cooldown、工具 circuit breaker、ApprovalGate 拒绝、Channel start/stop、Gateway auth audit、多 Agent delegation audit、调度器 job 错误等。
但日志、trace 和 audit 的职责不同。
| 类型 | 适合记录 | 不适合替代 |
|---|---|---|
| 日志 | 离散事件、异常、重试、模块状态 | 一次请求的结构化因果链 |
| Trace | span 链路、耗时、模型与工具阶段 | 安全审计和长期决策事实 |
| Audit | 认证、审批、委派、越权尝试 | 性能分析和阶段耗时 |
多 Agent 委派写 JSONL audit,记录任务摘要、worker 状态、迭代次数和耗时。Gateway auth 写 audit.jsonl,记录认证成功或失败。这些事实和 trace 一起,才能让系统既可调试,又可审计。
负例也要被观测。审批拒绝、工具被阻断、模型格式错误、知识库无结果、Gateway wait 超时,都不应该简单归为 error。它们语义不同,修复路径也不同。一次危险命令被拒绝,可能说明安全系统正常工作;频繁被拒绝,可能说明工具描述诱导模型走错路。
隐私边界
Agent 的观测数据往往比普通服务日志更敏感。它可能包含用户输入、文件路径、工具参数、知识库片段、模型输出、错误栈、审批原因和凭证名称。
所以生产系统不能把“记录越多越好”当原则。

echo-agent 这一章给出的边界很明确:trace metadata 尽量记录 ID、状态、模型、耗时和错误摘要,而不是完整敏感内容;工具参数里的 token、password、secret 要在记录前脱敏;Gateway auth audit 不应记录 API token 明文;模型输出日志应可配置,默认避免记录大段用户内容;OTel exporter 发到外部系统前,要确认数据合规。
这背后是一个工程判断:可观测性的目标是调试系统,不是复制用户会话数据库。
更成熟的部署可以按环境提供不同 profile:开发模式更详细,生产模式更脱敏,公共 Gateway 模式更保守。低敏指标可以长期聚合,高敏原文应该短期保留、加密或只在授权诊断包中出现。
排障路径
观测系统最终要服务行动。指标如果不能驱动策略调整,就只是仪表盘。
下面这些排障路径,比“先看日志”更接近 Agent 的真实问题结构。
| 用户反馈 | 优先检查 |
|---|---|
| 没有回复 | inbound event 是否进入 bus、session 是否限流、Agent Loop 是否运行、outbound 是否发布、channel 是否发送失败 |
| 回复很慢 | ContextStage 耗时、模型调用耗时、provider fallback、工具超时、重复工具调用 |
| 用了不该用的工具 | active tool list、InferenceController allowed/blocked、ApprovalGate、ToolExecutionContext allowed_tools、工具执行日志 |
| Gateway wait 超时 | pending_http 是否创建、inbound 是否被 bus 接收、final outbound 是否带 correlation id、是否被 handler drop、Agent 是否仍在后台执行 |
生产级 Agent 的可观测性,可以用几条硬标准判断。
| 检查项 | 合格标准 |
|---|---|
| Trace | 每次请求有 trace_id,LLM/tool/output 有 span |
| 工具 | 工具调用成功、失败、拒绝、超时可区分 |
| 健康 | 组件健康和 Gateway 入口健康分层暴露 |
| 遥测 | OTel 可接入外部系统,不可用时 no-op 降级 |
| Usage | token、缓存命中、provider/model 维度可分析 |
| 审计 | 认证、审批、委派等安全事实可追踪 |
| 隐私 | 参数脱敏、导出可控、保留期限和权限清晰 |
| 回归 | 高价值 trace 能脱敏转成评估样本 |
最后一条连接到下一篇。Trace 记录一次运行发生了什么,评估判断系统做得好不好。真实 trace 是评估样本的来源:失败、用户纠正、高风险审批、复杂工具链和长任务恢复,都会暴露系统边界。把它们脱敏后进入评估集,Agent 才能从“出了问题再排查”走向“同类问题不再复发”。
小结
Agent 自己需要记忆,工程师也需要系统记忆。
日志、trace、health check、metrics 和 audit,就是工程师理解 Agent 行为的记忆系统。没有它们,失败会被简化成“模型没答好”;有了它们,团队才能追问上下文怎么来的、模型为什么这样路由、工具为什么被调用、审批为什么通过或拒绝、最终回答是否成功投递。
echo-agent 当前的做法是:用 TraceLogger 记录单次处理链路,用 HealthChecker 和 Gateway health 暴露组件与入口状态,用 TelemetryManager 和 GenAI span helper 接入 OTel,同时通过日志和 audit 保留关键运行事实。
这套系统的价值不在于仪表盘好看,而在于让 Agent 的认知和行动都能被工程化理解。可观测性不是事后装饰,它是生产级 Agent 的基础设施。
(全篇完)
本文为 echo-agent 设计笔记系列第 27 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣,欢迎加入技术交流群参与日常讨论。下一篇我们将探讨 《Agent 评估体系:从单次回答到质量回归》,敬请期待。
更多推荐

所有评论(0)