返回 AICAP-180
B5 · Day 47tool/context engineering + 指标树

判分一致性校准

昨天(Day 46)我们用受控 A/B 把「v2 工具更好」变成了一个带 Δ 的数字——但这个数字完全建立在「judge 判得准」这个未经检验的前提上。

阶段: B5 · tool/context engineering + 指标树(Day 41-50) 标签: #cohens-kappa #llm-judge #calibration #bootstrap-ci

今日导引(由浅入深)

昨天(Day 46)我们用受控 A/B 把「v2 工具更好」变成了一个带 Δ 的数字——但这个数字完全建立在「judge 判得准」这个未经检验的前提上。

如果裁判本身和人类标注口径不一致,Δ 就是建在沙子上。

今天(Day 47)在 B1→B18 曲线上做的是 eval 体系的地基加固:用 Cohen's κ 量化 LLM-judge 与人工金标的一致性,给昨天的 A/B 结论上一道可信度保险。

明天(Day 48)才有资格谈跨模型复核——因为只有 judge 可信,跨模型的 Δ 方向比较才有意义。

今天的最小可判定产出:≥50 样本上 judge vs 人标的一个 κ 值 + 95% bootstrap CI(待 key 产 judge 输出)。

1. 机理精读

为什么需要 κ 而不是看「同意率」。 最朴素的 judge 评估是 raw agreement(judge 和人标判一样的比例 po)。 但 po 有个致命缺陷:当类别分布偏斜时它会虚高。 设想任务 90% 都该判 pass,一个永远输出 pass 的裁判 raw agreement 就有 90%,看起来很准,实际上毫无判别力。 Cohen's κ 的设计正是为了扣除「碰巧一致」的部分κ = (po − pe) / (1 − pe),其中 pe 是按两个评分者各自的边际分布算出的期望偶然一致率。 κ=0 表示「只达到随机水平」,κ=1 表示「完美一致」。这是 Anthropic《Demystifying evals》(2026-01) LLM-judge 校准段落的核心论点。

κ 的阈值与含义。 业界惯例(Landis & Koch 量表):κ<0.2 微弱、0.2–0.4 一般、0.4–0.6 中等、0.6–0.8 显著、>0.8 几近完美。 本项目把 κ≥0.6 作为「judge 可接受、A/B 结论可信」的门槛。 低于 0.6 意味着裁判和人类口径差太多,此时任何基于该 judge 的 Δ 都不该被引用。

为什么要配 bootstrap CI 而不只报点估计。 κ 是在一个有限校准集上算的,N 小则估计不稳。 N=20 算出 κ=0.65 可能 CI 宽到 [0.3, 0.9]——你根本不知道真实 κ 是否 ≥0.6。 所以必须报置信区间。本仓用百分位自助法:把 N 对配对观测重抽样 B 次,每次重算 κ,取第 2.5/97.5 百分位作 95% CI。 这也是 P1 阶段坚持「N≥50」的统计原因:只有样本够大,CI 才够窄到能下结论。

same judge 约束在校准里的落点。 Day 46 的 A/B 要求 v1/v2 共用同一 judge。今天进一步要求:这个被共用的 judge,必须先通过 κ 校准。 换言之顺序是——先校准 judge(κ≥0.6)→ 再用这个 judge 跑 v1/v2 → 才能引用 Δ。 校准和 A/B 不是两件独立的事,而是同一条证据链的上下游。

边界:κ 校准的是「判分口径」,不是「任务难度」。 κ 高只说明裁判和人标一致,不说明任务集本身覆盖得好。 一个只有简单任务的 eval,即使 κ=0.9,也测不出工具的真实差异。 所以 κ 是必要条件不是充分条件——它保证「测得准」,但「测得全」要靠任务集设计(Day 46 的 tasks.ts)。

2. 代码走读:cohensKappa.ts

本仓 src/agent/eval/cohensKappa.ts 是纯函数 + RNG 可注入、单测绿、无需 key 的统计装置。

  • cohensKappa(a, b):对两个等长标签数组算点估计。先校验等长(不等抛 'cohensKappa: rater arrays must be equal length')与非空(n===0'need >=1 paired observation')。
  • po 计算:遍历一遍统计 agreea[i]===b[i] 计数),po = agree / n,同时把所有出现过的类别收进 cats
  • pe 计算:用两个 Map c1/c2 统计各评分者每类别的边际频次,pe += (c1.get(c)/n)*(c2.get(c)/n) 对每个类别累加——这就是期望偶然一致率。
  • κ 公式const denom = 1 - pe; kappa = denom===0 ? (po===1?1:0) : (po-pe)/denom。注释明确处理了退化边际(两评分者都只用同一类别,pe=1,κ 数学上无定义):完美一致报 1,否则报 0。这是常见 κ 实现忽略的边界。
  • cohensKappaWithCI(a, b, {bootstrap, rng}):先算点估计,然后 B 次(默认 2000)重抽样——每次对 N 个配对位置 const j = Math.floor(rng()*n) 同步抽 ra[i]=a[j]rb[i]=b[j]配对重抽,保持配对结构),重算 κ 收进 samples
  • CI 输出samples.sort(...) 后取 percentile(samples, 2.5)percentile(samples, 97.5),返回 { kappa, po, pe, n, ci95, bootstrapSamples }
  • percentile(sorted, p):线性插值百分位(非最近邻),对小样本更平滑。
  • RNG 可注入opts.rng ?? Math.random):让单测能用确定性伪随机复现,这是「数学骨架已绿」的实现保证。

