本文结论

  • Agent tracing 是对一次 Agent workflow 的结构化执行记录,不是普通日志的简单加长版。
  • Trace 记录端到端任务,span 记录一段操作,event 记录某个时间点发生的事情。
  • 对 Agent 来说,tracing 的价值在于解释“为什么走到这个结果”,尤其适合分析工具调用、handoff、guardrail 和上下文问题。
  • Eval 告诉你任务是否成功,trace 告诉你成功或失败是怎么发生的。

适合谁读

  • 正在开发 Agent workflow、工具调用系统或多步骤 LLM 应用的人。
  • 已经遇到“最终答案错了,但不知道哪一步错了”的开发者。
  • 想理解 Agent eval、trace replay 和可观测性之间关系的人。

随着 LLM 应用从简单对话逐渐发展到 Agent workflow,系统的复杂度也在明显增加。

一个普通 Chatbot 通常可以被理解成一次模型调用:

用户输入 -> LLM -> 模型输出

但 Agent workflow 往往不是这样。它可能包含多轮模型生成、工具调用、上下文更新、规则检查、任务转交、失败重试,甚至还可能需要人工确认。

一个典型 Agent run 可能更像这样:

用户输入任务
  -> Agent 接收任务
  -> LLM 生成下一步动作
  -> 调用工具
  -> 工具返回结果
  -> LLM 基于结果继续生成
  -> 再次调用工具
  -> 触发 guardrail 检查
  -> 最终输出结果

这意味着 Agent 的执行过程不再是一个单点动作,而是一条由多个操作组成的链路。

如果这条链路失败,只看最终输出通常无法判断问题在哪里。失败可能来自模型理解错误、工具参数错误、工具执行失败、上下文丢失、guardrail 拦截、状态流转异常,也可能是多个问题叠加。

这就是 Agent 系统需要 tracing 的原因。

Tracing 的作用,是记录一次 Agent run 中发生的关键操作,并把这些操作组织成一条可观察、可回放、可调试的执行轨迹。

为什么 Agent Run 需要 Tracing

Agent run 需要 tracing,核心原因是:Agent 的失败往往不是单点失败,而是链路失败。

在传统 Web 服务中,一次请求失败,通常可以通过日志、错误码、调用栈、metrics 来定位问题。例如接口返回 500,可以查看异常堆栈;数据库查询慢,可以看 SQL 耗时;服务之间调用失败,可以看 RPC 日志。

但 Agent workflow 的问题会更绕一点。

例如,最终结果错误时,真正原因可能是:

LLM 一开始误解了用户任务
LLM 选择了错误工具
function call 参数不合法
工具执行失败
工具返回结果太长,关键信息被截断
模型没有正确理解 observation
上下文中混入了错误信息
状态机提前停止
Agent 在错误步骤中反复循环
guardrail 阻止了某个操作
handoff 转交给了不合适的 agent

这些问题只看最终回答很难判断。Tracing 的价值就在于,它可以把一次 Agent run 展开成多个可观察步骤,让开发者知道:

这次任务经历了哪些步骤?
每一步输入是什么?
每一步输出是什么?
哪一步失败了?
失败原因是什么?
模型为什么调用这个工具?
工具返回后模型又做了什么?
最终结果是如何产生的?

所以,Agent tracing 通常服务于几个目标:

可回放:复现一次 Agent run 的执行过程
可观测:看到每一步耗时、状态、输入输出和错误
可调试:定位失败发生在哪个环节
可评测:分析 Agent 成功率和失败类型
可优化:发现性能瓶颈、成本瓶颈和行为问题

Tracing 不是普通日志的替代品,而是 Agent workflow 的执行记录结构。普通日志更像散点记录,trace 则更像一棵执行树。

Trace 是什么

Trace 表示一次完整的端到端操作。

在 Agent 场景中,可以把一个 trace 理解为一次完整的 Agent run。例如用户发起任务:

帮我分析这个项目的代码结构,并找出潜在问题

从 Agent 接收这条任务开始,到它完成多轮模型调用、工具调用、上下文处理,并最终输出结果为止,这整个过程就是一个 trace。

Trace 通常会包含一些全局属性:

workflow_name
trace_id
group_id
metadata
started_at
ended_at
status

workflow_name 表示逻辑上的 workflow 或应用名称,例如 Code generationCustomer serviceData analysisResearch assistant

