一个让人不安的数字
在 SkillSentry 的实际测评中,我们发现一个现象:
同一批用例,脚本验证(exact_match)通过率 72%,LLM Judge(semantic)通过率 89%。
差了 17 个百分点。
脚本验证是确定性的:参数值是不是 "10"、接口有没有被调用、返回值里有没有某个字段------非黑即白,没有灰色地带。
而 LLM Judge 在评审时,经常这样写 evidence:「从整体输出来看,Skill 基本遵循了规则的精神」------这不是评审,这是放水。
这就是 LLM-as-Judge 的高估偏差(Overestimation Bias),行业内已被多篇论文确认,是所有使用 LLM 评审的系统必须面对的问题。
LLM-as-Judge 的三类系统性偏差
偏差1:高估偏差(Overestimation Bias)
表现:Judge 对"差不多对了"的输出倾向于判 pass。
原因:
- LLM 被训练为"有帮助的",它不擅长严厉否定
- 当输出看起来像正确答案时,Judge 倾向于找理由让它通过
- 对于模糊规则(如「输出应该友好」),几乎什么回答都能找到"友好的成分"
量化数据(SkillSentry 实测):
erlang
exact_match 断言:72% 通过
semantic 断言(同一规则的宽松表述):89% 通过
差值:17%(这 17% 就是 Judge 放水的部分)
偏差2:位置偏差(Position Bias)
表现:在 A/B 对比评估中,Judge 倾向于给先出现的选项更高分。
原因:LLM 的注意力机制对序列位置有偏好(尤其在长上下文中)。
影响场景 :SkillSentry 的 sentry-comparator(盲测对比)需要防范此偏差。
已有对策:comparator 随机化 A/B 顺序(with_skill 和 without_skill 随机分配为 A 或 B)。
偏差3:冗长偏差(Verbosity Bias)
表现:输出更长的回答往往获得更高评分,即使长度不代表质量。
原因:更长的输出"看起来更努力"、"信息更丰富",Judge 把详细性误认为正确性。
影响场景:text_generation 类 Skill 的评审(回答是否全面、是否遵循格式要求)。
为什么不能"全部用人工评审"
既然 LLM Judge 有偏差,为什么不全部换成人工?
| 维度 | 人工评审 | LLM Judge |
|---|---|---|
| 准确性 | 高(有领域知识时) | 中等(有系统偏差) |
| 成本 | 高($1-5/条) | 低($0.001-0.01/条) |
| 速度 | 慢(分钟级) | 快(秒级) |
| 一致性 | 低(不同人标准不同) | 高(同一prompt结果稳定) |
| 可扩展 | 差 | 好 |
正确答案是分层组合:确定性规则用脚本、模糊规则用 LLM Judge、高风险场景用人工复核。
SkillSentry 已有的偏差控制手段
在讨论"还需要补什么"之前,先看 SkillSentry 现有的防线:
手段1:断言精度分级(precision)
每条断言标注 exact_match / semantic / existence:
json
{"id": "E1", "text": "docStatus 参数为 10", "precision": "exact_match"},
{"id": "E2", "text": "回复友好且包含单号", "precision": "semantic"},
{"id": "E3", "text": "调用了 saveExpenseDoc", "precision": "existence"}
exact_match:脚本验证,LLM 不参与,零偏差semantic:LLM Judge 评审,承认存在偏差existence:最弱断言,只看有没有
通过率分别报告 :authoritative_pass_rate 只基于 exact_match,这是发布决策的核心数字。
手段2:脚本预验证(verify_assertions.py)
对所有 exact_match 断言,先用脚本跑一遍:
bash
python3 scripts/verify_assertions.py \
--transcript eval-1/with_skill/outputs/transcript.md \
--assertions eval-1/assertions.json \
--output eval-1/grading_script.json
脚本结论是最终结论,LLM 不允许翻转。 这条纪律写在 sentry-grader SKILL.md 中:
「脚本结论即最终结论,不允许 AI 重判(避免 15-20% 高估偏差)」
手段3:盲测消除确认偏差
sentry-comparator 在对比 with_skill vs without_skill 时采用盲测:Judge 不知道哪份输出来自有 Skill 的版本。这消除了"已知谁是被测对象"带来的确认偏差。
还缺什么:系统性校准闭环
上面三个手段都是设计时 的防线。但缺一个运行时的校准机制:
「我怎么知道我的 Judge 现在还准?它上个月准不代表这个月还准。」
校准闭环的设计
erlang
┌───────────────┐
│ 每次测评执行 │
│ (产出 grading) │
└───────┬───────┘
│
▼
┌───────────────────────┐
│ 定期抽检(每月/每季) │
│ 从 semantic 断言中 │
│ 随机抽 20-30 条 │
└───────┬───────────────┘
│
▼
┌───────────────────────┐
│ 人工标注 │
│ 同一条断言,人给 pass/fail │
└───────┬───────────────┘
│
▼
┌───────────────────────┐
│ 计算一致性 │
│ Cohen's Kappa 或 │
│ 简单 Agreement Rate │
└───────┬───────────────┘
│
▼
┌───────────────────────┐
│ 决策 │
│ ≥ 80%:Judge 可信 │
│ 60-80%:修改 Judge prompt│
│ < 60%:停用,换人工 │
└───────────────────────┘
具体操作方法
Step 1:构建校准数据集
从历史测评中挑选 semantic 断言的评审结果,人工复判:
markdown
| eval_id | 断言 | Judge判定 | 人工判定 | 一致? |
|---------|------|----------|---------|--------|
| eval-3 | 回复包含审批状态说明 | pass | pass | ✅ |
| eval-7 | 格式符合报销模板 | pass | fail | ❌ Judge放水 |
| eval-12 | 引导用户选择正确类型 | fail | pass | ❌ Judge误杀 |
Step 2:计算偏差指标
python
# 简单一致率
agreement_rate = consistent_count / total_count
# 偏差方向
overestimation_rate = judge_pass_but_human_fail / total_count # Judge放水率
underestimation_rate = judge_fail_but_human_pass / total_count # Judge误杀率
# 净偏差
net_bias = overestimation_rate - underestimation_rate
# 正值 = 系统性高估,负值 = 系统性低估
Step 3:修正策略
| 情况 | 修正动作 |
|---|---|
| 净偏差 > 15%(严重高估) | Judge prompt 增加严格性指令 + 增加"必须引用原文"要求 |
| 净偏差 5-15%(轻度高估) | 在报告中标注"语义通过率可能高估 X%,以精确通过率为准" |
| 净偏差 < 5% | 当前 Judge 可信,无需修正 |
| 误杀率 > 10% | Judge prompt 增加"合理变体应视为通过"的示例 |
Step 4:记录和趋势追踪
json
// calibration_history.json
[
{
"date": "2026-03-15",
"sample_size": 25,
"agreement_rate": 0.76,
"overestimation_rate": 0.20,
"underestimation_rate": 0.04,
"action": "增加 Judge prompt 严格性",
"judge_model": "current-judge-model"
},
{
"date": "2026-05-18",
"sample_size": 30,
"agreement_rate": 0.87,
"overestimation_rate": 0.10,
"underestimation_rate": 0.03,
"action": "无需修正",
"judge_model": "current-judge-model"
}
]
LLM-as-Judge 的适用边界
不是所有场景都适合用 LLM 做 Judge。明确边界:
适合用 LLM Judge 的场景
| 场景 | 为什么适合 |
|---|---|
| 回复友好度/语气评估 | 主观性强,脚本无法判定 |
| 格式合规性(宽松) | "大致符合模板"需要语义理解 |
| 相关性评估 | 回答是否与问题相关 |
| 安全性初筛 | 是否包含有害内容 |
| 执行轨迹合理性 | 步骤是否合理、是否有冗余 |
不适合用 LLM Judge 的场景
| 场景 | 为什么不适合 | 替代方案 |
|---|---|---|
| 参数值验证 | 有确定答案 | 脚本 exact_match |
| 事实准确性 | LLM 自己也可能幻觉 | 脚本验证 + 数据库对比 |
| 工具是否被调用 | 日志中有或没有 | 脚本检查 transcript |
| 数值计算正确性 | LLM 数学能力有限 | 脚本验证 |
| 安全漏洞的技术判定 | 需要安全领域专业知识 | 人工 + 专业工具 |
信任度分级
可信度:脚本验证 > LLM Judge(严格prompt) > LLM Judge(宽松prompt) > 自评审
这只是评审可信度的排序,不代表当前实现里存在加权评分机制。SkillSentry 的 authoritative_pass_rate 只基于 exact_match / 脚本验证断言计算;LLM Judge 的结论作为补充诊断信息展示,不参与 authoritative_pass_rate 的加权。
Judge Prompt 工程最佳实践
一个好的 Judge prompt 应该包含以下要素:
必须有的四要素
markdown
## 你的角色
你是一个严格的 AI Skill 评审员。你的任务是判断 Skill 执行结果是否满足断言。
## 评审规则
1. 只看证据,不推测:如果 transcript 中没有明确记录,判定为 fail
2. 不宽容"差不多":参数值必须精确匹配,"类似"不算通过
3. evidence 必须引用原文:你的判定依据必须来自 transcript/response 的直接引用
4. 沉默不是通过:如果某个步骤应该执行但 transcript 中没有记录,判定为 fail
## 输出格式
对每条断言,输出:
- pass/fail
- evidence:直接引用 transcript/response 中的文本(用引号括起来)
- reasoning:为什么这条证据支持/不支持断言(一句话)
## 断言列表
[具体断言...]
## 被评审内容
[transcript + response...]
常见的 Judge Prompt 反模式
| 反模式 | 问题 | 修正 |
|---|---|---|
| 「请评估输出质量」 | 太宽泛,Judge 自由发挥 | 给具体断言列表 |
| 「总体来看是否合格」 | 鼓励笼统判断 | 要求逐条评审 |
| 不要求引用证据 | Judge 可能凭印象判断 | 强制引用原文 |
| 只给 pass/fail 不要 evidence | 无法事后验证Judge判断 | 必须附带证据 |
校准频率建议
| 场景 | 校准频率 | 抽样量 |
|---|---|---|
| Judge 模型更换 | 立即全量校准 | 50+ 条 |
| 新 Skill 首次测评 | 测评完成后校准 | 20-30 条 |
| 常规运行 | 每月一次 | 20-30 条 |
| 在线监控 Judge | 每两周一次 | 15-20 条 |
| 发现异常高通过率 | 立即 | 对应 Skill 全量 |
落地行动清单
最小可行校准(今天就能做)
- 从最近一次 SkillSentry 测评中,取所有
semantic断言的 Judge 判定结果 - 人工复判 20 条(花 15 分钟)
- 计算 agreement_rate 和 overestimation_rate
- 如果高估 > 15%:修改 sentry-grader 的评审 prompt
持续校准机制
- 建立
calibration_history.json,每次校准后追加记录 - 在 SkillSentry grader-report 中增加「校准状态」字段
- 设置月度提醒:校准 Judge
总结
| 要点 | 结论 |
|---|---|
| LLM-as-Judge 有偏差吗 | 有,系统性高估 15-20% |
| 能完全消除吗 | 不能,但可以控制到可接受范围 |
| 核心防线是什么 | 脚本断言优先 + 分层信任 + 定期校准 |
| 什么场景不能用 | 事实验证、参数值、数值计算 |
| 多久校准一次 | 常规每月,模型更换后立即 |
一句话记住:把能用脚本验证的全部交给脚本,剩下的用 LLM Judge 但承认它有偏差,并定期用人工数据校准偏差大小。
实战补充:5 个 Judge 校准 Case
Judge 校准的目标不是证明 AI Judge 永远正确,而是知道它在哪些维度可靠、哪些维度需要人工复核。
| Case | 维度 | Judge 判定 | 人工判断 | 偏差 |
|---|---|---|---|---|
| 1 | L1 description | 缺不触发场景,3/5 | 同意,确实缺不触发条件 | 0 |
| 2 | L3 复杂度 | 统计所有箭头,复杂度 22,3/5 | 箭头多为流程格式,修正后为 20,4/5 | 1 |
| 3 | L2 HiL | 有复述但无确认等待,3/5 | 同意,复述不是确认 | 0 |
| 4 | Trigger | TP=5/5,TN=5/5 | 同意,正负样本判断一致 | 0 |
| 5 | L4 冗余 | session.json 引用 7 次,4/5 | 频次准确,但是否冗余需看语义 | 0 |
这 5 个 case 说明:
text
1. Judge 在结构完整性、HiL 语义、触发判断上比较可靠。
2. Judge 在复杂度和冗余这类"统计 + 语义判断"场景中容易出现偏差。
3. 低 confidence 或语义依赖强的判断,应标记 human_review_required。
Judge prompt 可以补充一条规则:
text
统计条件分支时,区分格式符号和逻辑符号。
流程描述中的箭头不算条件分支。
只有表达"如果...则..."语义的才算。
这类校准应成为常规动作:模型升级后、Judge prompt 改动后、评分规则变化后,都应该抽样复核。