跳转到主要内容

Agent 评估即事故回放:从生产轨迹构建回归语料库

Agent 评估套件应当从捕获的生产轨迹和事故回放中生长出来,而非从想象的任务中编写,因为真正重要的失败是被发现的,而不是被设计的。

Detailed view of a computer screen displaying code with a menu of AI actions, illustrating modern software development.

编写式基准的幻觉

大多数 Agent 评估套件最初都是一份团队已经想到的任务清单:预订航班、总结文档、解决支持工单。这些任务对冒烟测试有用,但它们有一个结构性缺陷。它们来自产生 Agent 的同一份想象力。团队想到的任务,几乎按定义就是 Agent 已经能处理的任务。一个从不失败的基准不是基准;它是一面镜子。

真正重要的失败存在于尾部:没人预测到的工具调用顺序、由不稳定的上游触发的重试风暴、导致状态不一致的部分写入、静默改变 schema 解释的模型版本。这些组合不是被设计的。它们是被发现的。而发现的失败的唯一可靠来源是生产环境。

这就是为什么 Agent 评估应当是事故回放。回归语料库不是一组精心策划的想象场景;它是一个不断增长的真实轨迹档案,每一条都是一个 fixture,编码了系统实际表现出的失败。这种转变既是文化上的,也是技术上的:评估套件成为运维的副产品,而不是一项单独的编写工作。

事故回放实际包含什么

生产轨迹比 prompt-response 对更丰富。一个可回放的事故至少包含四层:

  • 输入状态:用户请求、对话历史、检索到的上下文,以及任何环境状态(功能开关、租户配置、会话元数据)。
  • 工具调用序列:工具调用的有序列表,包括参数、调用方身份和出口目标。
  • 中间观察:工具结果、错误负载、延迟,以及任何重试或回退。
  • 终端副作用:最终答案,还包括 Agent 导致的写入、邮件、工单或状态变更。

捕获这些层是必要的,但不充分。回放必须是确定性的。外部端点用录制的响应打桩;时钟被冻结或注入;模型版本被固定到产生该轨迹的确切快照。没有确定性,回放就是重演,其通过/失败信号就是噪声。

因此,回放保真度首先是一个数据管道问题,其次才是评估问题。把追踪当作事后补充的团队会发现他们的事故无法回放:工具响应没有被记录,模型版本没有被记录,时钟没有被捕获。修复要向上游走:对 Agent 进行埋点,使每一条轨迹都是一等公民、带版本的工件。

一个最小回归测试框架

Detailed view of a trading chart analyzing cryptocurrency trends and market data.

这里描述的框架刻意保持小巧。它假设三个组件:轨迹存储、回放运行器和合并门禁。

将轨迹存储为带版本的工件

轨迹与产生它们的代码一起存储,而不是放在一个会漂移的独立数据库中。一个轨迹工件包括输入状态、工具序列、观察和终端副作用,外加模型版本和 Agent 配置的哈希。对工件进行版本化意味着 Agent 的变更可以针对确切的历史上下文进行评估。

# trace-2025-03-14-001.yaml
trace_id: 2025-03-14-001
agent_config_hash: 9f2c...
model_version: gpt-4.1-2025-04-14
input_state:
  user_request: "reconcile invoice 8842"
  conversation: [...]
tool_sequence:
  - name: fetch_invoice
    args: {id: 8842}
    result: {status: 200, body: {...}}
  - name: post_ledger_entry
    args: {amount: 120.00, account: "AR"}
    result: {status: 500, error: "timeout"}
terminal_side_effects:
  - type: ledger_entry
    status: not_written

基于轨迹属性评分,而不仅仅是最终答案

最终答案准确率对 Agent 来说是一个弱信号。一个需要危险工具调用、未授权出口或重试风暴才能得到的正确答案不算通过。该框架对轨迹属性评分:

  • 工具集:调用了哪些工具,是否有任何工具超出允许集合。
  • 顺序:序列是否遵守依赖关系和幂等性约束。
  • 出口目标:联系了哪些外部端点,是否与允许列表匹配。
  • 重试行为:发生了多少次重试,是否有界。
  • 副作用一致性:给定中间失败,终端状态是否连贯。

