本文结论
- 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 generation、Customer service、Data analysis、Research 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,以及失败发生在哪一步。
延伸阅读: