一个让人不安的数字

在 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 实测):

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

{"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 断言,先用脚本跑一遍:

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 现在还准?它上个月准不代表这个月还准。」

校准闭环的设计

┌───────────────┐
│ 每次测评执行   │
│ (产出 grading) │
└───────┬───────┘
        │
        ▼
┌───────────────────────┐
│ 定期抽检(每月/每季)   │
│ 从 semantic 断言中       │
│ 随机抽 20-30 条          │
└───────┬───────────────┘
        │
        ▼
┌───────────────────────┐
│ 人工标注               │
│ 同一条断言,人给 pass/fail │
└───────┬───────────────┘
        │
        ▼
┌───────────────────────┐
│ 计算一致性              │
│ Cohen's Kappa 或         │
│ 简单 Agreement Rate     │
└───────┬───────────────┘
        │
        ▼
┌───────────────────────┐
│ 决策                    │
│ ≥ 80%:Judge 可信       │60-80%:修改 Judge prompt│
│ < 60%:停用,换人工      │
└───────────────────────┘

具体操作方法

Step 1:构建校准数据集

从历史测评中挑选 semantic 断言的评审结果,人工复判:

| eval_id | 断言 | Judge判定 | 人工判定 | 一致? |
|---------|------|----------|---------|--------|
| eval-3  | 回复包含审批状态说明 | pass | pass | ✅ |
| eval-7  | 格式符合报销模板 | pass | fail | ❌ Judge放水 |
| eval-12 | 引导用户选择正确类型 | fail | pass | ❌ Judge误杀 |

Step 2:计算偏差指标

# 简单一致率
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:记录和趋势追踪

// 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 应该包含以下要素:

必须有的四要素

## 你的角色
你是一个严格的 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 判定人工判断偏差
1L1 description缺不触发场景,3/5同意,确实缺不触发条件0
2L3 复杂度统计所有箭头,复杂度 22,3/5箭头多为流程格式,修正后为 20,4/51
3L2 HiL有复述但无确认等待,3/5同意,复述不是确认0
4TriggerTP=5/5,TN=5/5同意,正负样本判断一致0
5L4 冗余session.json 引用 7 次,4/5频次准确,但是否冗余需看语义0

这 5 个 case 说明:

1. Judge 在结构完整性、HiL 语义、触发判断上比较可靠。
2. Judge 在复杂度和冗余这类“统计 + 语义判断”场景中容易出现偏差。
3. 低 confidence 或语义依赖强的判断,应标记 human_review_required。

Judge prompt 可以补充一条规则:

统计条件分支时,区分格式符号和逻辑符号。
流程描述中的箭头不算条件分支。
只有表达“如果...则...”语义的才算。

这类校准应成为常规动作:模型升级后、Judge prompt 改动后、评分规则变化后,都应该抽样复核。