这些属性无需评判模型即可检查。它们是确定性的、快速的、可解释的。在工具集或出口上失败的回放是安全或可靠性事故,而不是质量回归。

以回放通过率作为合并门禁

该框架在每次 pull request 上运行。门禁不是单一分数,而是整个语料库的通过率,并带有针对回归的策略:任何在基础分支上通过、在头分支上失败的轨迹都会阻止合并,除非有记录的豁免理由。新事故

A cybersecurity expert in a dimly lit room is typing on a colorful keyboard with multiple screens displaying data.

这就是将运维转化为评估的机制。语料库随系统实际的失败面增长,而不是随团队的想象力增长。

成本与限制

回放语料库单调增长。每个事故都会增加一个 fixture,而 fixture 存储便宜但运行昂贵。诚实的权衡是覆盖率和运行时间之间的权衡。一个需要一小时回放的语料库会被跳过;一个需要一分钟的语料库会被欠采样。务实的答案是修剪策略,而不是删除。轨迹被分层:快速层在每次提交时运行(一个小的、高信号的子集),完整层每晚运行,归档层保留用于取证。修剪是关于哪些失败仍具代表性的策略决策,而不是垃圾回收问题。

有些失败在构造上就是不可复现的。依赖于实时第三方状态的竞态条件、一次性的数据损坏、因提供商不暴露快照而无法固定的模型行为。这些无法被忠实回放。诱惑是丢弃它们。更好的做法是将它们记录为已知未知 fixture:一条带有负责人、疑似机制描述以及不属于通过/失败门禁标记的轨迹。语料库于是区分了被测试的内容和仅仅被记住的内容。

第二个限制是回放衡量的是对过去的保真度,而不是对未来的正确性。完全由事故构建的语料库会过度索引已经发生过的失败模式。这是可接受的,因为替代方案——由想象任务构建的语料库——会过度索引永远不会发生的失败模式。纪律是将语料库视为活的记录,而不是规范。

运维转变

论点不是编写式基准无用。它们对入职、冒烟测试和传达意图有用。论点是它们不足以作为 Agent 的主要评估面。真正重要的失败是在生产中被发现的,而防止它们复发的唯一方法就是回放它们。

这要求团队转变对轨迹的处理方式:作为带版本的工件,而不是日志。它要求团队转变对事故的处理方式:作为强制性的 fixture,而不是一次性的火灾。它还要求团队转变对评估的处理方式:作为真实轨迹的不断增长的档案,而不是想象任务的静态套件。

结果是一个反映系统实际行为的回归语料库。它永远不会完整。它总会落后于下一次事故。但它会是诚实的,而且会是有用的。

封面照片:panumas nikhomkhai / Pexels。

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

相关阅读

这能回答的问题

Why not just write more task-based benchmarks?

Authored benchmarks sample the tasks the team already imagined, which are the tasks the agent already handles. They miss the compositions that cause incidents: tool ordering, retry storms, partial writes. Incident replays sample the tail.

What makes a production trace replayable?

Determinism. External endpoints are stubbed with recorded responses, clocks are frozen or injected, and model versions are pinned. Without these, a replay is a re-enactment and its pass/fail signal is noise.

How do you score a replay without a judge model?

Score trajectory properties: tool set, ordering, egress targets, retry behaviour, and side-effect consistency. These are deterministic, fast, and explainable, and they catch security and reliability regressions that final-answer accuracy misses.

What about failures that cannot be reproduced?

Record them as known-unknown fixtures with an owner and a suspected mechanism, and keep them out of the pass/fail gate. The corpus then distinguishes between what is tested and what is merely remembered.

Doesn't the corpus grow without bound?

It grows monotonically, so it needs a pruning policy, not deletion. Tier traces into a fast subset for every commit, a full set for nightly runs, and an archive for forensics.