LLM 当裁判可靠吗:位置偏见、长度偏见、自我偏好
先说一个反直觉的结论:规则满分是最危险的信号。
因为它意味着没人会去看它。
我最近跑了一版"规则先行、LLM 兜底"的评测流程,5 条样本里 3 条规则满分被直接放行。其中一条恰恰是错的。
这篇讲三个事:这个盲区怎么来的、抽检预算该怎么分配、以及裁判自己的三个偏心。
一、5 条样本里那条漏网的
规则有四项:意图路由、工具调用、引用编号、关键词命中。全过就不请 LLM,省一次调用。
text
[4] 积雪草精华适合什么肤质
规则打分:{intent_ok: True, tool_ok: True, ref_ok: True, keyword_ok: True}
⚠️ 陷阱:规则满分,但实际「答非所问」
------ 只绕弯说美白淡斑,没正面回答适合什么肤质
→ ✅ 规则满分,跳过 LLM
四项全过:
- 意图路由对:确实是产品咨询
- 工具调了:该查的查了
- 引用合法:编号在范围内
- 关键词命中:提到了产品名和成分
每一条都对,但它没回答问题。
这就是规则评测的天花板:能验证"该做的动作做了没有",验证不了"做了之后有没有解决问题"。
答非所问的三种形态
后来我把这类错归了归类,发现规则抓不到的主要是三种:
一,答了相邻问题。
问"适合什么肤质",答"主打美白淡斑"。两个都跟这个产品有关,但没回答被问的那个。
二,答了更大的问题。
问"这款多少钱",答"这是我们今年的明星产品,成分温和、适合日常使用,性价比很高"。信息量不小,就是没有价格。
三,答了但没给结论。
问"敏感肌能用吗",答"这款含有积雪草提取物,积雪草具有舒缓修护的作用,很多敏感肌用户反馈良好"。绕了一圈,没说"能用"还是"建议先测试"。
三种形态有个共同点:关键词全命中,语义上全是错的。
这就是为什么正则表达式永远解决不了这个问题------它判的是"这些字出现了没有",而这个问题的答案在"这些字组成了什么"。
二、盲区出在模式名里的一个假设
这个模式叫 only_on_rule_fail------只有规则没通过才请 LLM。
逻辑很自然:规则能判的不花钱,判不了的才花钱。
但它隐含了一个假设:规则满分的样本没问题。
第 4 条证伪了这个假设。而且失效方式很阴险:
| 样本类型 | 会被谁看 | 错误存活概率 |
|---|---|---|
| 规则不满分 | 被标记 → 人工或 LLM 看 | 低 |
| 规则满分 | 没人看 | 高 |
一个编造价格的回答,会被 faithfulness 抓住。 一个答非所问的回答,四项全绿,安安静静过了。
规则满分不是"质量好"的证据,是"没人检查"的状态。
三、抽检预算该怎么分
想通这一点,我把配比调成了反直觉的:
python
RULE_FAIL_SAMPLE_RATE = 1.00 # 规则不满分:100% 进二审
RULE_PASS_SAMPLE_RATE = 0.05 # 规则满分:抽 5%
为什么反直觉?因为随机抽 5% 的话,两类样本被抽到的概率一样。
但期望收益不一样:
erlang
期望抓到错误数 = 样本数 × 抽检率 × 该类样本的错误率
规则不满分: n₁ × 100% × 高错误率
规则满分: n₂ × 5% × 低错误率
关键在 n₂ 远大于 n₁。本例 5 条样本里 3 条满分,占 60%。
即使满分样本的错误率只有失败样本的十分之一,n₂ × 5% 也可能和 n₁ × 100% 是同一个量级。
成本增量算给你看
| 模式 | LLM 调用量 | 覆盖 |
|---|---|---|
| 全量二审 | 5 次(100%) | 全部 |
| 只审失败 | 2 次(40%) | 规则未满分的 |
| 失败全审 + 满分抽 5% | 2.15 次(43%) | 两边都沾 |
从 2 次涨到 2.15 次,增幅 7.5%,覆盖了一个 100% 的无人区。
这个 7.5% 很多人会高估。以为加抽检要花很多钱,实际上满分样本本来就占多数,抽 5% 的绝对量很小。
抽检预算该按"错误存活概率"分配,不按"样本量"分配。 这是本文最实用的一条。
四、裁判自己的三个偏心
请了 LLM 当裁判,还有第二层问题:裁判本身不可靠。
业界的公开研究记录过 LLM-as-Judge 的三个系统性偏差。这里只讲工程应对。
位置偏见
把 A、B 并排给裁判问哪个好,它倾向于固定选前面那个(或后面那个,不同模型方向不同)。
应对:交换顺序跑两次,一致才采信。
python
def judge_pairwise_robust(judge, q, a, b):
first = judge(q, a, b)
second = judge(q, b, a)
return first if first == second else "tie"
成本翻倍。所以只对"分数卡在阈值附近"的样本做。远离阈值的不用浪费。
长度偏见
长回答更容易得高分------信息多、看起来更努力,裁判容易被"内容丰富"迷惑。
应对一: 提示词明确"长度不计分,冗长不加分,简洁不扣分"。 应对二: 评测集里放"短但正确"和"长但啰嗦"的对照样本,测它有没有压住。
第二条比第一条可靠。提示词是请求,对照样本是测量。
自我偏好
用自己的模型当裁判,给它自己生成的答案打分,分数偏高。
应对:裁判和被测用不同家族。 被测 DeepSeek,裁判就别用 DeepSeek。
这条最容易被忽略,因为"用同一个模型"最省事------一把 Key 搞定两件事。但它恰恰最不可靠。
五、裁判和人的一致率,必须高于你要验证的改进幅度
这是我认为最该被写进规范的一条。
校准方法是:人工标 20 条,让裁判判同一批,算一致率。
| 一致率 | 判断 |
|---|---|
| > 90% | 可用 |
| 80% ~ 90% | 可用,但要知道它偏哪一侧 |
| 70% ~ 80% | 提示词还得改 |
| < 70% | 判断和人类不是一回事,别用 |
然后关键的一句:
如果你要验证 5 个点的提升,而裁判和人的一致率只有 75%,那这个裁判分辨不出那 5 个点。
这和上一篇讲样本量的逻辑是同一个:测量的分辨率必须小于被测的差异。
样本量不够,是量不出来。 裁判不准,也是量不出来。
两个坑经常同时存在,而且都不报错。
六、分数的用法:只能相对,不能绝对
LLM Judge 的分数分布往往很窄,都堆在 0.8 到 0.9 之间。
这时候设绝对阈值("0.85 算达标")意义不大------稍微一动就跨过去了,而且不同批次的分布中心会漂。
能用的口径是:同一个裁判、同一批样本、两个版本之间的相对变化。
不能用的口径:
- "语义分要 0.9 以上"(这是裁判的刻度,不是客观质量)
- "这批 0.86 比上批 0.85 好"(差异在噪声内)
- "换个裁判再比对"(刻度会整体平移)
这条最实用。很多团队把 LLM Judge 分数当 KPI,然后花大量时间在 0.01 的波动上找原因。
七、一个工程细节:解析失败要记 0 分
裁判返回的 JSON 可能被 ```````json```` 围栏包住,也可能因网络超时直接失败。
这种情况必须记 0 分,不能跳过。
因为跳过意味着这条样本"没被审过",而它很可能挂着一个"规则满分"的身份。跳过之后它就混进达标名单了------这恰恰是本文要讲的那个盲区。
宁可错杀,不能让未知样本溜过去。
配套的代码处理:剥围栏 → 尝试解析 → 失败返回全 0 并打上 parse_error 标记。
python
except Exception as e:
return {"relevance": 0.0, "hallucination": 1.0,
"completeness": 0.0, "score": 0.0,
"parse_error": str(e)}
parse_error 这个字段要留着,它让你能统计"裁判有多少次没返回合法 JSON"。这个比例超过 5%,说明提示词或网络有问题。
八、还有一个容易搞反的字段
裁判输出三个语义分,其中 hallucination 是反向的------越高越差。
如果汇总时写成 (relevance + hallucination + completeness) / 3,编得越厉害反而分数越高。
这个 bug 很隐蔽:代码能跑、分数也像个分数、报表也能出。
我的建议是直接改名:
python
"hallucination_free": 0到1, # 别叫 hallucination
语义反转在命名上,比在注释里提醒可靠得多。
九、结论
四句话:
- 规则满分不是质量证据,是没人检查的状态。 盲区就在这儿。
- 抽检预算按错误存活概率分配。 满分样本抽 5%,成本涨 7.5%,覆盖一个无人区。
- 裁判和人的一致率要高于你要验证的改进幅度。 否则量和没量一样。
- 裁判分数只能做相对比较。 0.86 不代表 86 分,只代表这批里比 0.72 好。
最后一句:LLM Judge 的价值不在"比规则更聪明",在"能覆盖规则看不见的地方"。
如果你的抽检全花在已经被标记的样本上,那你等于用更贵的方式,做了规则已经在做的事。
下一篇讲回流:失败样本怎么变成提示词里的具体规则。难点不在技术,在"这条失败该改哪一条"的判断------改错了,同类问题会换个形状再来。