Tracing
记录一次 Agent Run:模型调用、工具调用、参数、返回、耗时与错误。
一句话理解:Trace 是 Agent 的“飞行记录仪”:把一次运行里的每次模型调用、工具调用连同参数、返回、耗时和错误,按父子关系串成一棵树,让你能回答“它为什么这么做”。
传统软件出错,工程师看日志和堆栈,基本能复现问题。Agent 不一样:同一个输入,这次可能调用 3 个工具,下次调用 7 个;这次读到了完整的退款记录,下次因为工具超时拿到空结果,于是“合理地”编出一个结论。用户只看到最后那句错误的回答,中间发生了什么全部消失。
没有 Trace,排查 Agent 问题就只能靠猜:“是不是模型不行?换个模型试试。”有了 Trace,你能精确地指出“第 4 步工具返回了空数组,第 5 步模型把‘没有数据’理解成了‘退款已被撤销’”。这一课讲清 Trace 的结构、该记什么、怎么读,以及行业里正在形成的标准。
先用一张图看清本模块的全貌(图 1):Trace 采集每一次运行,汇总成三种信号,支撑看板、成本和延迟的决策,再通过持续改进闭环回到线上。
Trace、Span 与 Run
先统一三个词:
| 概念 | 含义 | 类比 |
|---|---|---|
| Run / Trace | 一次完整的 Agent 执行,从收到任务到给出结果 | 一次航班的完整飞行记录 |
| Span | Trace 里的一个操作:一次模型调用、一次工具调用、一次审批等待 | 记录里的一个事件段:起飞、巡航、降落 |
| 父子关系 | Span 可以嵌套:Agent 调用 Span 下面挂着模型调用和工具调用 | 巡航阶段里包含多次航向调整 |
每个 Span 至少有:名称、开始与结束时间、状态(成功或错误)和一组属性(模型名、Token 数、工具名、参数等)。整棵树共享同一个 trace_id,因此可以从一条用户投诉一路追到具体的某次工具调用。
图 2 把三个概念画在一起:
读一条真实的 Trace
用户输入:“订单 A1024 的退款为什么还没到账?”客服 Agent 给出了错误结论“退款已被撤销,请重新申请”。下面是这次运行的 Trace:
trace_id=7f3a… agent=support-diagnoser prompt_version=v12 总耗时 24.8s 状态=成功
└─ invoke_agent support-diagnoser 24.8s
├─ chat model-large in=6,480 out=212 1.9s → 决定调用 get_order
├─ execute_tool get_order args={order:"A1024"} 0.3s ok
├─ chat model-large in=7,310 out=180 1.6s → 决定依次查询退款和支付
├─ execute_tool get_refunds args={order:"A1024"} 8.0s ok result=[] ⚠
├─ execute_tool get_payment args={order:"A1024"} 0.4s ok
├─ chat model-large in=8,020 out=356 2.8s → “退款记录为空,推断退款被撤销”
├─ execute_tool search_docs args={q:"退款到账时效"} 5.2s error=timeout
└─ chat model-large in=8,650 out=610 4.6s → 输出最终结论
把这条 Trace 按时间展开,偏离发生在哪里一目了然(图 3):
读 Trace 时按顺序问五个问题:
- 结果对吗? 结论是“退款已被撤销”,但人工核查发现退款单正常在处理中。
- 哪一步第一次偏离?
get_refunds用了 8 秒且返回空数组,这不正常。进一步查工具日志,发现退款查询服务超时后返回了空结果,而不是报错。 - 模型的判断是否基于证据? 第 3 次模型调用把“没有数据”当成了“没有退款记录”,这是工具返回值设计的问题,也暴露了指令里缺少“数据缺失时不要下结论”的规则。
- 有没有被忽略的错误?
search_docs超时了,模型仍然输出了确定性的结论,没有降级为“无法判断”。 - 耗时和成本花在哪? 近 25 秒里有 13 秒在等两个慢工具,模型调用本身并不慢。
一次读 Trace,定位出三个可修复点:工具超时应返回明确错误、指令补充数据缺失规则、结论需要经过验证(见 为什么必须验证)。这比“换个模型试试”有效得多。
一条 Trace 应该记录什么
| 层级 | 必记字段 | 为什么需要 |
|---|---|---|
| Run | trace_id、用户与租户、任务类型、入口渠道 | 从投诉或工单反查到具体运行 |
| 版本 | Agent 版本、Prompt 版本、工具集版本、模型名 | 回答“是不是上次改动引入的” |
| 模型调用 | 输入与输出 Token、缓存命中 Token、耗时、结束原因 | 成本与延迟分析,发现输出被截断 |
| 工具调用 | 工具名、参数、结果摘要、耗时、错误类型、重试次数 | 定位工具问题与参数错误 |
| 控制点 | 审批请求与结果、护栏拦截、停止原因 | 复盘权限与安全问题 |
| 结果 | 最终状态、验证结果、用户反馈 | 连接到评估与改进闭环 |
内容要不要记? 模型输入输出、工具返回里常有客户姓名、电话、订单金额等敏感信息,而且体积很大。OpenTelemetry 的 GenAI 语义约定明确建议:插桩库默认 不 采集指令、输入与输出内容,只在用户显式开启时记录;生产环境更推荐把内容存到有独立权限控制的外部存储,Span 上只记录引用。产品经理要在需求里写清:哪些环境记录全文、保留多久、谁能看、是否脱敏。
标准与工具生态
OpenTelemetry GenAI 语义约定。 OpenTelemetry 是可观测性领域通用的开放标准,它为生成式 AI 定义了一套 Span、指标和事件的命名约定。截至本文更新时,这套约定仍处于 Development(开发中)状态,并已迁移到独立的 semantic-conventions-genai 仓库维护。几个核心约定:
| 操作 | gen_ai.operation.name |
建议的 Span 名称 |
|---|---|---|
| 模型推理 | chat 等 |
chat {模型名} |
| 工具执行 | execute_tool |
execute_tool {工具名} |
| 调用 Agent | invoke_agent |
invoke_agent {Agent 名} |
常用属性包括 gen_ai.provider.name、gen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.tool.name、gen_ai.conversation.id 以及通用的 error.type。上面那条 Trace 就是按这个思路组织的。由于仍在开发状态,字段名可能调整,接入时以仓库最新文档为准。
SDK 内置追踪。 主流 Agent 框架大多自带追踪:
- OpenAI Agents SDK 默认开启追踪,自动为 Agent 执行、模型生成、函数工具调用、护栏和 Handoff 创建 Span;可以通过配置关闭敏感数据记录。它的文档也注明,使用零数据保留(ZDR)政策的组织无法使用该追踪功能。文档列出了 Langfuse、Arize Phoenix、MLflow 等可接收追踪数据的外部平台。
- Claude Code 支持通过 OpenTelemetry 导出指标和事件(如 Token 用量、成本、工具结果、API 错误),用户提示词内容默认脱敏;链路追踪(Traces)导出目前标注为 Beta。
选型建议:优先选择能导出到 OpenTelemetry 的方案,避免把排障能力锁死在某一家平台上;同时确认它能串起多 Agent 与子 Agent 的父子关系(见 多 Agent 的代价)。
常见误区
- “有日志就够了。” 平铺的日志行无法表达“这次工具调用属于哪一步、由哪次模型决策触发”。Agent 需要的是带父子关系的树。
- “全量记录所有内容最保险。” 这会带来隐私合规风险和巨大的存储成本。默认记录结构化元数据,内容按环境与权限分级开启。
- “Trace 是工程师的事。” 产品经理恰恰最需要读 Trace:它是理解 Agent 真实行为、发现需求漏洞(比如缺少数据缺失规则)的第一手材料。
- “只在出问题时才看。” 定期抽样阅读成功的 Trace,常常能发现“结果碰巧对了,但过程有问题”的隐患。
产品经理检查清单
- 每次运行是否都有唯一的 trace_id,并能从用户反馈、工单直接跳转过去?
- Trace 里是否记录了 Agent、Prompt、工具集和模型的版本?
- 工具调用是否记录了参数、结果摘要、耗时、错误类型和重试次数?
- 审批、护栏拦截、停止原因这些“控制点”有没有进入 Trace?
- 内容记录策略是否明确:哪些环境记全文、怎么脱敏、保留多久、谁有权限查看?
- 追踪方案能否导出到 OpenTelemetry 兼容的后端?
练习
为你在 实操:做一个 Tool Agent 中完成的天气 Agent(或任何一个简单 Agent)接入追踪:
- 开启所用 SDK 的内置追踪,或手动为每次模型调用和工具调用记录开始时间、结束时间、Token 与参数。
- 故意制造一次工具失败(例如传入不存在的城市,或让工具超时)。
- 打开这条 Trace,用上文的“五个问题”写一段 200 字的复盘:哪一步第一次偏离、模型如何应对、应该修改工具、指令还是验证逻辑。
延伸阅读
下一步
单条 Trace 回答“这一次为什么”,下一课「日志、指标与告警」讲如何把成千上万条运行汇总成看板,并在出问题时第一时间收到告警。