trace_id 是这次 trace 的唯一标识。

group_id 可以用来关联多个 trace。例如同一个聊天线程里,用户可能连续发起多次 Agent run。每次 run 都是一个独立 trace,但它们可以通过同一个 group_id 关联起来。

可以这样理解:

一个 conversation / thread
  -> 可能包含多个 trace

一个 trace
  -> 表示一次完整 Agent run

一个 trace
  -> 由多个 span 组成

Trace 关注的是整体链路,而不是某一个具体步骤。

Span 是什么

Span 是 trace 中的一个具体工作单元。它表示一个有开始时间和结束时间的操作。

在 Agent workflow 中,以下操作都可以是 span:

一次 agent 运行
一次 LLM generation
一次 function tool call
一次 guardrail 检查
一次 handoff
一次 speech-to-text
一次 text-to-speech
一次自定义业务操作

Span 的关键特征是:它有持续时间。

也就是说,它不是一个瞬间发生的事件,而是一个从开始到结束的操作。例如,一次 LLM 调用可以是一个 span:

Span: generation
  started_at: ...
  ended_at: ...
  input: messages, tools, model config
  output: model response, tool calls
  status: ok

一次工具调用也可以是一个 span:

Span: function
  started_at: ...
  ended_at: ...
  input: tool name, tool arguments
  output: tool result
  status: ok / error

这里有一个容易混淆的点:function call result 通常不是单独的 span,而是 function call span 的 output。

也就是说:

执行工具这个过程 = span
工具执行结果 = span.output

同理:

LLM generation 这个过程 = span
LLM response = span.output

只有当响应处理本身足够复杂,例如 JSON 解析、参数校验、结果压缩、错误恢复,才有必要把这些处理步骤继续拆成新的 span。

Span 的父子关系

Span 不只是平铺记录,它们通常有父子关系。

例如,一次 Agent run 可以包含多次 LLM generation 和 function call:

Trace: Agent workflow
  Span: agent
    Span: generation
    Span: function.lookup_order
    Span: generation
    Span: function.send_email
    Span: generation

这表示:

  • 整个 workflow 是一个 trace
  • agent 的运行过程是一个大的 span
  • 每次模型生成、工具调用都是它下面的子 span

如果发生 handoff,结构可能会更复杂:

Trace: Customer service workflow
  Span: agent.support_agent
    Span: generation
    Span: handoff.to_refund_agent
      Span: agent.refund_agent
        Span: generation
        Span: function.create_refund

这种层级关系很重要。它可以帮助我们理解:

某次工具调用属于哪次 agent run
某次模型生成是否发生在 handoff 之前
某个错误是父流程导致的,还是子流程内部导致的
一次长 workflow 中各阶段的耗时分布

Span 通常通过 trace_id 归属于某个 trace,并通过 parent_id 指向父 span。

Event 是什么

Event 表示某个 span 内部发生的瞬时事件。

它和 span 的区别是:

Span 有开始和结束,表示一段操作
Event 是某个时间点发生的事情

在 Agent workflow 中,event 适合记录这些内容:

模型生成了 tool call
JSON 解析失败
开始重试
请求用户授权
用户批准了工具调用
上下文窗口超限
状态发生跳转
流式输出收到一个 chunk
用户中断了任务

例如,在一次 LLM generation span 中,可以记录一个 event:

{
  "name": "tool_call_generated",
  "timestamp": "2026-07-01T10:00:00Z",
  "attributes": {
    "tool_name": "search_docs",
    "step": 2
  }
}

这里的含义是:在这次模型生成过程中,模型在某个时间点生成了一个 tool call。

但真正执行这个工具,则应该是另一个 span。

模型决定调用某个工具 = event
系统真正执行这个工具 = span

因为“决定调用工具”是一个瞬间动作,而“执行工具”有开始、结束、耗时、输入、输出和错误状态。

Attribute 是什么

Attribute 是附加在 trace、span 或 event 上的结构化元数据。

它通常用于描述一个操作的属性,方便后续过滤、检索、聚合和分析。

常见 attribute 包括:

step
model
tool_name
retry_count
status
error_type
input_tokens
output_tokens
duration_ms
prompt_version
tool_schema_version

例如,一个 LLM generation span 可以有这些 attributes:

