跳转到主要内容

Agent 部署前验证是一个故障注入问题

干净的 trace 只能证明 agent 处理了你运行过的用例。就绪意味着注入生产环境将会产生的故障,并观察恢复路径。

Close-up of a futuristic robotic toy against a gradient background, symbolizing innovation and technology.

为什么 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 必须优先采用新鲜来源并更新其状态,而不是将两者调和成一个看似合理但错误的答案。

Detailed view of an aircraft parking brake lever with selective focus in a cockpit.

要测量什么

注入故障而不测量只会产生轶事。真正重要的测量是那些告诉你恢复路径是设计行为还是偶然的测量。

  • 恢复时间。 在故障之后,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 一起进行版本控

White / light blue / purple / dark blue sail; without harness or cover bag; 18ft. 5 1/2in. leading edge long; aluminum and dacron; 1974. In

使其可重复

一个未版本化的故障集是一次性测试。一个版本化的故障集是一项资产。

实践是将故障集视为 agent 定义的一部分。当 agent 的 prompt 改变时,故障集运行。当工具的 schema 改变时,故障集运行。当发现新的生产故障时,它成为故障集中的一个新 fixture,而不是事后分析中的一段文字。事后分析仍然存在,但它的输出是一个可复现的测试,而不是叙述。

这改变了部署对话的形态。不再问 agent 是否处理了演示,问题变成注入了哪些故障类别、恢复画像是什么样子,以及是否有任何之前通过的故障现在失败了。这是一个有基于证据答案的问题。

权衡是故障集单调增长。每个生产故障都会增加一个 fixture,套件变得越来越慢。在某个时候,套件需要分层:一个在每次 prompt 改变时运行的快速子集,以及一个在部署前运行的完整集。分层决策本身就是关于哪些故障最可能复发的判断,并且应该随着 agent 环境的变化而重新审视。

验证问题

问题不是 agent 在 trace 中看起来是否良好。问题是当它依赖的工具停止响应、它持有的凭证过期、它携带的上下文与它刚刚读取的内容相矛盾时会发生什么。如果你没有注入这些故障,你就不知道。如果你注入了,你就有了恢复画像、一组 fixture,以及一个基于证据而非干净运行的部署决策。

验证不是阅读。而是注入。

封面照片:Jakub Pabis / Pexels。

分享XLinkedIn永久链接5 分钟阅读

相关阅读

这能回答的问题

Why is reading an agent's trace not enough to validate it?

A trace only shows the cases someone chose to run. It cannot contain the failures the environment will produce, so a clean trace validates the scenario set, not the agent's readiness for production.

Which fault classes should be injected first?

Start with tool timeouts, partial responses and schema drift, credential expiry mid-run, permission denials after a plan is committed, conflicting instructions from two tools, and stale context that contradicts fresh retrieval.

What should be measured after each injected fault?

Recovery time, steps consumed by recovery, whether the run terminates in a safe state, and the divergence between the planned and executed trajectory.

How do you keep fault injection repeatable?

Version the fault set alongside the agent, re-run it on every prompt or tool change, and convert each newly discovered production failure into a new fixture rather than a postmortem paragraph.