编写式基准的幻觉
大多数 Agent 评估套件最初都是一份团队已经想到的任务清单:预订航班、总结文档、解决支持工单。这些任务对冒烟测试有用,但它们有一个结构性缺陷。它们来自产生 Agent 的同一份想象力。团队想到的任务,几乎按定义就是 Agent 已经能处理的任务。一个从不失败的基准不是基准;它是一面镜子。
真正重要的失败存在于尾部:没人预测到的工具调用顺序、由不稳定的上游触发的重试风暴、导致状态不一致的部分写入、静默改变 schema 解释的模型版本。这些组合不是被设计的。它们是被发现的。而发现的失败的唯一可靠来源是生产环境。
这就是为什么 Agent 评估应当是事故回放。回归语料库不是一组精心策划的想象场景;它是一个不断增长的真实轨迹档案,每一条都是一个 fixture,编码了系统实际表现出的失败。这种转变既是文化上的,也是技术上的:评估套件成为运维的副产品,而不是一项单独的编写工作。
事故回放实际包含什么
生产轨迹比 prompt-response 对更丰富。一个可回放的事故至少包含四层:
- 输入状态:用户请求、对话历史、检索到的上下文,以及任何环境状态(功能开关、租户配置、会话元数据)。
- 工具调用序列:工具调用的有序列表,包括参数、调用方身份和出口目标。
- 中间观察:工具结果、错误负载、延迟,以及任何重试或回退。
- 终端副作用:最终答案,还包括 Agent 导致的写入、邮件、工单或状态变更。
捕获这些层是必要的,但不充分。回放必须是确定性的。外部端点用录制的响应打桩;时钟被冻结或注入;模型版本被固定到产生该轨迹的确切快照。没有确定性,回放就是重演,其通过/失败信号就是噪声。
因此,回放保真度首先是一个数据管道问题,其次才是评估问题。把追踪当作事后补充的团队会发现他们的事故无法回放:工具响应没有被记录,模型版本没有被记录,时钟没有被捕获。修复要向上游走:对 Agent 进行埋点,使每一条轨迹都是一等公民、带版本的工件。
一个最小回归测试框架

这里描述的框架刻意保持小巧。它假设三个组件:轨迹存储、回放运行器和合并门禁。
将轨迹存储为带版本的工件
轨迹与产生它们的代码一起存储,而不是放在一个会漂移的独立数据库中。一个轨迹工件包括输入状态、工具序列、观察和终端副作用,外加模型版本和 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 上运行。门禁不是单一分数,而是整个语料库的通过率,并带有针对回归的策略:任何在基础分支上通过、在头分支上失败的轨迹都会阻止合并,除非有记录的豁免理由。新事故

这就是将运维转化为评估的机制。语料库随系统实际的失败面增长,而不是随团队的想象力增长。
成本与限制
回放语料库单调增长。每个事故都会增加一个 fixture,而 fixture 存储便宜但运行昂贵。诚实的权衡是覆盖率和运行时间之间的权衡。一个需要一小时回放的语料库会被跳过;一个需要一分钟的语料库会被欠采样。务实的答案是修剪策略,而不是删除。轨迹被分层:快速层在每次提交时运行(一个小的、高信号的子集),完整层每晚运行,归档层保留用于取证。修剪是关于哪些失败仍具代表性的策略决策,而不是垃圾回收问题。
有些失败在构造上就是不可复现的。依赖于实时第三方状态的竞态条件、一次性的数据损坏、因提供商不暴露快照而无法固定的模型行为。这些无法被忠实回放。诱惑是丢弃它们。更好的做法是将它们记录为已知未知 fixture:一条带有负责人、疑似机制描述以及不属于通过/失败门禁标记的轨迹。语料库于是区分了被测试的内容和仅仅被记住的内容。
第二个限制是回放衡量的是对过去的保真度,而不是对未来的正确性。完全由事故构建的语料库会过度索引已经发生过的失败模式。这是可接受的,因为替代方案——由想象任务构建的语料库——会过度索引永远不会发生的失败模式。纪律是将语料库视为活的记录,而不是规范。
运维转变
论点不是编写式基准无用。它们对入职、冒烟测试和传达意图有用。论点是它们不足以作为 Agent 的主要评估面。真正重要的失败是在生产中被发现的,而防止它们复发的唯一方法就是回放它们。
这要求团队转变对轨迹的处理方式:作为带版本的工件,而不是日志。它要求团队转变对事故的处理方式:作为强制性的 fixture,而不是一次性的火灾。它还要求团队转变对评估的处理方式:作为真实轨迹的不断增长的档案,而不是想象任务的静态套件。
结果是一个反映系统实际行为的回归语料库。它永远不会完整。它总会落后于下一次事故。但它会是诚实的,而且会是有用的。
封面照片:panumas nikhomkhai / Pexels。
