作者通过29道订单测试题发现,按答对数量评分会掩盖致命错误——答错5题可能只是无关紧要的typo,也可能是一次性把货发给了已取消订单的客户。推荐按不可逆性分四级:致命、可疑、遗漏、无害。
我给 LLM 做了一份 29 道题的订单阅读考试。上次讲了如何设计考试,今天讲如何打分。
打分之所以有独立一篇是有原因的——评分逻辑没做对,分数就是在骗你。
错 5 题,能上线吗?
不知道。因为"哪 5 题"这个信息缺失了。
如果错的 5 道题都是满是拼写错误的题目,那上线没问题。但如果有 1 道是把"请取消我的订单"读成了"新订单"?那就算其他所有题目都答对了,这个程序也不能上线——它会把货发给了刚刚取消订单的客户。
所以不要按题数打分。按严重程度打。
严重程度 = "人类能否逆转这件事?"
我的评分器有 4 个等级。唯一标准——是否可逆?在这个程序中,不可逆的时刻是错误的货物被装上了卡车。
FATAL 错误货物已装上卡车。无法撤销
RISKY 确认了模糊事项但未提问。这次对了——下次致命
MISSED 漏掉了一个订单。客户打来电话。可修复
HARMLESS 多问了"请确认"。只是慢一点
由此衍生出一条原则:
一次错误的确认比没有确认更糟糕。
听起来是废话。上线后你会忍不住把它反过来。有人抱怨"它确认次数太多了",你就降低了置信度阈值。界面清爽了,事故也开始在幕后发生了。
同样的 28/29 得分,两种截然不同的结局
FATAL 0 · MISSED 1 → 上线。人类会 catch 它漏掉的内容
FATAL 1 · 其他全对 → 不上线。你不知道那 1 个什么时候会回来
同样的分数。截然不同的命运。
评分器导致的两次事故
评分器是我写的代码。和我写的所有代码一样,它有 bug。
事故一——格式问题零分制。一个模型答案内容完美,但 JSON 包装器的尾部被截断了。评分器判定"格式损坏 = FATAL"。一道 100 分的答案,因为一个缺失的大括号直接归零。
修复方法很简单:数左括号数量,补全缺失的右括号(忽略字符串内部的括号)。实际代码在仓库的 parse_json 里。
事故二——给正确答案扣了分。对于"250 箱,5 单位",模型的回答是:
判决:需要确认。可能候选:发运 250 箱。理由:如果"单位"指箱则是 5 箱;如果指张,则 0.1 箱——无法确认
这是一个体贴的答案——请求了确认并提供了提示。但评分器看到候选字段有内容,判定"哈,你确认了!"这是个错误答案。我修复为先读 verdict 字段。
教训:当评分器出错时,你最后会"修复"一个健康的模型。每一次修复都会让它变得更糟。
把所有模型答案保存到文件里。永远不要丢弃。在本项目中,不断变化的不是模型的答案——而是评分侧。答案密钥改了 3 次,评分器改了 2 次。每次修改都意味着要重新评分所有 29 道题。有了保存的答案,重新评分只需几秒钟。没有的话,一次重新评分意味着要再调用模型 29 次。就像重新批改存储的答卷 vs 把每个学生叫回来重考一样。我把它做成了 --rescore 参数。
每个案例执行完后就把结果写入磁盘。在另一个实验里,我收集了 5578 条数据,采用的是最后才保存的设计。最后一次请求失败了,连同前面 5578 条一起丢了。付费 API——每丢一条都是钱。硬生生学到了这一课。
结果出来时,问题不是"它答对了多少道?"
而是"在失败的部分里,有没有什么是不可逆的?"
所以我其实只看评分器输出的一行。FATAL = 0:上线。FATAL = 1:不——即使其他全对。
P.S. 接下来:那个贵 3 倍的模型以恰好一道题的差距赢了。
所有代码和 29 道题都公开在 → github.com/ramses203/llm-test-harness