失效模式
针对固定评估集反复迭代的智能体,并不会收敛到任务本身。它们收敛到的是这个集合。这不是什么微妙的统计假象,而是一个优化循环的可预测结果——在这个循环里,目标函数是一个有限、静态的样本。每一次 prompt 修订、每一次 tool call 策略调整、每一次评分阈值改动,都是朝着评估集的特异性迈出的一步梯度,而不是朝着该集合本应衡量的底层能力。
这一动态在自我改进智能体的文献中已有充分记录。Google 的研究人员提出了方法,防止自我改进的智能体记住测试,正是因为默认行为就是记忆。当一个智能体能够生成自己的训练信号或优化自己的 prompt 时,「学习任务」与「学习测试」之间的边界就消融了。智能体无法区分真正的能力与恰好满足评分器的模式。
污染通常是在结果已经发布之后才被发现的。基准分数被报告,模型卡被更新,博客文章被发出。几个月后,有人注意到某个评估项子集逐字出现在训练语料中,或者智能体在某个切片上的输出可疑地一致。撤回很尴尬;对信任的损害更糟。流水线没有任何机制在发布前捕获这个问题,因为评估集被当作固定的参考点,而不是活的产物。
为什么这是流水线缺陷
评估集被复制进训练和 prompt 上下文时,没有血缘关系。一个 JSONL 文件被提交,然后被复制进微调任务,然后被粘贴进 few-shot prompt,然后被嵌入检索索引。每一份副本都是一次分叉。它们都没有携带对原始文件的引用。当原始文件更新时,分叉会静默地偏离。当某个分叉泄漏进训练时,没有任何记录表明评估项和训练样本共享同一个祖先。
用于迭代的集合与用于报告的集合之间,没有留出纪律。团队在他们所报告的同一批条目上迭代。迭代集变成了调优集,调优集变成了记忆目标。真正的留出集不只是「我们不看的集合」;它是一个在结构上与迭代循环隔离的集合,带有访问控制和审计日志。
对测试框架的改动会静默地改变历史数字的可比性。测试框架是运行评估的代码:prompt 模板、工具定义、解析逻辑、评分函数、重试策略、模型版本。其中任何一项的改动,都可能使分数偏移超过团队试图测量的效应。然而测试框架的改动通常在没有对评估集做版本号提升的情况下被提交,于是仪表盘展示的时间序列实际上是不可比测量的拼接。

根本原因是一个范畴错误。评估集被当作数据,而数据被假定为惰性的。但评估集是每一个引用它的结果的依赖项。它有 schema、有来源、有一组消费者、有生命周期。它理应获得与栈中任何其他依赖项相同的版本化、审查和弃用纪律。
设计规则:把评估当作有版本的依赖项
把评估集、测试框架和评分代码固定为单个有版本的单元。不是三个独立的版本;而是一个复合版本。一个评估版本是一个元组:条目集、prompt 与工具配置、评分实现、模型标识符。对任何元素的任何改动都会产生新版本。版本是内容哈希,而不是人工指定的标签,因此它不会漂移。
维护一个轮换的私有留出集,永不用于迭代。留出集单独编写,存储在单独的仓库中,带有单独的访问控制,并按计划轮换。轮换意味着退役已暴露的条目并编写新条目。这很昂贵,而这是诚实的权衡:一个永不轮换的留出集,一旦其内容泄漏进团队知识,就会变成事实上的迭代集。轮换的成本,就是让测量仍然有意义的代价。
在每一个报告的指标旁记录评估版本。没有评估版本的数字不是结果,而是传闻。版本应当出现在仪表盘、模型卡、实验跟踪器,以及任何引用分数的文档中。当审查者问「这有改进吗?」时,第一个问题是这两个数字是否共享同一个评估版本。
一个具体机制:一个把元组绑定在一起的 manifest 文件。
eval_version: sha256:9f2c...
items: s3://evals/holdout-2024-11/items.jsonl
harness:
prompt_template: sha256:1a4b...
tools: sha256:7c3d...
scoring: sha256:e8f1...
model: provider/model-name@revision
created: 2024-11-04
consumers:
- reporting-dashboard
- release-gate
manifest 是引用的单元。