{
  "name": "generation",
  "attributes": {
    "model": "model-name",
    "step": 3,
    "input_tokens": 1200,
    "output_tokens": 300,
    "finish_reason": "tool_calls"
  }
}

一个 function call span 可以有这些 attributes:

{
  "name": "function.lookup_order",
  "attributes": {
    "tool_name": "lookup_order",
    "step": 3,
    "retry_count": 0,
    "status": "ok"
  }
}

可以这样区分:

Trace 适合记录“整次 workflow”
Span 适合记录“这一步操作本身”
Event 适合记录“这一步里发生了什么”
Attribute 适合记录“这一步是什么样的”

这套结构让 Agent workflow 不再是一个不可见的黑盒,而是变成一棵可以检查的执行树。

一个完整 Trace 示例

假设用户请求:

帮我查一下订单状态,如果已经发货,告诉我物流信息。

对应 trace 可能是:

Trace: Customer service workflow
  attributes:
    workflow_name: "Customer service"
    group_id: "thread_123"

  Span: agent.support_agent
    attributes:
      agent_name: "Support Agent"

    Span: generation
      attributes:
        step: 1
        finish_reason: "tool_calls"
      events:
        - tool_call_generated
      output:
        tool_call: lookup_order

    Span: function.lookup_order
      attributes:
        tool_name: "lookup_order"
        step: 1
        status: "ok"
      input:
        order_id: "..."
      output:
        order_status: "shipped"
        tracking_id: "..."

    Span: generation
      attributes:
        step: 2
        finish_reason: "stop"
      output:
        final_answer: "你的订单已经发货,物流单号是..."

如果这次任务失败,例如工具参数缺失,trace 可能变成:

Trace: Customer service workflow
  Span: agent.support_agent
    Span: generation
      events:
        - tool_call_generated
      output:
        tool_call: lookup_order

    Span: function.lookup_order
      attributes:
        status: "error"
        error_type: "missing_required_argument"
      input:
        order_id: null
      error:
        message: "order_id is required"

    Span: generation
      output:
        final_answer: "抱歉,我无法查询订单状态。"

通过这条 trace 可以清楚看到:失败不是工具不可用,而是模型调用工具时缺少必要参数。

这就是 tracing 和普通最终日志最大的区别。最终日志只能告诉你“失败了”,trace 可以告诉你“失败是怎么发生的”。

Handoff 和 Guardrail 为什么适合做 Span

Handoff 表示控制权从一个 agent 转移到另一个 agent。

例如:

用户询问退款问题
  -> Support Agent 判断这是退款请求
  -> handoff 给 Refund Agent
  -> Refund Agent 查询订单并处理退款

这个过程中,handoff 不是一条简单日志,而是一次有输入、有输出、有开始和结束的操作。它可能成功,也可能失败;它可能携带上下文,也可能触发新的 agent run。

因此,handoff 适合记录为 span。一个 handoff span 可以包含:

from_agent
to_agent
handoff_reason
handoff_payload
status
started_at
ended_at

Guardrail 也是类似的。它通常用于检查模型输入、模型输出或工具调用是否符合规则,例如:

检查用户请求是否允许
检查模型输出是否包含敏感内容
检查工具调用是否越权
检查某个操作是否需要用户确认

Guardrail 不是一个简单事件,而是一次判断过程。它有输入、规则、判断结果、可能的拦截原因。因此,guardrail 也适合作为 span。

这对调试非常重要。因为有些 Agent run 的失败并不是模型或工具的问题,而是被 guardrail 拦截了。如果没有 guardrail span,开发者可能只能看到任务没有继续执行,却不知道为什么停止。

Custom Span 和 Custom Event

默认 tracing 通常只能覆盖框架已知的操作,例如 LLM generation、function call、handoff、guardrail。

但实际业务系统中,可能还有很多自定义逻辑值得记录。

例如:

从数据库加载用户配置
执行权限检查
调用内部服务
读取缓存
命中某个业务规则
进行结果格式化
执行自定义评估

这些操作可以用 custom span 或 custom event 记录。选择标准仍然是:

如果它有开始和结束,记录为 custom span
如果它是某个时间点发生的事情,记录为 custom event
如果它是描述信息,记录为 attribute

例如:

读取用户配置 = custom span
缓存命中 = custom event
cache_key = attribute

这样可以把业务侧逻辑和 Agent 框架内部逻辑放在同一条 trace 中观察。

