Agent 工程体系 · 第 82/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。
Agent 可观测性:Trace、Span、模型回合、工具调用、Token 和关联 ID
Agent 的一次请求,通常不是一次模型调用,而是一条动态执行链:
- 接收用户请求;
- 选择或创建 Agent;
- 调用模型;
- 解析模型输出;
- 调用工具、检索系统或子 Agent;
- 将工具结果重新交给模型;
- 继续循环,直到得到终态、触发限制或失败。
如果系统只记录最终回答,工程师只能知道“结果是什么”,却不知道:
- 模型为什么选择了这个工具;
- 工具参数是模型生成的,还是服务端补全的;
- 哪一次模型调用消耗了最多 Token;
- 延迟发生在模型、工具、排队还是重试;
- 工具失败后 Agent 是否正确恢复;
- 多个并发任务、重试请求和异步回调是否属于同一业务请求。
Agent 可观测性的核心,不是“多打日志”,而是把一次执行表示成一棵带有时间、父子关系、输入输出和状态的轨迹树。OpenAI Agents SDK 将一次工作流表示为 Trace,将其中的具体操作表示为 Span;Trace 可以覆盖模型生成、工具调用、交接、Guardrail 和自定义事件。(openai.github.io)
一、先建立执行模型:Agent 不是函数,而是状态机
定义一次 Agent 运行:
其中:
- :用户输入;
- :初始状态,包括会话、权限、工具、上下文等;
- :Agent 的决策策略;
- :外部环境,包括模型服务、工具服务、数据库和网络;
- :最终输出或失败终态。
在第 次循环中,Agent 根据当前状态 生成模型请求:
模型返回:
如果 是工具调用,则执行:
并更新状态:
如果 是最终答案,则运行进入终态;如果发生异常,则进入错误、恢复、回滚或人工接管路径。
这个模型直接决定了可观测性应该记录什么:
- Trace 记录整个 ;
- Span 记录
ModelRequest、Model、Tool、Update等可定位的操作; - 模型回合记录一次循环 ;
- Token 记录模型请求和输出的资源消耗;
- 关联 ID 记录这些事件如何属于同一棵执行树。
二、Trace:一次端到端工作流
2.1 Trace 的定义
Trace 是一次完整工作流的端到端记录。它通常从业务请求进入开始,在最终回答、失败、超时或取消时结束。
可以把 Trace 看成一个容器:
Trace
└── Span
├── Span
├── Span
└── Span
在 OpenAI Agents SDK 中,Trace 具有以下关键属性:
workflow_name:逻辑工作流名称;trace_id:Trace 的唯一标识;group_id:用于把同一会话或流程中的多条 Trace 归组;metadata:附加业务元数据;disabled:是否禁用记录。
SDK 文档规定,自动生成的 trace_id 应符合 trace_<32_alphanumeric> 格式;group_id 可以用于关联同一对话中的多个 Trace。(openai.github.io)
需要区分:
- 一次用户发送消息,通常对应一次 Trace;
- 一个长期会话,可以包含多个 Trace;
- 一个重试请求,可能是新的 Trace,但应通过业务 ID 或
group_id关联; - 一个异步任务,可以有独立 Trace,但不能丢失其来源请求的关联信息。
例如:
conversation_id = conv_42
request_id = req_20260901_001
trace_id = trace_abc...
第一轮用户消息:
group_id = conv_42
trace_id = trace_abc...
第二轮用户消息:
group_id = conv_42
trace_id = trace_def...
这里两个 Trace 不应强行合并,否则单轮延迟、Token 和终态会混在一起;但它们应能通过 group_id 或业务字段被查询到同一会话下。
2.2 Trace 的开始和结束
Trace 必须有明确生命周期:
from agents import Agent, Runner, trace
agent = Agent(
name="订单助手",
instructions="帮助用户查询订单状态。",
)
async def handle_request(user_text: str):
with trace(
"order_support",
group_id="conversation_42",
metadata={
"request_id": "req_20260901_001",
"tenant_id": "tenant_demo",
},
):
result = await Runner.run(agent, user_text)
return result.final_output
with trace(...) 的价值不只是语法简洁:它能保证 Trace 在代码块退出时结束,包括异常退出路径。SDK 也支持手动调用 start() 和 finish(),但手动模式必须自行保证所有异常路径都执行结束逻辑。(openai.github.io)
错误处理应记录“失败发生在哪里”,而不是只在最外层打印异常:
async def handle_request(user_text: str):
try:
with trace(
"order_support",
group_id="conversation_42",
metadata={"request_id": "req_20260901_001"},
):
result = await Runner.run(agent, user_text)
return result.final_output
except TimeoutError:
# Trace 已经因为上下文退出而结束
# 这里负责业务层响应和告警
raise
2.3 长时间运行任务的导出风险
Tracing 通常不是同步写入远端,而是先进入内存队列,再批量导出。Agents SDK 的默认 BatchTraceProcessor 会在后台批量发送,并在进程退出时尝试最终刷新;因此长时间运行的 Worker 中,任务已经结束但 Trace 尚未立即出现在后台是可能的。若一个任务结束就必须保证当前 Trace 被导出,应在 Trace 上下文退出后调用 flush_traces()。(openai.github.io)
from agents import Runner, flush_traces, trace
def process_job(prompt: str):
try:
with trace("background_job"):
result = Runner.run_sync(agent, prompt)
return result.final_output
finally:
# 必须放在 with trace(...) 之后
flush_traces()
顺序不能反过来:
# 不推荐
flush_traces()
with trace("background_job"):
...
因为此时 Trace 还没有结束,部分 Span 可能仍处于打开状态,刷新动作无法表达完整的终态。
三、Span:Trace 中可定位的操作区间
3.1 Span 的定义
Span 是一个有开始时间和结束时间的操作区间。一个 Span 至少应能回答:
- 操作是什么;
- 何时开始;
- 何时结束;
- 属于哪个 Trace;
- 父操作是什么;
- 输入、输出和错误状态是什么。
可抽象为:
Span 耗时为:
但要注意:父 Span 的耗时不能简单等于所有子 Span 耗时之和。并发工具调用可能重叠,网络排队和本地处理也可能不属于任何子 Span。
3.2 常见 Span 类型
Agent 系统中,至少应区分以下操作:
| Span 类型 | 表示内容 | 典型问题 |
|---|---|---|
| Agent Span | 某个 Agent 的逻辑作用域 | 是否发生了错误交接 |
| Task Span | 一次顶层 Runner 调用 | 一次运行整体耗时和终态 |
| Turn Span | Agent 循环中的一次回合 | 哪个回合发生了重复或发散 |
| Generation Span | 一次模型生成 | 模型、配置、输入输出和 Token |
| Function Span | 一次函数或工具调用 | 参数、结果、异常和耗时 |
| Handoff Span | 控制权从一个 Agent 转移到另一个 Agent | 路由是否正确 |
| Guardrail Span | 输入或输出安全检查 | 哪个检查阻断了流程 |
| Custom Span | 业务自定义操作 | 数据库、缓存、回滚、人工审批 |
Agents SDK 当前文档明确区分了 task_span、turn_span、function_span 和 generation_span 等 Span 创建接口。(openai.github.io)
3.3 父子关系不是装饰,而是诊断依据
一次典型轨迹可以表示为:
Trace: 客服请求
└── Task Span: Runner.run
└── Agent Span: 客服 Agent
├── Turn Span: turn=1
│ └── Generation Span: 识别意图
├── Turn Span: turn=2
│ ├── Generation Span: 生成工具调用
│ └── Function Span: query_order
└── Turn Span: turn=3
└── Generation Span: 生成最终回答
由此可以定位不同类别的故障:
Generation Span成功、Function Span参数错误:模型决策或 Schema 问题;- 工具返回成功、下一次
Generation Span仍重复调用:状态更新或工具结果注入问题; Function Span很快开始但长时间不结束:工具超时或下游阻塞;- 多个工具 Span 并行且总耗时接近最慢者:并发执行有效;
- 父 Span 结束但子 Span 未结束:生命周期管理或取消传播有缺陷。
四、模型回合:Turn 不等于模型调用
4.1 模型回合的定义
模型回合,这里指 Agent 循环的一次迭代,通常称为 turn:
OpenAI Agents SDK 将 turn_span 定义为“一次 Agent loop turn”。(openai.github.io)
一个回合可能包含:
- 构造上下文;
- 一次或多次模型请求;
- 解析工具调用;
- 并行执行多个工具;
- 把工具结果写回上下文;
- 判断是否继续。
因此必须区分:
- Turn:Agent 的一次循环;
- Generation:一次具体模型生成;
- Tool Call:一次具体工具执行。
4.2 完整算例
用户请求:
查询订单 1001 的物流状态,如果已签收,告诉我签收时间。
可能产生以下轨迹:
Turn 1
└── Generation 1
输入:用户问题 + 工具定义
输出:调用 query_order(order_id="1001")
Function 1
输入:{"order_id": "1001"}
输出:{"status": "shipped", "tracking_no": "SF123"}
Turn 2
└── Generation 2
输入:用户问题 + query_order 结果 + 工具定义
输出:调用 query_logistics(tracking_no="SF123")
Function 2
输入:{"tracking_no": "SF123"}
输出:{"status": "delivered", "delivered_at": "2026-09-01T10:20:00+08:00"}
Turn 3
└── Generation 3
输入:上述全部上下文
输出:最终回答
这里有三个 Turn、三个 Generation 和两个工具调用。若监控只统计“模型请求次数”,会漏掉 Agent 实际经过了几个决策回合;若只统计“回合数”,又无法判断某个回合内部是否进行了多次模型请求。
4.3 不能把回合数当作质量指标
较少的 Turn 不一定更好:
方案 A:
Turn 1 -> 错误工具 -> Turn 2 -> 重试 -> Turn 3 -> 终态
方案 B:
Turn 1 -> 正确工具 -> Turn 2 -> 终态
方案 B 通常更高效,但如果方案 A 在第一次工具失败后正确恢复,而方案 B 直接返回错误,单纯比较 Turn 数会得出错误结论。
因此应同时记录:
turn_index;- 每个 Turn 的模型调用数;
- 每个 Turn 的工具调用数;
- Turn 是否产生有效状态变化;
- Turn 结束原因;
- 累计 Token;
- 是否发生重试、回滚或人工接管。
一个有用的效率指标是:
若连续两个 Turn 的工具集合、参数和状态摘要都不变,则可能发生循环:
这比单纯设置最大回合数更有诊断价值。
五、模型调用与工具调用:必须记录“意图”和“事实”
5.1 模型调用记录什么
一次模型生成至少需要记录:
{
"span_type": "generation",
"model": "model-name",
"model_config": {
"temperature": 0,
"max_output_tokens": 800
},
"input_message_count": 8,
"output_item_types": ["tool_call"],
"usage": {
"input_tokens": 2300,
"output_tokens": 94,
"total_tokens": 2394
},
"status": "ok"
}
Agents SDK 的 GenerationSpanData 包含模型输入、输出、模型名称、模型配置和 usage 数据。(openai.github.io)
生产环境不一定要保存完整 Prompt 和完整输出。可以采用分层策略:
- 调试环境:保存完整内容;
- 生产默认:保存摘要、哈希、消息类型和大小;
- 高风险字段:脱敏或不采集;
- 事故样本:在访问控制下保留完整证据。
SDK 文档特别提醒,Generation Span 会保存模型输入输出,Function Span 会保存函数输入输出,这些内容可能包含敏感数据;可通过 trace_include_sensitive_data 关闭敏感数据采集,且该选项默认开启。(openai.github.io)
5.2 工具调用记录什么
工具 Span 不能只记录:
query_order succeeded
至少应记录以下字段:
{
"span_type": "function",
"tool_name": "query_order",
"call_id": "call_01",
"arguments": {
"order_id": "1001"
},
"argument_source": {
"order_id": "model"
},
"downstream_request_id": "order-api-req-7788",
"result_summary": {
"status": "shipped",
"tracking_no_present": true
},
"status": "ok",
"duration_ms": 86
}
其中 argument_source 很重要。假设模型生成:
{"order_id": "1001"}
服务端却把它改成了当前登录用户的订单:
{"order_id": "1002"}
如果只记录最终发给下游的参数,就无法判断错误来自模型、参数校验、权限过滤还是业务重写。
对写操作还必须记录副作用状态:
{
"side_effect": "payment_refund",
"idempotency_key": "refund-order-1001",
"authorization": "approved",
"commit_state": "committed",
"compensation_state": "not_needed"
}
工具调用的成功至少有三层含义:
- 调用请求被发出;
- 下游返回了成功响应;
- Agent 状态正确吸收了结果。
第三层失败时,工具和 HTTP 都可能显示成功,但最终 Agent 仍然错误。
六、Token:资源计量,不是质量计量
6.1 Token 的基本字段
对一次模型请求,常见字段包括:
input_tokens:输入 Token;output_tokens:输出 Token;total_tokens:输入与输出的总数;cached_input_tokens:命中缓存的输入 Token;reasoning_tokens:如果模型或适配器提供,表示推理相关输出消耗;requests:模型请求次数。
Agents SDK 的 Usage 对象区分单次请求与运行累计值;其中输入、输出和总 Token 可以按请求记录,也可以在一次运行中聚合。(openai.github.io)
单个模型调用:
一次 Agent 运行:
其中:
- :第 次模型请求的输入 Token;
- :第 次模型请求的输出 Token;
- :模型请求次数。
不要把一次模型调用的 Token 与一次用户请求的 Token 混为一谈。Agent 可能经过多个 Turn,因此:
一次用户请求
├── Generation 1: 1,200 input + 80 output
├── Generation 2: 2,100 input + 120 output
└── Generation 3: 2,900 input + 160 output
总 Token 是三次 Generation 的和,而不是最后一次调用的 usage。
6.2 为什么输入 Token 往往越来越大
工具结果会被追加到上下文:
因此即使每次模型输出都很短,后续输入 Token 仍可能增长。一个常见的异常轨迹是:
Turn 1: input=1,000
Turn 2: input=2,400
Turn 3: input=5,100
Turn 4: input=9,800
这可能来自:
- 工具返回了完整原始文档;
- 每轮重复注入工具定义;
- 历史消息未压缩;
- 失败重试不断追加错误上下文;
- 子 Agent 输出未进行边界控制。
诊断时应把 Token 与上下文来源关联起来,而不是只看总量:
{
"input_tokens": 5100,
"context_breakdown": {
"system_prompt": 900,
"conversation_history": 1800,
"tool_definitions": 700,
"tool_results": 1500,
"current_user_input": 200
}
}
这个拆分不是模型服务必然提供的字段,而是应用层建议记录的审计信息。它可以通过消息来源标签、序列化前统计或上下文构造器实现。
6.3 Token 与费用、延迟和质量的关系
Token 通常影响成本和延迟,但不是简单的一一对应关系:
其中 和 是对应模型和计费类别的单价。
但以下情况会破坏简单估算:
- 输入缓存和非缓存输入价格不同;
- 不同模型价格不同;
- 推理 Token 可能单独计量;
- 工具耗时可能远大于模型耗时;
- 并发工具调用会改变总延迟但不一定改变 Token。
因此生产面板至少应按以下维度聚合:
workflow_name
model
tenant_id
trace_status
tool_name
turn_count
model_request_count
input_tokens
output_tokens
cached_input_tokens
latency
七、关联 ID:让日志、Trace、工具和业务记录连成一条证据链
7.1 不同 ID 的职责
一个成熟系统通常需要多个 ID:
| ID | 作用 | 生命周期 |
|---|---|---|
trace_id |
标识一次 Agent 工作流 | 一次端到端运行 |
span_id |
标识一个操作区间 | 一个 Span |
parent_id |
指向父 Span | 当前 Span 的结构关系 |
group_id |
关联同一会话或长期流程 | 多次 Trace |
request_id |
标识一次业务入口请求 | 一次 HTTP、RPC 或消息消费 |
tool_call_id |
标识一次模型发起的工具调用 | 一次工具意图 |
downstream_request_id |
标识下游服务请求 | 一次下游调用 |
idempotency_key |
防止副作用重复提交 | 一次业务操作 |
这些 ID 不能互相替代。
例如:
request_id = req-001
trace_id = trace-aaa
span_id = span-tool-01
tool_call_id = call-xyz
downstream_request_id = order-api-7788
idempotency_key = refund-order-1001
含义分别是:
req-001:用户请求进入了 Agent 服务;trace-aaa:这次 Agent 工作流;span-tool-01:其中某个工具操作;call-xyz:模型要求调用工具的那一次意图;order-api-7788:工具访问订单服务的网络请求;refund-order-1001:退款副作用的幂等边界。
7.2 Span 的结构关联
一个 Span 的父子关系可表示为:
trace_id = trace-aaa
└── span_id = span-task
└── span_id = span-turn-2
├── span_id = span-generation-2
└── span_id = span-tool-1
其中:
span-tool-1.parent_id = span-turn-2
span-tool-1.trace_id = trace-aaa
OpenAI Agents SDK 会自动将 Span 放入当前 Trace,并嵌套到最近的当前 Span 下;其 Python 实现使用 contextvar 跟踪当前 Trace 和 Span,从而支持并发上下文隔离。(openai.github.io)
但“自动传播”不是“跨进程自动传播”。当工具通过 HTTP、消息队列或任务系统离开当前进程时,应用必须显式传递关联信息,例如:
X-Request-ID: req-001
X-Agent-Trace-ID: trace-aaa
X-Agent-Parent-Span-ID: span-tool-1
下游服务收到后,应创建自己的服务 Span,并保存来源字段,而不是直接伪造父子关系。这样既能保留 Agent 轨迹,也能避免不同观测系统之间的 ID 语义冲突。
7.3 重试与关联 ID
重试是最容易造成重复计数的地方。
Trace trace-aaa
├── Function Span tool_call=call-1
│ └── downstream_request_id=req-down-1 -> timeout
└── Function Span tool_call=call-1
└── downstream_request_id=req-down-2 -> success
这里:
- 两个下游请求是不同网络尝试;
tool_call_id可以相同,表示同一个工具意图;- 每次尝试应有新的 Span 或 attempt 序号;
- 写操作必须复用同一个
idempotency_key。
如果重试创建了新的 Trace,却没有保存原 Trace ID,那么事故复盘时会误以为是两次独立用户请求。
八、一个可执行的最小观测实现
下面示例使用 Python Agents SDK 的公开 tracing 结构,演示手动创建业务 Span。自动产生的 Agent、模型和工具 Span 仍应优先使用 SDK 默认能力;手动 Span 适合补充数据库、缓存、审批和补偿等业务操作。SDK 文档也说明,一般不需要手动创建所有 Span,必要时可以使用 custom_span()。(openai.github.io)
import json
from agents import Agent, Runner, custom_span, trace
agent = Agent(
name="订单助手",
instructions=(
"你是订单助手。查询订单时使用工具;"
"不得猜测订单状态;工具失败时明确说明失败。"
),
)
async def save_audit_record(request_id: str, result: str) -> None:
# 这里用内存操作代替真实数据库,示例可直接运行。
with custom_span(
"audit_record",
data={
"request_id": request_id,
"result_length": len(result),
},
):
print(json.dumps({
"event": "audit_saved",
"request_id": request_id,
}, ensure_ascii=False))
async def handle_request(user_text: str, request_id: str, conversation_id: str):
with trace(
"order_support",
group_id=conversation_id,
metadata={
"request_id": request_id,
"service": "agent-api",
},
):
result = await Runner.run(agent, user_text)
await save_audit_record(
request_id=request_id,
result=result.final_output,
)
return result.final_output
这个示例的生命周期是:
- 创建 Trace;
Runner.run()产生 Agent 运行及其内部 Span;- Agent 可能产生模型生成和工具调用 Span;
- 业务代码创建
audit_record自定义 Span; with trace(...)退出,Trace 结束;- 返回最终答案。
风险在于 result_length 不是业务正确性的证明。审计记录应同时保存:
trace_id;- Agent 最终终态;
- 工具调用是否成功;
- 是否发生恢复;
- 关键业务对象 ID;
- 数据脱敏后的结果摘要。
九、用 Trace 评测 Agent,而不是只评测最终答案
最终答案评测只能回答“说得像不像正确”,不能回答“过程是否正确”。
OpenAI 的 Agent Evals 指南建议,在仍处于行为调试阶段时先使用 Trace;Trace grading 可以检查工具选择、交接、指令或安全策略违反,以及端到端行为是否因 Prompt 或路由变化而改善。(developers.openai.com)
9.1 步骤正确性
给定任务:
查询订单 -> 查询物流 -> 根据签收状态回答
可以定义期望约束:
实际轨迹:
这不是简单的最终答案错误,而是效率和步骤正确性问题。评测器应分别给出:
{
"step_correct": true,
"duplicate_tool_call": true,
"terminal_answer_correct": true
}
9.2 参数正确性
工具名正确不代表调用正确:
{
"expected": {
"tool": "query_order",
"arguments": {
"order_id": "1001"
}
},
"actual": {
"tool": "query_order",
"arguments": {
"order_id": "10001"
}
}
}
参数评测需要区分:
- 字段是否齐全;
- 类型是否正确;
- 值是否来自用户输入;
- 值是否满足权限范围;
- 是否使用了上一步工具的真实输出;
- 是否存在模型幻造字段。
9.3 恢复和终态
工具失败后的正确轨迹可能是:
query_order -> timeout
重试一次 -> timeout
停止继续调用
向用户说明暂时无法查询
错误轨迹可能是:
query_order -> timeout
query_logistics(order_id="1001") -> schema error
继续重复调用
因此 Trace 评测应包含:
- 是否识别错误类别;
- 是否按策略重试;
- 是否停止无效循环;
- 是否避免在副作用工具上盲目重试;
- 是否进入明确终态;
- 是否留下可复盘证据。
当掌握了“什么是好轨迹”后,再将单条 Trace 提炼成数据集,进行重复评测和版本比较。官方评测指南也将 Trace 调试与后续 Dataset、Eval Run 区分开:前者用于定位行为,后者用于可重复基准和持续比较。(developers.openai.com)
十、故障诊断:从终态反向沿着 Trace 定位
10.1 最终答案错误,但工具正确
检查顺序:
Generation Span
├── 工具结果是否完整进入上下文
├── 工具结果是否被截断或序列化错误
├── 下一回合是否引用了正确 call_id
└── 最终 Generation 是否违反输出约束
若工具返回:
{"status": "delivered", "delivered_at": "..."}
但最终回答说“仍在运输中”,问题可能不在工具,而在上下文拼接、字段映射或模型回合。
10.2 延迟升高,但模型耗时没有升高
查看 Trace 时间线:
Task Span: 4.2s
├── Generation: 0.8s
├── Function: 0.3s
├── Function: 0.4s
└── 未归属等待: 2.7s
此时不能把 4.2 秒都归因于模型。未归属等待可能来自:
- 任务队列;
- 并发信号量;
- 连接池;
- 本地 JSON 序列化;
- 事件循环阻塞;
- Span 开始时间晚于实际等待开始时间。
可观测性本身也有边界:如果没有在排队前创建 Span,排队时间就不会出现在工具 Span 中。
10.3 成本升高,但用户请求量不变
按 Trace 聚合:
再拆解:
平均模型请求数增加
平均 Turn 数增加
输入 Token 增加
输出 Token 基本不变
这通常指向上下文膨胀、重复工具调用或路由循环,而不是回答变长。
10.4 生产中完全没有 Trace
应按以下顺序检查:
- 是否通过
OPENAI_AGENTS_DISABLE_TRACING=1全局关闭; - 是否调用了
set_tracing_disabled(True); - 是否在单次
RunConfig中关闭; - 是否使用了 Zero Data Retention 组织策略;
- 是否只是批量导出尚未刷新;
- 是否替换了 Trace Processor 却没有配置后端 Exporter。
Agents SDK 默认启用 tracing,但支持环境变量、全局代码配置和单次运行配置关闭;官方文档同时说明,使用 ZDR 策略的组织无法使用该 tracing 能力。(openai.github.io)
十一、隐私、采样和证据完整性
可观测性数据往往比普通日志更敏感,因为它可能同时包含:
- 用户原始问题;
- 系统 Prompt;
- 工具参数;
- 数据库查询结果;
- 访问令牌或个人信息;
- 模型输出;
- 内部错误信息。
因此要把“可诊断性”和“数据暴露面”分开设计。
11.1 分层记录
建议将数据分成三层:
索引层:
trace_id, request_id, status, latency, model, token counts
摘要层:
tool_name, argument schema, result schema, error class, state transition
证据层:
脱敏后的完整输入输出,仅限受控访问
索引层用于监控,摘要层用于大多数诊断,证据层用于事故复盘。不要让所有生产 Trace 默认保存完整业务数据。
11.2 采样不能破坏错误证据
纯随机采样会漏掉低频高风险故障。更合理的策略是:
- 成功请求低比例采样;
- 失败请求全量保留;
- 高 Token 请求保留;
- 高 Turn 或循环请求保留;
- 写操作、权限拒绝和人工接管请求保留;
- 采样决策本身写入 Trace metadata。
例如:
{
"sampled": true,
"sample_reason": "tool_error",
"retention_class": "incident"
}
采样只影响观测数据,不应影响 Agent 的业务状态。否则会出现“为了记录失败而改变失败行为”的观测扰动。
十二、规范保证、常见实现与经验建议
需要明确区分三类内容:
规范保证
指 SDK 或接口明确承诺的语义,例如:
- Trace 表示端到端工作流;
- Span 有开始和结束时间;
- Span 通过
trace_id和parent_id建立关系; turn_span表示一次 Agent 循环回合;generation_span可包含模型输入、输出、配置和 usage。
这些语义可以直接作为系统数据模型的基础。(openai.github.io)
常见实现
指工程中常见但不应误称为协议保证的做法:
- 使用
request_id关联业务入口; - 使用
downstream_request_id关联下游服务; - 为每次重试增加 attempt 序号;
- 将工具参数来源标记为模型、用户或服务端;
- 在消息队列中显式传递 Trace 关联字段。
经验建议
指需要结合业务验证的取舍:
- 是否保存完整 Prompt;
- 是否全量保留成功 Trace;
- 是否把每次重试建模为独立 Span;
- 是否将一个长期工作流拆成多个 Trace;
- 是否把成本阈值、Turn 阈值接入自动止损。
这些建议没有脱离业务就能适用于所有系统的固定答案。关键是保证:发生故障时,工程师能从 request_id 找到 Trace,从 Trace 找到出错 Span,从出错 Span 找到模型回合、工具参数、Token 消耗和下游请求,并据此判断是决策错误、执行错误、状态错误还是外部依赖错误。
Agent 可观测性的最小闭环因此不是“把日志接入某个平台”,而是:
业务请求
-> Trace
-> Turn
-> Generation / Tool Span
-> 状态变化
-> Token 与延迟
-> 终态
-> 评测与故障复盘
只要这条链条完整,Agent 的行为才从不可重复的黑箱输出,变成可以定位、比较、评测和恢复的工程轨迹。
系列导航与关联阅读
- 系列入口:Agent 工程完整路线:从运行循环、记忆与协议到安全、评测和生产交付
- 上一篇:Agent 供应链安全:模型、MCP Server、Skill、依赖和制品来源
- 下一篇:Agent 评测数据集:任务、环境、期望、版本、污染和抽样
- 延伸:Agent 轨迹与工具评测:步骤正确性、参数、效率、恢复和终态
- 延伸:Agent 故障应急:错误分类、止损、证据、回滚、补偿和复盘
官方资料
本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论