16.01概念更新于 2026年9月17日

Tracing

记录一次 Agent Run:模型调用、工具调用、参数、返回、耗时与错误。

16.01 · 课时讲解

Tracing

Trace 是 Agent 的飞行记录仪,让你能回答「它为什么这么做」。

1 / 12

一句话理解:Trace 是 Agent 的“飞行记录仪”:把一次运行里的每次模型调用、工具调用连同参数、返回、耗时和错误,按父子关系串成一棵树,让你能回答“它为什么这么做”。

传统软件出错,工程师看日志和堆栈,基本能复现问题。Agent 不一样:同一个输入,这次可能调用 3 个工具,下次调用 7 个;这次读到了完整的退款记录,下次因为工具超时拿到空结果,于是“合理地”编出一个结论。用户只看到最后那句错误的回答,中间发生了什么全部消失。

没有 Trace,排查 Agent 问题就只能靠猜:“是不是模型不行?换个模型试试。”有了 Trace,你能精确地指出“第 4 步工具返回了空数组,第 5 步模型把‘没有数据’理解成了‘退款已被撤销’”。这一课讲清 Trace 的结构、该记什么、怎么读,以及行业里正在形成的标准。

先用一张图看清本模块的全貌(图 1):Trace 采集每一次运行,汇总成三种信号,支撑看板、成本和延迟的决策,再通过持续改进闭环回到线上。

可观测性与成本全景:采集 Span 树,汇总为三种信号,驱动看板、成本与延迟决策,并由持续改进闭环串起 ① 采集 · 同一 trace_id 的 Span 树 invoke_agent · Agent 调用 chat · 模型调用 Token · 耗时 · 结束原因 execute_tool · 工具调用 参数 · 结果摘要 · 错误 控制点 审批 · 护栏拦截 · 停止原因 ② 汇总 · 三种信号 Trace 链路 这一次为什么这样? 日志 / 事件 发生过什么? 指标 整体趋势如何?是否异常? 共享维度:trace_id · 版本 · 租户 · 任务类型 ③ 决策 · 看板、成本与延迟 看板与告警 核心数字下钻到 Trace · 只叫醒该行动的人 单任务成本 模型 + 工具 + 重试摊销 + 人工兜底 延迟 瀑布图找瓶颈 · 并行 · 缓存 · 流式 预算上限 最大步数 · 单任务最大花费 持续改进闭环 1 线上运行 2 发现问题 3 归因 4 沉淀为用例 5 修改 6 回归与灰度 回到 1,进入下一轮:每个失败都沉淀为评测用例 支撑体系:OpenTelemetry GenAI 语义约定 · 版本化一切 · 内容脱敏与分级权限 · 失败与异常运行全量保留
图 1 · 可观测性与成本全景:采集 → 汇总 → 决策,再由持续改进闭环带回线上

Trace、Span 与 Run

先统一三个词:

概念 含义 类比
Run / Trace 一次完整的 Agent 执行,从收到任务到给出结果 一次航班的完整飞行记录
Span Trace 里的一个操作:一次模型调用、一次工具调用、一次审批等待 记录里的一个事件段:起飞、巡航、降落
父子关系 Span 可以嵌套:Agent 调用 Span 下面挂着模型调用和工具调用 巡航阶段里包含多次航向调整

每个 Span 至少有:名称、开始与结束时间、状态(成功或错误)和一组属性(模型名、Token 数、工具名、参数等)。整棵树共享同一个 trace_id,因此可以从一条用户投诉一路追到具体的某次工具调用。

图 2 把三个概念画在一起:

Trace、Span 与父子关系:同一个 trace_id 下,Agent 调用 Span 挂着模型调用、工具调用和控制点 Trace / Run · trace_id=7f3a… invoke_agent(父 Span) chat(模型调用) execute_tool(工具调用) 审批等待 · 护栏拦截 子 Span 可继续嵌套 (如子 Agent 的调用) 每个 Span 都有 名称 开始与结束时间 状态:成功 / 错误 属性:模型名、 Token 数、工具名、参数
图 2 · Trace 是一棵 Span 树:父 Span 下挂着模型调用、工具调用与控制点

读一条真实的 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 返回空结果是第一次偏离,search_docs 超时被忽略 trace_id=7f3a… · 总耗时 24.8s · 状态=成功 两个慢工具约 13 秒 chat #1 1.9s get_order 0.3s chat #2 1.6s get_refunds 8.0s ① 空结果:首次偏离 get_payment 0.4s chat #3 2.8s ② 把“无数据”当成“已撤销” search_docs 5.2s ③ 超时被忽略 chat #4 4.6s 0s 5s 10s 15s 20s 25s
图 3 · 按时间展开这条 Trace:第 4 步空结果是第一次偏离,模型调用本身并不慢

读 Trace 时按顺序问五个问题:

  1. 结果对吗? 结论是“退款已被撤销”,但人工核查发现退款单正常在处理中。
  2. 哪一步第一次偏离? get_refunds 用了 8 秒且返回空数组,这不正常。进一步查工具日志,发现退款查询服务超时后返回了空结果,而不是报错。
  3. 模型的判断是否基于证据? 第 3 次模型调用把“没有数据”当成了“没有退款记录”,这是工具返回值设计的问题,也暴露了指令里缺少“数据缺失时不要下结论”的规则。
  4. 有没有被忽略的错误? search_docs 超时了,模型仍然输出了确定性的结论,没有降级为“无法判断”。
  5. 耗时和成本花在哪? 近 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)接入追踪:

  1. 开启所用 SDK 的内置追踪,或手动为每次模型调用和工具调用记录开始时间、结束时间、Token 与参数。
  2. 故意制造一次工具失败(例如传入不存在的城市,或让工具超时)。
  3. 打开这条 Trace,用上文的“五个问题”写一段 200 字的复盘:哪一步第一次偏离、模型如何应对、应该修改工具、指令还是验证逻辑。

延伸阅读

下一步

单条 Trace 回答“这一次为什么”,下一课「日志、指标与告警」讲如何把成千上万条运行汇总成看板,并在出问题时第一时间收到告警。

试试:上下文、MCP、沙箱、Prompt 注入、外链