别再用榜分决定要不要把活交给模型,先给你的回归集分层
同一批模型,在两个都叫 SWE-bench 的榜单上,顶分差出了十几个百分点:Verified 上 Claude Opus 5 登顶,头部口径在 96%—97% 之间;Pro(Public)的顶分只有 80.30。
两个数字都是真的。错的是把它们当成同一把尺子来读。
一、先分清:Verified 和 Pro 不是同一张卷子
SWE-bench 家族的共同做法是从真实开源仓库里捞 issue,把 issue 描述和当时的代码快照交给模型,让它提交补丁,再用项目自带的测试套件判定通没通过。分歧出在题目怎么筛、筛完留下什么。
Verified 是经过人工复核的子集:题目可解性被确认过,描述被清理干净,判定链路可靠。它的价值在于可信,代价在于——它是一份被人修过边的卷子。
Pro(Public)走的是另一条路:题目直接取自真实的大型仓库,issue 原文更长,涉及的代码面更广,没有为了「好判」而把上下文削薄。
所以 96%—97% 和 80.30 之间的落差,主要不是模型能力的落差,而是口径的落差。
这里有一起真实发生过的误读值得记一笔:有人看到 Pro 公开榜顶分 80.30,转头就下了结论——「Verified 顶分才 79% 左右,榜上根本没有 Opus 5」。两个数字都真实存在,拼出来的判断完全是假的。做质量的人对这个错误应该很熟,它就是我们天天在防的跨口径对比,只不过这次翻车的是读榜的人,不是写代码的人。
二、口径差在四个维度上
把两边摊开对照,差别不是「难一点」,而是四件事同时变了:
|
维度 |
SWE-bench Verified |
SWE-bench Pro(Public) |
|
头部成绩 |
Claude Opus 5 登顶,头部口径 96%—97% |
顶分 80.30(Claude Fable 5 深度思考) |
|
题面来源 |
真实仓库 issue,经人工复核筛选 |
真实大仓 issue,保留原始复杂度 |
|
题面长度与信息密度 |
短、聚焦,可解性已确认 |
长、噪声多,跨文件跨模块 |
|
被针对性优化的难度 |
分布稳定、题目公开,容易被反复打磨 |
上下文长、路径多,难以押题 |
|
对测试团队的读法 |
看模型在干净题面上的能力上限 |
看模型在脏题面上的可用性下限 |