Sensitive Data:Tracing 中的敏感数据问题

Tracing 会记录 Agent 的执行过程,因此很容易捕获敏感数据。

例如:

用户原始输入
LLM messages
模型输出
function call 参数
工具返回结果
文件内容
命令输出
API 返回数据
音频输入输出

这些数据对调试很有用,但在生产环境中也可能带来隐私和合规风险。

因此,tracing 系统一般需要支持是否记录敏感数据的配置。例如:

是否记录完整 LLM 输入输出
是否记录完整 function call 输入输出
是否记录音频原始数据
是否只记录摘要、hash 或 metadata

这里需要在两类目标之间做取舍:

调试可用性
隐私与合规安全

开发环境可以记录更完整的数据,方便定位问题。生产环境则应该更谨慎,默认只记录必要的结构化信息、错误类型、耗时、token 使用量和摘要。

Long-running Worker 中的 Trace 导出

在一些长时间运行的 worker 中,trace 不一定会在任务结束后立刻出现在 dashboard。

例如:

Celery
RQ
Dramatiq
FastAPI background tasks

这类系统中,trace processor 通常会批量导出数据。它可能每隔几秒导出一次,也可能等队列达到一定大小后再导出。

这种方式对性能更友好,但会带来一个现象:任务已经执行结束,但 trace dashboard 里还没有立刻显示。

如果需要在一个任务结束后立刻保证 trace 被导出,就需要显式 flush。

概念上可以理解为:

trace context 结束
  -> trace 构建完成
  -> flush buffered traces
  -> dashboard 可以看到完整数据

需要注意的是,flush 应该发生在 trace 结束之后。否则可能会导出一个还没构建完整的 trace。

Tracing 与 Eval 的关系

Tracing 和 eval 是两个不同但互补的系统。

Eval 关心的是:

Agent 最终有没有完成任务?
答案是否正确?
工具调用是否符合预期?
是否违反规则?

Tracing 关心的是:

Agent 是怎么一步步走到这个结果的?
中间发生了哪些操作?
哪一步导致了成功或失败?

只做 eval,可能只知道任务失败了,但不知道为什么失败。

只做 tracing,能看到执行过程,但不一定能自动判断结果好坏。

二者结合起来,才能做更深入的失败分析。例如,一个任务 eval 失败后,可以通过 trace 判断它属于哪类失败:

模型理解错误
工具选择错误
工具参数错误
工具执行错误
上下文丢失
guardrail 拦截
handoff 错误
最终答案格式错误

如果修改了 prompt、tool schema、guardrail、handoff 策略或上下文管理策略,就可以通过 eval 看成功率变化,通过 trace 看失败原因变化。

小结

Agent tracing 的核心,是用结构化方式记录一次 Agent workflow 的完整执行过程。

几个关键概念可以这样理解:

Trace:一次完整的端到端 workflow
Span:workflow 中一个有开始和结束的操作
Event:span 内某个时间点发生的事件
Attribute:trace、span 或 event 上的结构化元数据

在 Agent 系统中,常见 span 包括:

agent
generation
function
guardrail
handoff
transcription
speech
custom

Tracing 的价值不是“日志更详细”,而是让 Agent 的执行过程变得可检查。

它可以帮助开发者回答:

Agent 做了什么?
为什么这么做?
哪一步失败了?
失败来自模型、工具、上下文、guardrail 还是 handoff?
如何复现这次失败?
如何评估和优化后续行为?

当 Agent workflow 变得越来越复杂时,tracing 会从一个辅助调试工具,变成理解 Agent 行为的基础设施。

常见问题

Agent tracing 和普通日志有什么区别?

普通日志通常是散点记录,trace 更强调一次任务的端到端结构。它会把模型生成、工具调用、guardrail、handoff 等步骤组织成可以追踪父子关系和时间顺序的执行轨迹。

Trace、span、event 应该怎么区分?

Trace 表示一次完整任务,span 表示任务中的一段操作,event 表示某个时间点发生的事件。比如一次 Agent run 是 trace,一次工具调用是 span,JSON 解析失败可以是 event。

为什么 Agent eval 需要 trace?

只看最终答案很难判断失败原因。Trace 可以告诉你 Agent 是否选错工具、参数是否错误、上下文是否丢失、是否触发 guardrail,以及失败发生在哪一步。

延伸阅读: