怎么给两个模型出一套自己信得过的题

上一篇我把 Opus 5 和 Fable 5 的实测结果贴了出来。这篇讲更耐用的部分:题是怎么出的,分是怎么判的。
结论会过期。下个月出新版本,那些数字就废了。但一套能自己出题、自己判分的方法,换哪两个模型都还能用。
为什么要自己出题
公开榜单有两个问题。
一是和你的活不是一回事。榜单测竞赛数学、SWE-bench、长上下文召回,你每天让模型干的是「读这堆乱七八糟的 CSV 合并成一张表」「这段代码哪里有问题」。两者的相关性没有想象中高。
二是你没法验证。榜单给你 43.3% 对 33.7%,你不知道题目长什么样,不知道错在哪,也不知道是不是刚好卡在某类题型上。
自己出题的成本比想象中低。这套八道题从设计到跑完不到一个下午。但前提是有件事得做对。
一条铁律:判分不能靠眼睛
这是整件事的核心。只要判分环节需要你「看一眼觉得谁更好」,你测的就是自己的偏好,不是模型的能力。
所以我给八道题定的规矩是:能机械判分的必须机械判分,实在没法机械判分的必须盲评。
分下来是这样:
| 类型 | 题目 | 怎么判 |
|---|---|---|
| 有唯一正确答案 | 数学推理、逻辑约束 | 字符串比对 |
| 有可执行标准 | 算法实现 | 隐藏测试套件 |
| 有预设清单 | 代码找 bug | 与埋点清单比对 |
| 有硬性格式 | 指令遵循、信息提取 | 校验脚本 |
| 有确定产物 | Agentic 文件任务 | 校验输出文件 |
| 只能靠判断 | 决策备忘录 | 双评委盲评 |
八道题里七道能机械判分。这个比例是刻意设计的,不是碰巧。
六种出题法
一、有唯一解的题:先验证你的答案是对的
数学题和逻辑题最容易出,也最容易出错。出题人自己算错了,整道题就废了。
所以我给每道题都写了独立的验证脚本。逻辑题是 5 人 × 5 服务 × 周一到周五、9 条约束,我用暴力枚举把全部排列跑了一遍:
solutions = []
for svc_perm in permutations(services):
for day_perm in permutations(days):
# ...逐条检查 9 个约束
solutions.append(...)
print("solutions found:", len(solutions)) # 必须是 1
这个脚本的意义不在于算出答案,在于证明答案唯一。如果跑出来是 2 个解,说明约束写少了,模型答对了也可能被我判错。
数学题同理。三道小题(lcm(a,b)=N 的无序对数、抛硬币出现三连正面的期望次数、无 AA 且 B 出现偶数次的字符串数)我都用穷举或者精确分数运算独立验算过,没靠自己心算。
二、算法题:用隐藏测试集
让模型实现一个 topo_min(graph),返回字典序最小的拓扑序。判分不是我读代码,是一套它看不到的 pytest:
def test_cycle_raises():
with pytest.raises(ValueError) as ei:
topo_min({"a": ["b"], "b": ["a"]})
assert "cycle" in str(ei.value).lower()
def test_input_not_mutated():
g = {"a": ["b"], "b": []}
snapshot = {k: list(v) for k, v in g.items()}
topo_min(g)
assert g == snapshot
九个用例里,真正拉开差距的从来不是主流程,是这些边角:空图、自环、重复边、只作为后继出现的节点、不许改入参。主流程谁都会写。
我还写了个参考实现,用来和模型的输出对拍。出题人手里得有正确答案的可执行版本,否则你只是在读代码猜对错。
三、找 bug 题:埋点法
给一段两百行的日志分析代码,让模型找出全部功能性 bug。判分方式是我预先埋了 6 个,看它找到几个。
埋点的关键是难度要分层。我埋的六个:
rec["status"] > 500,应该是>= 500,漏掉 500 本身sorted(counts, key=...)缺reverse=True,返回的是最不频繁的端点sums[d] // counts[d],地板除截断了平均值if summary["error_rate"] is 0.0,用is比较浮点merged = a之后update(b),原地改了入参"latency_ms": int(latency_s),小数延迟记录被静默丢弃
前五个两个模型都找到了。第六个两个都没找到,而它恰恰是最危险的那个,因为它藏在这里:
try:
rec = {
"latency_ms": int(latency_s), # "12.5" 在这里抛 ValueError
...
}
except ValueError:
continue # 然后这条记录就没了
这个 bug 之所以隐形,是因为它被包在一个看起来很负责任的错误处理里。try/except ValueError: continue 是标准写法,扫过去只会觉得容错做得不错。没人会想到容错本身就是数据丢失的入口。
这是整场测试我最喜欢的一个发现。两个模型的盲区高度重合:显眼的 bug 都抓得到,伪装成防御性代码的 bug 一起漏。
四、指令遵循:给硬约束,写校验脚本
让模型写一段产品公告,加七条硬约束:恰好 8 行、每行带序号前缀、每行 10-15 个词、每行以 shipped. 结尾、全文不许出现字母 z、dark mode 恰好出现在 2 行里、不许用逗号。
判分脚本几十行,一条对应一个布尔值:
results["C3_10_to_15_words"] = all(10 <= len(l.split()) <= 15 for l in lines)
results["C5_no_letter_z"] = "z" not in text.lower()
results["C6_dark_mode_twice"] = sum("dark mode" in l for l in lines) == 2
results["C7_no_commas"] = "," not in text
约束要互相冲突才有意思。不许用逗号、每行 10-15 词、还得以固定词结尾,这些要求会互相打架,模型得同时兼顾。单条容易,七条同时满足才见功夫。
五、Agentic 任务:把陷阱埋在数据里
这类题不能只看最终数字,得看它有没有踩坑。我给了三份格式各异的订单 CSV,让它合并成一张表:
orders_east.csv order_id,customer,amount_usd,date,status
1001,Acme Corp,250.00,2026-06-01,completed
orders_west.csv id,client_name,total_cents,order_date,state
2001, Acme Corp ,50000,06/06/2026,completed
legacy_orders.csv OrderID;Customer;Amount;Date
3001;Hotel BV;42.00;10-06-2026
一共埋了六个坑:分号分隔、金额单位是美分、三种日期格式(ISO、月/日/年、日-月-年)、客户名带首尾空格、跨文件重复订单号需要按优先级去重、cancelled 订单要剔除。
其中最阴的是日期。06/06/2026 和 10-06-2026 都是合法日期,但一个是月在前、一个是日在前。光看单条数据分不出来,得结合文件里其他行才能推断。
判分脚本直接比对输出文件的每一行,外加三个汇总值:
checks["merged_rows_exact"] = rows == EXPECTED_ROWS
checks["total_revenue"] = abs(tr - 1572.09) < 0.005
checks["top_customer"] = summary.get("top_customer") == "Acme Corp"
总收入这个数字很好用。只要有一个坑没踩对,少剔一条、单位错一个、去重去错,这个数就对不上。一个数兜住全部六个坑。
六、主观题:盲评,而且要换位置
只有一道题没法机械判分:让模型以平台工程负责人的身份,给 CTO 写一份架构选型的决策备忘录。
这种题只能靠人或者别的模型判断,所以要防三件事。
防作者偏见。 评委不知道哪份是谁写的,材料里只叫「备忘录甲」和「备忘录乙」。
防位置偏置。 这条最容易被忽略。模型评委存在偏爱第一个或最后一个选项的倾向,所以两个评委拿到的顺序是相反的,甲乙对调。如果两次都是同一方赢,才说明是内容赢的,不是位置赢的。
防评分标准漂移。 给评委一张明确的分项表,而不是让它「综合打个分」:
决策明确性与理由说服力 0-3
对给定数据的运用与技术判断 0-3
风险识别与缓解的完备性 0-2
90 天计划可执行性 0-1
格式与约束遵循(含字数) 0-1
顺带一提,字数这种可以数的东西,我是用脚本数完再告诉评委的,不让它自己估。模型数不准字数。
我把题出砸的地方
这部分比上面的方法更值得看。
一、七道题打平,这是出题失败
45 分的客观题,两个模型都拿了 44 分。八道题里七道完全打平。
看起来像是「两个模型能力相当」的结论,但更准确的解读是:我的题目对这两个模型来说太简单了,区分度不够。
这在测试设计里叫天花板效应。所有人都考满分的卷子,测不出任何东西。有效的题应该落在「一个能做对、另一个做不对」的区间,而我只有一道题踩到了这个区间,还是两个都没做对的那道。
下次出题我会加大难度,或者放一批已知会失败的题进去,用来标定天花板在哪。
二、评委自己也有方差
盲评过程中出了个意外:同一个评委跑了两轮,给出了两份不同的分数,9.5:8.5 和 8.5:8.25。
一开始我觉得这是个麻烦,后来发现它是这次测试最有价值的副产品:
- 绝对分不稳定。同一份材料、同一个评委,分差能从 1.0 变成 0.25。
- 排名很稳定。三张记分卡(含另一个评委)全部把同一份排在前面。
所以用模型当裁判时,只信排名,别信绝对分。你可以说「A 比 B 好」,但别说「A 得了 9.5 分」,那个 9.5 换一轮就变了。
这一条现在几乎适用于所有用 LLM 做评审的场景,我打算单独写一篇。
三、「禁用工具」是靠自觉的
有几道题我要求模型不许用工具、纯推理作答。但我没有办法真正禁止它,只能在提示词里说,然后指望它遵守。
两边条件相同,所以对比还算公平,但这是个没法消除的不确定性。要真正禁用,得从执行框架层面切断工具权限,光靠提示词不行。
四、还有个混杂因素我没消除
好几道题我要求「只输出答案,不要其他内容」,结果两个模型都有几次加了前言。我一开始记为格式违规,后来意识到:我是用子代理框架跑的测试,而框架本身就要求代理返回结果摘要。
也就是说,模型可能是在遵守框架的要求,而不是违反我的要求。这个指标被污染了,所以最后我只把它作为方向性参考,没算进分数。
出题的时候要想清楚,你的执行环境有没有偷偷给模型加别的指令。
五、n=1 测不出方差
我每道题只跑了一次。所以严格说,我测的是「这一次的表现」,不是「稳定的能力」。
上面那个评委自己都能给出两份不同分数的事,恰好说明单次运行的噪声有多大。要谈能力,至少得跑 5 次看分布。这是这套测试最大的局限,我在原文里也标了。
一份可以照抄的清单
如果你也想给两个模型出题:
- 先想清楚你要测什么,是你每天真正在用的能力,不是榜单上的能力
- 能机械判分的题优先,目标是七成以上不需要人判
- 答案先自己验证一遍,唯一解要证明它唯一,算法题要有参考实现
- 判分标准不给模型看,隐藏测试集,埋点清单单独存
- 难度分层,全对和全错的题都是废题,价值在中间
- 主观题必须盲评加位置互换,否则你测的是位置偏好
- 同一份提示词逐字复制,任何措辞差异都是变量
- 记录过程指标,耗时和工具调用次数有时比分数更能说明工作风格
- 老老实实写局限,n 是多少、什么没控制住、哪个指标被污染了
两个总分是 52.63 对 53.25,差距小到没什么意义。做完之后我知道了哪类活它们都稳、哪类会一起翻车、风格上差在哪,这个比分数有用。下次换模型,这套题直接再跑一遍就行。