评测集怎么建:换模型、换平台之后怎么验证没变差
把线上那个模型换成便宜一档的,成本大概能省一半——这个决定通常在十分钟内就做完了。真正难的是接下来那个问题:换完以后,效果还行吗?
我见过太多团队对这个问题的回答是:“我试了几个例子,感觉还行。“这句话在改动上线之前听起来足够了,在出事之后一文不值。客服机器人开始漏字段、抽取结果的 JSON 突然多了一层包裹、长文档场景下答案开始跑偏——你没法证明是这次改动造成的,也没法证明不是,因为改动前后你根本没有可比的记录。你唯一能做的是回滚,然后不敢再动。
一个自己的评测集,就是为了让你敢动。它不是什么高级工程实践,它是换便宜模型、按成本重新选型、迁到另一个平台这几件事的共同前提。没有它,上面每一篇文章里的方法你都只能纸上谈兵——因为你省下的钱能不能换来可接受的效果,只有你自己的数据能回答。
一、先把野心降下来:评测集的最低要求
很多团队一提”建评测集”就开始设计大工程:标注规范、多人交叉标注、覆盖率矩阵、评分模型。设计了三周,一条样本都没跑。
能落地的最小版本只有四条要求。
第一,样本必须来自真实业务。 不是你坐在工位上编的例句。编出来的样本有一个致命特征:它们太干净了。真实用户会打错字、会在一句话里问三个问题、会粘贴一段带乱码的表格、会说到一半就发过来。这些脏东西恰恰是模型换档之后最容易崩的地方。从线上日志里捞,从客服工单里捞,从产品同事的抱怨截图里捞——哪怕手动复制粘贴,也比编的强。
第二,覆盖三类:典型、边界、已知失败案例。 典型样本让你知道基本盘没塌;边界样本(超长输入、空字段、多意图混杂、罕见品类)让你知道天花板在哪;已知失败案例是最值钱的那部分——它们是你过去真金白银踩过的坑,是回归测试真正要盯的地方。每一次线上故障复盘,都应该往这一类里加样本。一个用了半年的评测集,价值的大头就沉淀在这里。
第三,每条样本要有明确的期望输出,或者一条可自动判定的验收规则。 这两者选一个就行。分类任务写明期望标签;抽取任务写明必须出现哪些字段、字段类型是什么;开放问答写不出唯一答案,那就写规则——“必须提到退货时限”、“不得出现除资料外的具体金额”。规则写不出来的样本,先别放进来。
第四,规模从几十条起步就有用。 我知道”几十条不够有统计意义”这种反驳,但请对比一下另一个选项:零条。三十条真实样本能拦住的回归,比零条多三十条。别等”攒够一千条再开始”,那一天不会到来。跑起来以后,样本会自己长大——每次故障加几条,每次新功能加几条,半年后自然就上百了。
二、验收标准必须可量化
“感觉更好”不能作为验收标准,原因有两个,都很实在。
一是不同人的感觉不同。你觉得回答更简洁了,产品经理觉得信息量掉了。没有数字,这个争论没有终点,最后靠谁嗓门大来定。
二是感觉无法回归。三个月后你再换一次模型,你想知道”这次跟上次比怎么样”,但上次的”感觉还行”没法跟这次的”感觉还行”做减法。数字可以。
可量化的标准,按落地难度从低到高:
| 指标 | 怎么算 | 适用场景 |
|---|---|---|
| 格式合法率 | 能被解析器成功解析的输出条数 / 总条数 | 所有结构化输出场景 |
| 字段准确率 | 字段值与期望一致的字段数 / 期望字段总数 | 抽取、分类、打标 |
| 规则通过率 | 通过自动断言的条数 / 总条数 | 有硬约束的开放生成 |
| 人工抽检通过率 | 抽检中判为合格的条数 / 抽检条数 | 自动判不了的主观质量 |
定基线的时候只做一件事:在改动之前,先用当前线上配置把整个评测集跑一遍,把这几个数记下来。 这一步经常被跳过,然后新配置跑出来 92% 的时候没人知道该高兴还是该慌——因为没人知道老配置是 95% 还是 88%。
验收线怎么定,取决于这次改动是为了什么。如果是为了省钱降档,那”效果略降但在容忍线内”本来就是预期,提前把容忍线写下来(比如字段准确率下降不超过 2 个百分点、格式合法率不得下降)比事后拍脑袋要好得多。如果是平台迁移,效果本应持平,任何明显下降都得先查原因再谈上线。
三、能自动判的,就别用人判
人工评审最大的问题不是贵,是不可重复。同一个人上午和下午的标准都会漂。
先把能自动判的部分全部榨干:
- 格式:JSON 能不能解析、XML 标签闭不闭合、Markdown 表格列数对不对
- 必填字段:schema 里要求的键在不在、有没有多出来意料之外的键
- 数值范围:金额是不是正数、日期是不是合法日期、置信度在不在 0 到 1 之间
- 枚举合法性:分类标签是不是在允许集合里(这一条抓到的问题比想象中多——模型很爱自创一个看起来很合理但你系统里不存在的类别)
- 禁止项:不该出现的字符串(比如把 prompt 里的示例内容原样抄出来)
def check(sample, output):
problems = []
try:
data = json.loads(output)
except json.JSONDecodeError as e:
return ["格式非法: %s" % e]
for key in sample["required_keys"]:
if key not in data:
problems.append("缺字段: %s" % key)
if data.get("category") not in ALLOWED_CATEGORIES:
problems.append("类别越界: %s" % data.get("category"))
return problems
剩下真正需要人看的,通常是”答得对不对、有没有编造、语气合不合适”这类。这部分给一个务实的抽样思路:自动判定跑全量,人工只看两块——一是自动判定失败的全部条目(这些本来就要看,量不大),二是自动判定通过的部分里抽 10% 到 20%。 抽样的时候优先抽边界样本和已知失败案例,别均匀随机抽——典型样本本来就不容易出问题,把人力花在那儿是浪费。
人工评审要留痕:谁看的、判了什么、为什么判不合格。判不合格的那条,顺手就该变成一条新的自动断言(如果能写出来的话),下次就不用人看了。评测集就是这样从”靠人”慢慢变成”靠机器”的。
四、控制变量,否则你测的是随机性
这一条很容易被忽略,但它能毁掉整套结论。
采样参数不固定,你两次跑出来的差异可能全是温度带来的抖动,跟换没换模型没关系。把温度设成 0 或者你线上实际用的值,并且两次跑保持完全一致;支持随机种子的接口就把种子固定下来。至于具体参数名和取值范围,各家不一样,以官方文档为准。
要保持一致的不止参数:
- 同一批输入:不要一边改评测集一边跑对比,改集合和跑对比必须是两件分开的事
- 同一套判定:判定脚本也要版本化,判定逻辑改了就等于换了尺子,历史数据全部作废
- 同一个记录表:每次跑完落一份结果文件,至少包含时间、模型标识、平台标识、参数、各项指标数值、失败条目清单
那份记录表是整套体系里真正的资产。半年后有人问”我们当初为什么从 A 换到 B”,你能翻出一行数据,而不是一段回忆。
顺带说一句:即使温度设成 0,也别指望两次输出一模一样。分布式推理本身就有不确定性,不同批次的算子调度可能带来细微差异。所以判定要看的是指标层面的差异是否显著,而不是”字符串是不是完全相等”。用字符串全等做回归断言,你会被淹死在假阳性里。
五、四种触发场景,各测什么
评测集不是每次都要全量跑。什么改动跑什么,是决定这套体系能不能长期活下去的关键——跑得太重,两个月后就没人跑了。
场景一:换模型 / 降档。 全量跑,重点看边界和已知失败案例。降档最典型的失效模式不是”整体变差”,而是”典型样本基本没变、边界样本崩了”。如果你只跑典型样本,会得出”降档零损失”的错误结论,然后在上线第三天被长文档请求打脸。同时记得记录每条的输入输出 token 数——这次降档到底省了多少,得用同一批输入的真实用量来算,不能只看单价表。
场景二:换平台。 重点在格式稳定性和流式行为,而不是”答得对不对”。同一个模型在不同平台上,最容易出差异的地方是外围:结构化输出是不是同样被强约束、停止序列的处理方式、流式分块的粒度和结束事件、超长输入的截断策略、错误码和重试语义。这些东西答案的”内容质量”看不出来,但会让你的解析代码在半夜崩掉。所以换平台时,格式合法率这一项要看得比准确率更紧。
场景三:模型版本升级。 全量跑,跟历史结果逐条对比。版本升级最阴的一点是它可能不需要你做任何操作就发生了——如果你用的是不带版本号的模型别名,供应商在后台推了新版本,你的应用行为就变了。所以这一场景要么定期主动跑(比如每周一次全量),要么就把模型标识固定到明确版本上,具体哪些模型提供固定版本标识,以各平台官方文档为准。
场景四:prompt 改动。 跑受影响的子集,加全量抽样。改的是抽取字段的说明,就跑所有抽取类样本;改的是拒答策略,就跑所有边界样本。但一定要再从全量里随机抽一小部分——prompt 改动的副作用经常出现在你以为不相关的地方,那个”请基于给定资料回答”的例子就是典型:改的是幻觉约束,坏的是长文档召回。
六、放进 CI,但要算清代价
理想状态是每个 PR 自动跑评测集。现实是每跑一次都要烧 token,而且要花时间。三百条样本、每条几千 token,一天跑二十次,账单会让人不舒服。
折中做法是分层:
| 层级 | 集合规模 | 触发时机 |
|---|---|---|
| 冒烟集 | 20-30 条,覆盖主路径 + 几条最经典的失败案例 | 每次 PR,自动跑 |
| 全量集 | 全部样本 | 合并到主干时、每晚定时、或换模型/换平台前手动触发 |
| 扩展集 | 全量 + 压力样本(超长、并发) | 大改动上线前 |
冒烟集要控制在几分钟内跑完,否则开发会想尽办法绕过它。这不是纪律问题,是人性问题——CI 里一个要等二十分钟的检查,最终一定会被加上 skip 标记。
再补两个工程细节。一是评测调用要跟线上业务用不同的凭据或计费标签,否则你的成本报表会被评测污染,分不清哪部分是真实业务。二是评测跑批要能接受部分失败——限流、超时是常态,脚本要有重试和退避,并且要把”因为报错没跑成”和”跑成了但判定不通过”分开统计。这两者混在一起,你会把一次限流误读成质量下降。
七、评测集会腐化,要有人管
一套用了一年没动过的评测集,大概率已经在测一个不存在的业务了。
腐化的几种典型形式:业务规则改了(退货时限从 7 天改成 15 天),但期望输出还是老的,于是模型答对了反而被判错;样本里的商品、政策、人名过期了;某个功能下线了,但它的样本还在拖慢每次跑批;新上线的核心功能一条样本都没有。
治理办法不需要复杂流程,两条就够:
第一,把”补充新失败案例”变成故障复盘的固定动作。 线上出了问题,复盘会上除了改代码,必须产出至少一条评测样本,带上期望输出。这一条是整套体系里投入产出比最高的动作——它让评测集自动跟着你踩过的坑长大,而且加样本的时机正好是所有人对这个 case 记忆最清楚的时候。
第二,定期做一次对账。 频率不用高,跟着版本节奏走就行。每次对账做三件事:删掉已下线功能的样本、更新业务规则变化涉及的期望输出、检查最近上线的功能有没有样本覆盖。顺便看一眼哪些样本从来没失败过——常年满分的样本要么是太简单,要么是判定太松,可以考虑换成更难的版本。
还有一个信号值得留意:如果评测全绿但线上还在出问题,说明评测集和真实分布脱节了。 这时候要做的不是加严判定,而是回到线上日志里重新采样。评测集的价值上限,取决于它有多像真实流量。
自查清单
- 评测集里的样本,是不是全部来自真实业务日志或工单,而不是编的例句?
- 三类样本(典型 / 边界 / 已知失败案例)是不是都有?已知失败案例的数量是不是在随着每次故障复盘增长?
- 每条样本是不是都有明确的期望输出,或者一条能被脚本执行的验收规则?
- 在做任何改动之前,是不是先用当前线上配置跑出了基线数值,并且落盘存档了?
- 采样参数、随机种子、判定脚本版本,两次对比之间是不是完全一致?
- 能用规则自动判的部分(格式、必填字段、数值范围、枚举合法性)是不是已经全部自动化,人工只花在判不了的地方?
- 冒烟集是不是能在几分钟内跑完,并且真的挂在 CI 上、没被人加 skip?
- 最近一次评测集对账是什么时候?有没有已下线功能的僵尸样本,或者新功能零覆盖的盲区?
更完整的评测体系设计(指标分层、判定方法、与迭代流程的结合)可以看大模型应用评测 Eval 的工程路径;如果你现在正处在选型阶段、需要的是”怎么把几个候选模型摆在一起横向比”,那份自己实测大模型的方法更对路。两件事的共同点是:结论都得建立在你自己的数据上,而不是别人的榜单上。