判分输出与人标的对接在 runner 侧:

  • scripts/run-agent-eval.tsloadHumanLabels()agent-evals/labels.json{ "<taskId>": "pass"|"fail"|"unknown" }),JSON 非法时 console.warn 并跳过 κ。
  • runTaskEval(...) 把它作为 humanLabels 传入,跑出的报告里带 judgeHumanKappa(κ + ci95 + N),runner 打印 judge-human kappa: <k> 95% CI [...] (N=...)
  • seed 提到的 agent-evals/labels.example.json 即此人标的样例形态。

手算一个最小 κ(验证 raw agreement 的陷阱)

设 N=10 个任务,judge 与人标如下(p=pass, f=fail):

#12345678910
人标ppppppppff
judgepppppppfpf
  • agree:第 1–7、10 共 8 个一致 → po = 8/10 = 0.80。光看这个 raw agreement 你会以为裁判很准。
  • 边际:人标 p=8、f=2(→ 0.8 / 0.2);judge p=8、f=2(→ 0.8 / 0.2)。
  • pepe = (0.8×0.8) + (0.2×0.2) = 0.64 + 0.04 = 0.68——因为类别严重偏向 pass,两个裁判「碰巧都判 pass」的概率高达 0.68。
  • κκ = (po − pe)/(1 − pe) = (0.80 − 0.68)/(1 − 0.68) = 0.12/0.32 = 0.375
  • 结论:raw agreement 0.80 看似不错,κ 却只有 0.375(中等偏下,未达 0.6 门槛)。这正是「不看 κ 会被偏斜类别骗」的数值证据——cohensKappa()c1/c2 边际算出的 pe 把这层虚高扣掉了。N=10 时再叠加 bootstrap CI,区间会宽到根本不敢下「judge 可用」的结论,这也是 N≥50 的由来。

3. 今日实战

  1. 准备 ≥50 个 tool-use / AML 任务的人工金标,写进 agent-evals/labels.json(taskId → pass/fail/unknown)。
  2. 跑一次带 judge 的真实 eval(pnpm eval:agent --provider openrouter --model deepseek/deepseek-chat),让 runner 产出 judge 标签。
  3. runner 会在报告里输出 judgeHumanKappa(κ + 95% CI + N);或离线直接调 cohensKappaWithCI(judgeLabels, humanLabels)
  4. 确认本次校准过的 judge 配置,与 Day 46 A/B 两组共用的是同一个——替换掉旧的 evalBaseline 自评循环。
  5. 无 key 时:用 cohensKappa.ts 单测里的固定数组验证 κ + CI 计算路径全绿,得到逻辑可信度;真实 κ 待 key 后产 judge 输出再算。

4. 今日实测 / 产出

  • κ harness 与 bootstrap CI 已绿cohensKappa.ts 单测通过,纯函数 + RNG 可注入)。
  • 本次 ≥50 样本的 κ 值「待跑(需 OPENROUTER_API_KEY 产 judge 输出)」;目标 κ≥0.6,附 95% bootstrap CI
  • 代码路径已就绪(cohensKappaWithCI + runner 的 loadHumanLabels/judgeHumanKappa)。
  • 本日不臆造 κ 数值;seed 标注待跑就写待跑。

5. 常见误区 / 陷阱

  • 只看 raw agreement 不看 κ:偏斜类别下 raw agreement 虚高,κ 才扣除偶然一致——seed 明确点名的反模式。
  • N 太小就下结论:N=20 的 κ 点估计 CI 可能宽到跨过 0.6,必须报 CI、争取 N≥50。
  • 校准用的 judge 和 A/B 用的 judge 不是同一个:校准失去意义,A/B 的 same-judge 约束被破坏。
  • 忽略退化边际:两评分者都判同一类时 pe=1,κ 无定义;本仓代码已处理,手算时别忘。

6. 学习资源(每条带 YYYY-MM)

  • Anthropic, Demystifying evals — 2026-01(LLM-judge 校准段,κ 阈值与方法)。
  • Cohen, J., A Coefficient of Agreement for Nominal Scales — 经典统计(κ 原始定义,奠基方法无过时风险)。
  • Landis & Koch, κ 强度量表 — 经典(0.6–0.8 显著、>0.8 几近完美的解释惯例)。
  • 本仓代码:src/agent/eval/cohensKappa.tsscripts/run-agent-eval.tsloadHumanLabels/judgeHumanKappa)— 2026-06。

SOTA检查 (2026-06 更新)

  • 当前主流:Cohen's κ 仍是 inter-rater agreement 的标准度量,2026-06 仍 SOTA 用于 LLM-judge 校准。
  • 2026 趋势:多标注者场景补充 Krippendorff's α(支持 >2 标注者、缺失值、多种测量尺度);κ 仍是两评分者基准。
  • 过时黑名单:只看 raw agreement / 同意率不看 κ(高基线偏差虚高);不报 CI 的单点 κ。
  • 下次复查点:若引入第三方人标或多裁判,评估是否迁移到 Krippendorff's α;OpenRouter 上 judge 模型 id 当周重验。

衔接

  • 昨天:Day 46 — A/B eval 设计,产出依赖 judge 的 Δ。
  • 今天:用 Cohen's κ(≥0.6 + bootstrap CI)校准 judge 与人标一致性,给 A/B 结论上可信度保险。
  • 明天:Day 48 — 跨模型稳健性,用第二个独立模型复核 Δ 方向是否一致。