为什么 trace 审查不是验证
trace 是对已经发生的一次运行的记录。它显示了调用了哪些工具、以什么顺序、用什么参数,以及返回了什么。它对于调试和建立对 agent 行为的直觉很有用。它不是就绪信号。
当团队审查 trace 并宣布 agent 已验证时,他们验证的是自己选择的场景集,而不是 agent 将要面对的环境。通过的用例是有人想到要运行的用例。在生产中失败的用例,按定义,是没人想到要运行的用例。trace 无法包含它们。
这很重要,因为审批门禁往往建立在演示运行之上。利益相关者观看 agent 完成任务,看到干净的 trace,然后签字。本应在门禁处消除的风险反而被转移到部署窗口,在那里观察成本更高,回滚也更困难。门禁没有降低风险;它只是移动了风险。
验证必须是一个主动过程。你不是通过阅读 agent 的行为来推断就绪。你扰动环境,并观察 agent 的恢复路径是否成立。这就是故障注入,而对于 agent 来说,这是唯一能产生关于你尚未见过的故障证据的验证形式。
值得注入的故障类别
针对 agent 的故障注入不是通用的混沌实验。真正重要的故障是那些与 agent 的规划、工具使用和记忆交互的故障。一个实用的起始集合:
- 工具超时。 一个依赖的 API 在 agent 的超时预算内停止响应。agent 必须决定是重试、回退还是中止,并且不能承诺一个假设结果已到达的计划。
- 部分响应和 schema 漂移。 一个工具返回截断的 payload,或者一个字段改变类型或消失。用隐含假设解析工具输出的 agent 要么崩溃,要么更糟,静默地误读结果。
- 运行中途凭证过期。 一个 token 在计划已提交之后、最终写入之前过期。agent 必须暴露失败,而不是用过期凭证重试或在未授权的情况下继续。
- 提交后的权限拒绝。 一个工具调用因授权原因被拒绝,而不是可用性原因。这与超时是不同的恢复路径,而且它往往在 agent 已经告诉用户任务正在进行之后才到达。
- 来自两个工具的冲突指令。 两个来源对同一事实存在分歧。agent 必须检测到冲突,而不是选择它最后读到的那个。
- 与新鲜检索相矛盾的陈旧上下文。 agent 上下文中的缓存值与新鲜读取冲突。agent 必须优先采用新鲜来源并更新其状态,而不是将两者调和成一个看似合理但错误的答案。

要测量什么
注入故障而不测量只会产生轶事。真正重要的测量是那些告诉你恢复路径是设计行为还是偶然的测量。
- 恢复时间。 在故障之后,agent 需要多长时间(以挂钟时间或模型轮次计)达到稳定状态。恢复时间超过任务自身截止时间的恢复不是恢复。
- 恢复消耗的步骤。 在故障上花费了多少工具调用和规划周期。一个恢复了但耗尽整个步骤预算的 agent 没有剩余容量来完成任务。
- 终止状态。 运行是否在安全状态下结束。安全意味着副作用要么被正确提交,要么完全未提交,并且用户被告知是哪一种。一次以部分写入和成功消息结束的运行是失败的,无论它恢复得多么优雅。
- 轨迹偏离。 在每次注入故障后,比较计划轨迹与执行轨迹。偏离本身不一定是坏事,但未测量的偏离是坏事。如果 agent 静默地改变计划,你就失去了推理其行为的能力。
这些测量不是一个单一分数。它们是一个画像。一次恢复很快但终止不安全的运行,比一次耗时更长但干净终止的运行更糟。画像让你明确地做出这种权衡。
一个具体机制:故障注入 harness
使这件事可重复的机制是一个位于 agent 与其工具之间的 harness。它不是 mock。它是一个代理,可以将故障集应用于实时或录制的工具响应。
class FaultInjector:
def __init__(self, faults, seed):
self.faults = faults
self.rng = random.Random(seed)
def wrap(self, tool):
def call(*args, **kwargs):
fault = self._select(tool.name)
if fault == "timeout":
raise ToolTimeout(tool.name)
if fault == "schema_drift":
return self._mutate_schema(tool(*args, **kwargs))
if fault == "partial":
return self._truncate(tool(*args, **kwargs))
return tool(*args, **kwargs)
return call
harness 为每个注入的故障记录恢复时间、消耗的步骤、终止状态,以及计划轨迹与执行轨迹之间的偏离。故障集与 agent 一起进行版本控

使其可重复
一个未版本化的故障集是一次性测试。一个版本化的故障集是一项资产。
实践是将故障集视为 agent 定义的一部分。当 agent 的 prompt 改变时,故障集运行。当工具的 schema 改变时,故障集运行。当发现新的生产故障时,它成为故障集中的一个新 fixture,而不是事后分析中的一段文字。事后分析仍然存在,但它的输出是一个可复现的测试,而不是叙述。
这改变了部署对话的形态。不再问 agent 是否处理了演示,问题变成注入了哪些故障类别、恢复画像是什么样子,以及是否有任何之前通过的故障现在失败了。这是一个有基于证据答案的问题。
权衡是故障集单调增长。每个生产故障都会增加一个 fixture,套件变得越来越慢。在某个时候,套件需要分层:一个在每次 prompt 改变时运行的快速子集,以及一个在部署前运行的完整集。分层决策本身就是关于哪些故障最可能复发的判断,并且应该随着 agent 环境的变化而重新审视。
验证问题
问题不是 agent 在 trace 中看起来是否良好。问题是当它依赖的工具停止响应、它持有的凭证过期、它携带的上下文与它刚刚读取的内容相矛盾时会发生什么。如果你没有注入这些故障,你就不知道。如果你注入了,你就有了恢复画像、一组 fixture,以及一个基于证据而非干净运行的部署决策。
验证不是阅读。而是注入。
封面照片:Jakub Pabis / Pexels。
