Agent 工作流可观测性:Trace、日志与回放
为什么只看最终结果不够
我在做 Agent 系统的前几个月,遇到一个典型问题:用户说「帮我查一下上海飞东京下周最便宜的航班」,Agent 最终返回了一个结果,看起来格式正确、有航班号、有价格。但用户截图反馈说价格差了将近一倍。
我打开日志,只看到一行 task_finished: status=success。没有中间步骤,没有工具调用参数,没有模型为什么选了这个航班号的推理过程。这行日志告诉我任务完成了,但没有告诉我为什么完成错了。
传统后端服务的日志逻辑到这里不太够用了。一个 HTTP 接口返回 200,问题通常出在请求参数或者数据库查询。但 Agent 的「200」背后,可能经过了五六次 LLM 调用、三次工具调用、一次上下文裁剪,其中任何一步出了偏差,最终结果就会错——而每一步的输入和输出如果不显式记录下来,根本无从回溯。
这篇文章讨论的是如何在 Agent 工作流中建立结构化的可观测性:记录什么、怎么记录、如何在出了问题的时候回放整个过程。
理论基础:从 OpenTelemetry 到 Agent Trace
OpenTelemetry GenAI 语义约定
OpenTelemetry 社区在 2024 年底启动了 GenAI 语义约定项目,到 2026 年中已经进入开发阶段,定义了 gen_ai.* 命名空间下的标准化属性。这套约定的核心思路是把一次 Agent 执行拆成三层 span:
- gen_ai.chat / gen_ai.embeddings:底层模型调用,记录模型名、token 用量、延迟、finish_reason
- agent.tool_call:工具调用,记录工具名、参数摘要、执行结果、耗时
- agent.run:顶层任务,串联整个执行流程
MLflow 和 Portkey 等平台已经实现了这套约定,Langfuse、Arize Phoenix 等工具通过 OpenInference 提供了兼容的 span 结构。这套标准化的好处是,换模型提供商或者换 Agent 框架时,trace 数据不用重写。
Braintrust 的四类 Span 分类
Braintrust 在 2026 年的 Agent Observability 完整指南中,将 Agent trace 的 span 分为四类:
- Tool-call spans:工具名、参数、原始输出、重试次数、错误状态
- Reasoning spans:模型的计划、选择的行动、观察结果、下一步决策
- State transition spans:每步执行前后的状态快照、上下文编辑
- Memory operation spans:对长期存储的读写,含查询、返回条目、相关性评分
这个分类比单纯记录 LLM 调用要完整得多。它把「Agent 为什么这么做」也纳入了 trace 范围。
LangChain 的三层数据结构
LangSmith 用 Runs → Traces → Threads 三层结构组织数据:
- Run:Agent 的单步操作(一次 LLM 调用、一次工具调用)
- Trace:Agent 单次完整执行,由嵌套的 Runs 树组成
- Thread:用户与应用的完整对话集合
这三层对应了不同的调试场景:单次调用异常看 Run,任务逻辑错误看 Trace,跨会话行为问题看 Thread。
Trace 事件设计
一个 Agent 任务的完整 trace 应该包含以下事件节点:
| 事件类型 | 记录内容 | 调试用途 |
|---|---|---|
task_created | 用户目标、入口、权限范围 | 还原任务起点 |
plan_generated | 计划步骤、置信度、需确认动作 | 检查规划是否合理 |
context_built | 上下文来源、检索材料、裁剪策略 | 定位上下文问题 |
context_compressed | 压缩前/后 token 数、压缩策略 | 诊断上下文饱和 |
tool_called | 工具名、参数摘要、耗时、状态、错误 | 定位工具故障和循环 |
tool_blocked | 阻断原因(循环/限流/权限) | 检查循环检测是否误杀 |
handoff_sent | 交接载荷、子任务数、格式 | 定位多 Agent 协作问题 |
state_updated | 任务进度、产物、下一步 | 追踪执行路径 |
task_finished | 结果、失败类型、人工接管/回滚信息 | 任务级复盘 |
eval_scored | 评分、评分器、是否通过质量门控 | 连接评估循环 |
OpenTelemetry GenAI 约定定义了 gen_ai.usage.input_tokens 和 gen_ai.usage.output_tokens 等标准属性,可以跨平台使用。在这个基础上加上 Agent 特有的事件类型(tool_called、handoff_sent 等),就构成了一套可用的 trace schema。
指标设计:不要只看调用次数
Agent 指标容易陷入一个误区:只统计 LLM 调用次数和 token 总量。这些是成本指标,不是质量指标。更有价值的指标分为三个层次:
任务层指标
| 指标 | 定义 | 为什么重要 |
|---|---|---|
| 任务成功率 | 用户目标是否达成 | 最直接的质量信号 |
| 人工接管率 | 需要人工介入完成任务的比例 | 反映 Agent 自主能力 |
| 平均步骤数 | 完成任务的平均工具调用次数 | 过多说明效率低,过少可能有遗漏 |
| 用户采纳率 | 用户是否接受 Agent 的输出 | 最终衡量标准 |
工具层指标
| 指标 | 定义 | 对应问题 |
|---|---|---|
| 工具失败率 | 按工具名分组的失败比例 | 定位不可靠的工具 |
| 工具重试率 | 同一工具连续调用比例 | 暗示循环或参数问题 |
| 平均工具延迟 | P50/P95/P99 延迟分布 | 识别性能瓶颈 |
失败分类(对应不同修复方向)
| 失败类型 | 典型表现 | 修复方向 |
|---|---|---|
| 上下文不足 | 工具调用参数缺失或错误 | 改进 RAG 检索或上下文组装 |
| 工具失败 | 外部服务超时、权限不足 | 工具侧修复,添加重试和降级 |
| 模型误判 | 选错工具、幻觉参数 | 改进工具描述、添加参数验证 |
| 规划循环 | 反复调用同一工具 | 添加循环检测和硬限制 |
| 用户目标不清 | 任务范围模糊导致反复确认 | 改进入口交互,明确任务边界 |
可观测性落地路径
不需要第一天就搭建完整的观测平台。一个务实的分阶段路径:
阶段一:Day 1 — 结构化日志
最小起步:给每个任务一个 trace_id,用结构化 JSON 输出关键事件。不需要引入 OTel SDK,标准的 structlog 或 pino 就够了。
{
"trace_id": "abc-123",
"event": "tool_called",
"tool": "search_flights",
"params": {"origin": "SHA", "destination": "TYO"},
"status": "success",
"duration_ms": 320,
"tokens": {"input": 512, "output": 128}
}关键要求:字段稳定,能按 trace_id 串联,能按用户、任务、工具筛选。
阶段二:Week 1 — Trace 可视化
接入 Langfuse(自托管)或 LangSmith(SaaS),让 trace 数据可视化。这时候可以开始做:
- 按 trace_id 查看完整的执行路径
- 按工具名过滤,找到失败率最高的工具
- 按用户分组,找到体验最差的使用场景
阶段三:Month 1 — 在线评估
在 trace 样本上添加在线评分器。可以用 LLM-as-a-judge(用一个独立模型给 Agent 输出打分),也可以用规则评分(检查输出格式、关键信息是否完整)。设置质量回归告警。
阶段四:Quarter 1 — CI 集成
把生产环境发现的失败案例收集为 eval 数据集。在 CI 中跑相同的评估器。质量低于阈值时阻断 PR 合并。这一步把一次性调试变成了持续的质量循环。
| 阶段 | 时间 | 核心动作 | 所需工具 |
|---|---|---|---|
| 结构化日志 | Day 1 | trace_id + JSON 输出 | structlog / pino |
| Trace 可视化 | Week 1 | 接入 Langfuse/LangSmith | Langfuse / LangSmith |
| 在线评估 | Month 1 | 评分器 + 质量告警 | Braintrust / Langfuse evals |
| CI 集成 | Quarter 1 | eval 数据集 + PR 门控 | GitHub Actions + eval 框架 |
检查清单
设计阶段
- 确定 trace_id 生成策略,确保能按用户、任务、会话三个维度查询
- 定义事件类型枚举(task_created / plan_generated / tool_called / state_updated / task_finished 等),写入团队共享的 schema 文档
- 确定哪些字段是必填(trace_id、event、timestamp、status),哪些是可选(params、result、error_detail)
- 决定 prompt/completion 内容的记录策略:全量记录、截断记录、还是只记录 hash(涉及隐私和成本)
- 为每个工具定义成功/失败的返回格式规范,包含明确的 SUCCESS/FAILED 标记
实现阶段
- 每次 LLM 调用记录 model 名、input_tokens、output_tokens、finish_reason、延迟
- 每次工具调用记录工具名、参数摘要、执行结果、耗时、重试次数
- 上下文窗口利用率在每轮结束时计算并记录,接近 70% 时标记
- 多 Agent 交接时,交接载荷结构化记录,每个子任务独立 span
- 添加循环检测 hook:同一工具+参数在滑动窗口内出现 N 次时阻断
- 添加工具调用硬限制:单次任务每个工具最多调用 M 次
运维阶段
- 任务成功率、人工接管率、工具失败率按日/周聚合,建立基线
- 上下文 token 数增速设置告警(单轮增长超过阈值)
- 工具延迟 P95 设置告警
- 生产环境失败案例定期(每周)收集为 eval 数据集
- CI 中运行 eval 套件,质量低于阈值时阻断合并
- 定期校准 LLM-as-judge 评分器与人工标注的一致性
边界与局限
有些问题可观测性本身解决不了。
Trace 数据量大。一个复杂 Agent 任务可能产生几十个 span,每个 span 包含 prompt 和工具调用详情。全量记录的存储成本不低,需要采样策略。对错误 trace 做 100% 采样,对成功 trace 做 10-20% 采样是比较常见的做法。
Prompt 内容涉及用户隐私。OTel GenAI 约定明确警告了属性中可能包含敏感信息,建议 opt-in 记录并配合过滤和截断。在合规要求严格的场景(医疗、金融),可能需要完全放弃记录 prompt 原文,只保留结构化元数据。
LLM-as-judge 评分器本身有不确定性。用它做在线评估时,评分结果有方差,不能像传统断言一样做精确比对。需要足够大的样本量才能得到有意义的趋势信号。
可观测性是调试的基础设施,不是银弹。它让你能在出问题后回溯原因,但不能自动修复问题。修复还是要回到 prompt 工程、工具设计和 Agent 逻辑本身。
参考资料
- OpenTelemetry GenAI Semantic Conventions — OTel 官方 GenAI 语义约定,定义了
gen_ai.*命名空间下的标准化属性 - Agent Observability: The Complete Guide for 2026 — Braintrust — 四类 span 分类、最小可行 trace schema、分阶段落地路径
- AI Agent Observability with Langfuse — Langfuse 的 Agent 可观测性策略、评估方法和跨框架集成
- Debugging Deep Agents with LangSmith — LangChain — Deep Agent 的 trace 分析、Polly AI 助手辅助调试、Fetch CLI 工具
- How to Prevent AI Agent Reasoning Loops — AWS/Dev.to — DebounceHook、明确 SUCCESS 状态、硬限制三层防线
- The AI Agent Debugging Playbook — 8 种失败模式分类、Replay 方法、三层评估体系
- OpenTelemetry for AI Systems: LLM and Agent Observability — Uptrace — OTel GenAI 约定的代码示例、自动/手动 instrumentation、告警规则设计
- Semantic Conventions for Generative AI Agentic Systems — GitHub — Agent 系统语义约定的正式提案,定义了 agentic 系统的专用属性