最后一行才是重点。同一个分数,在两个榜上回答的是两个不同的问题:一个回答「它最好能做到什么」,一个回答「它在真实噪声里还能不能用」。拿前者去做后者的决策,是选型阶段最贵的一种错。
三、为什么短题面天然更容易被优化
题面越短、越干净,模型和它背后的工程链路能发力的地方就越集中:把 issue 读准,定位到那一两个文件,改对那一处逻辑,让项目自带测试通过。这是一条窄路,而窄路是可以被反复打磨的——公开题目、稳定分布、明确判定,三个条件凑齐,针对性优化就有了着力点。
题面一旦变长变脏,情况就完全不同。真实大仓里的 issue 常常同时夹着复现步骤不全、依赖版本不一致、报错信息误导、需要跨好几个模块才能定位。模型要先在一大堆文字里挑出真正相关的几十行,还要在长上下文中不丢掉关键约束。这已经不是同一种难度的任务。
所以 Pro 顶分只有 80.30,不代表模型退步了,而是它终于被放到了一条没法押题的路上。一个榜单最值钱的地方,往往不是它的顶分有多高,而是它还能不能被针对性优化。
再补第三个数字作旁证:LiveCodeBench 顶分 92.00,来自 Gemini 3.0 Pro Preview。它是竞赛题口径,题目短、输入输出边界清楚、判定确定。于是我们手上有了三个数:96%—97%、92.00、80.30。
这三个数不是三个能力值,是三种口径。你只能竖着读同一列,不能斜着读。
跨口径比较,是数据造假里最便宜的一种形式——它甚至不需要篡改任何一个数字。
四、测试团队读榜的三条规矩
第一条,先看口径,再看分数。打开任何一个榜,先找三件事:题目从哪来、题面多长、判定用的是谁的测试。这三件事没弄清之前,分数只是发布会素材。
第二条,把「顶分差」当信息,不要当排名。Verified 与 Pro 之间那十几分的落差,本身就是一个测量结果:它告诉你,从干净题面走到真实大仓题面,模型的可用水位会掉多少。这个落差比任何单个分数都更贴近你项目的真实处境,因为它量的是「环境变脏的代价」,而你的项目环境从来都是脏的。
第三条,只把榜单当上限,不要当预期。公开榜的分数是在最优条件下跑出来的:题目被筛过、环境被冻结、失败没有任何生产后果、还可以多次采样取最好的一次。你的仓库不具备其中任何一条。
一句话:「登顶」是市场的语言,「分层落差」才是工程的语言。 测试团队的价值,就是把前者翻译成后者。
五、把公开榜结论映射到自建回归集:先分层,再对号
大多数团队的动作是这样的:看到榜分 → 决定要不要把某类活交给模型。中间跳过了一个关键步骤——把你自己的回归集按题面难度分层,再让每一层去对榜上口径最接近的那一档。
|
自建回归集分层 |
典型特征 |
口径最接近的公开榜 |
可参考的顶分 |
该做的动作 |
|
L1 单元级 |
单函数、单文件、断言明确 |
LiveCodeBench(竞赛题口径) |
92.00 |
可较高比例交给模型改,人只审断言 |
|
L2 单 issue 级 |
单模块缺陷、有明确复现步骤 |
SWE-bench Verified |
96%—97% |
模型可自主提交,回归集当闸口 |
|
L3 跨模块级 |
长上下文、跨服务、需求半口头 |
SWE-bench Pro(Public) |
80.30 |
模型产出必须人工复核,别只靠测试兜 |
|
L4 生产环境级 |
依赖真实数据、配置、灰度状态 |
公开榜无对应口径 |
不引用 |
自建判据,任何榜分都不作依据 |
注意 L4 那一行:不是分数低,而是没有对应口径。这一层去借榜,等于拿别人的卷子给自己打分。
分层真正的产出也不是「哪些用例能过」,而是评审强度。L1、L2 的口径接近公开榜的干净题面,模型产出可以走轻评审:人只看断言写得对不对,测试跑绿就放行。L3 必须走重评审:diff 逐段过、跨模块的调用链要有人复核一遍,测试通过只是入场券。L4 则根本不该由榜分参与决策,它的判据只能来自你自己的灰度数据与回滚记录。把评审强度绑在分层上,比把通过率绑在榜分上靠谱得多。
下面这段脚本做的事很朴素:给用例打标 → 算各层占比 → 输出每层可借的口径,最后给一个「榜分对你还有多少参考价值」的判据。
# bench_map.py
# 用途:把公开榜口径映射到自建回归集分层,输出「可借榜程度」判据
# 用法:python bench_map.py tests/regression --meta case_meta.json
# case_meta.json 示例:
# [{"id": "TC-001", "files": ["service/refund.py"], "steps": 3,
# "cross_module": false, "needs_prod_data": false}, ...]
import json, argparse, pathlib, collections
# 口径锚点:分数只作「参考水位」,禁止跨层混用
BENCH_ANCHOR = {
"L1_unit": {"bench": "LiveCodeBench(竞赛题口径)", "top": "92.00"},
"L2_single_issue": {"bench": "SWE-bench Verified", "top": "96%—97%(登顶·区间口径)"},
"L3_cross_module": {"bench": "SWE-bench Pro(Public)", "top": "80.30"},
"L4_prod_env": {"bench": "无公开对应口径", "top": "不引用"},
}
def classify(case: dict) -> str:
"""打标优先级:生产数据 > 跨模块 > 单文件短步骤 > 其余归 L2"""
if case.get("needs_prod_data"):
return "L4_prod_env"
if case.get("cross_module") or len(case.get("files", [])) > 3:
return "L3_cross_module"
if len(case.get("files", [])) == 1 and case.get("steps", 0) <= 3:
return "L1_unit"
return "L2_single_issue"
def report(meta_path: pathlib.Path, threshold: float = 0.4):
cases = json.loads(meta_path.read_text(encoding="utf-8"))
buckets = collections.Counter(classify(c) for c in cases)
total = len(cases) or 1
print(f"{'分层':<18}{'用例数':>7}{'占比':>8} {'可借口径':<28}{'参考顶分'}")
for layer, anchor in BENCH_ANCHOR.items():
n = buckets.get(layer, 0)
print(f"{layer:<18}{n:>7}{n / total:>8.0%} "
f"{anchor['bench']:<28}{anchor['top']}")
# 关键判据:L3+L4 占比越高,公开榜对你的参考价值越低
hard = buckets.get("L3_cross_module", 0) + buckets.get("L4_prod_env", 0)
ratio = hard / total
verdict = ("榜分参考价值低:别用榜分做放行依据,L3 必须人工复核"
if ratio >= threshold
else "L1/L2 口径可作有限参考,仍不代表你的成功率")
print(f"\nL3+L4 占比 {ratio:.0%}(阈值 {threshold:.0%},经验起手值需按项目校准)")
print(f"判据 → {verdict}")
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("target", help="回归集目录,仅作记录,实际读取 --meta")
p.add_argument("--meta", required=True, help="用例元数据 JSON")
p.add_argument("--threshold", type=float, default=0.4)
a = p.parse_args()
report(pathlib.Path(a.meta), a.threshold)
三个实现上的提醒:
第一,打标数据不要靠人手填。 files 可以直接从 git 变更文件数拿,steps 可以用用例步骤数或断言数近似,needs_prod_data 看用例是否连生产库或读线上配置。手工打标撑不过三个月。
第二,0.4 这个阈值是经验起手值【推断】,不是行业标准。 正确做法是拿你过去半年的线上事故去校准:把出过事故的那批变更按同样规则打标,看它们落在哪一层,再把阈值压到那条线附近。
第三,top 字段只能打印,不能进判据。 一旦你把 96%—97% 或 80.30 写进 if 条件里当放行门槛,你就完成了本篇开头那起误读的同款操作——只是这次是代码替你错的。
六、映射时最容易犯的三种错位
错位一:把 Pro 的 80.30 读成「模型只能修好八成的 bug」。 80.30 是在 Pro 那批特定题目、特定判定下跑出来的,你的缺陷分布和它没有任何统计关系。它是口径水位,不是你的成功率。
错位二:把 Verified 的 96%—97% 当成自己项目的预期值。 Verified 的题目已经被人工确认可解。你的 backlog 里有多少 issue 是描述不清、根本复现不了的?那部分既不该由模型背锅,也不该由榜分背书——它该由你的需求流程负责。
错位三:给 L4 层也找一个榜来靠。 生产环境层没有公开口径,任何「某模型在生产级任务上表现如何」的说法,都必须由你自己的数据支撑。这一层最需要的是自建判据,而不是一个更好看的引用来源。
最后补一句流程上的提醒:榜单会更新,分数会变。今天写下的 96%—97% 与 80.30,发布前都应该回到 DataLearner 代码榜再核一遍区间,尤其是「登顶」这种带时效性的表述。把来源固定下来、把复核动作写进流程,比记住某个具体数字重要得多。
七、写在最后
同一批模型在两个榜上差出十几分,这不是矛盾,这是两把尺子;同一条 bug 落在你的 L2 和 L4 上,也该差出两套判据,这不是麻烦,这是分层。
分不清口径的人,永远只能读别人写好的结论;分得清口径的人,才有资格决定哪些活可以交出去。
关于我们
本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。
- 点赞
- 收藏
- 关注作者
评论(0)