LLM grader 接入 eval harness
昨天(Day 124)写好了独立 LLM 分类 prompt,产出 200 条 {case_id, predicted_typology, reason}——但那还只是「另一套预测」。今天把它正式接进 eval harness:核心动作是解耦「被测系统」与「裁判」,把 evalBaseline.ts 的循环自评换成独立 grader,再用 Cohen's κ 度量 LLM 标签与 AMLSim 真
阶段: B13 · FATF typologies + BSA grader + 用户研究(Day 121-130) 标签: #cohens-kappa #grader-decoupling #inter-rater-agreement #external-validity
今日导引(由浅入深)
昨天(Day 124)写好了独立 LLM 分类 prompt,产出 200 条 {case_id, predicted_typology, reason}——但那还只是「另一套预测」。今天把它正式接进 eval harness:核心动作是解耦「被测系统」与「裁判」,把 evalBaseline.ts 的循环自评换成独立 grader,再用 Cohen's κ 度量 LLM 标签与 AMLSim 真值的一致性。
在 B1→B18 曲线上,这是 B13 的度量收口——从「裸一致率」升级到「扣除偶然一致后的 κ」,让「裁判可信吗」这个问题有了可量化的答案。这也直接通向 B14:循环 baseline 在这里被正式宣判死刑。
最小可判定产出:复用 cohensKappa.ts 算 Day 124 LLM 标签 vs 真值的 κ,把 grader 调用从 evalBaseline.ts 拆出接独立接口。
1. 机理精读
解耦的核心:被测系统 ≠ 裁判。 evalBaseline.ts 的循环自评里,规则引擎既生成数据又评数据——被测和裁判是同一个,分数虚高。
今天要做的解耦:裁判用 LLM 标签(Day 124 产出,独立第三方视角),真值用 AMLSim 注入标签(数据生成时就写死的 ground truth)。两者是来源完全不同的两个标签源,对它们算一致性才有意义——这才是「外部 grader」的正确接法。
算出的 κ 度量的是「裁判 LLM 与数据真相的超随机一致程度」,而不再是「规则自己跟自己」。换句话说,解耦把「评测」从被测系统里拆出来变成一个独立可校准的部件——这是后面 B14 能用外部 ground-truth 做正经 AML eval 的结构前提。
为什么用 Cohen's κ 而不是裸一致率。 直接报「LLM 和真值在 200 条里 agree 了 X%」会高估——因为类别基率会带来偶然一致。设想 normal 占 60%,那么哪怕 LLM 瞎猜,光靠「两边都常猜 normal」就能撞对一大堆。
Cohen's κ = (po − pe) / (1 − pe),其中 po 是观测一致率、pe 是按各类边际概率算出的偶然期望一致率——κ 扣除了偶然一致,只保留「超越随机的真实一致」。约定俗成:κ > 0.6 算实质一致 (substantial agreement)。κ 比裸一致率可信,正因为它对类别基率做了修正。
κ 的常用解读带(Landis & Koch 1977 经典分级,仍是现役约定):
| κ 区间 | 解读 |
|---|---|
| < 0.00 | 比随机还差(系统性反向) |
| 0.00–0.20 | 轻微 (slight) |
| 0.21–0.40 | 一般 (fair) |
| 0.41–0.60 | 中等 (moderate) |
| 0.61–0.80 | 实质 (substantial) ← 裁判可信下限 |
| 0.81–1.00 | 近乎完美 (almost perfect) |
落到 AML:LLM 裁判要进生产 grading 链路,κ 至少要进 0.61–0.80 的「实质」带;若 LLM-vs-真值 κ 落在 0.41–0.60,说明 prompt rubric 还不够清晰或某些 typology 本身判定模糊(Day 126 的模型间 κ 会进一步定位是哪类)。
要强调的是:解读带是经验约定不是法定门槛——同一个 κ=0.65 在「裁判校准」场景算够用,在「替代人工标注」场景可能仍嫌不足。所以 κ 要和用途绑定解读,并永远配 CI:κ=0.65 但 CI=[0.40, 0.90] 时,下界已掉到「中等」带,不能拍胸脯说裁判可信——这正是下面要讲的小样本不确定性。
关键权衡:κ 的小样本不确定性。 κ 是点估计,N 小时极不稳。所以本仓 cohensKappaWithCI 配了百分位 bootstrap 95% CI——重采样 N 个配对观测 B 次(默认 2000),看 κ 的分布宽度。小 N → CI 很宽 → 点估计不可信,这正是 P1 要求校准集 N≥50 的原因。
今天的 LLM-vs-真值 κ 是「裁判 vs 数据真值」的一致性;但还有一个更关键、更难的 κ 待跑——judge-human κ(LLM 裁判 vs 人工金标,需 ≥50 条人工手标),那是裁判可信度的终极证据,目前仍 gated。
三种 κ 别混淆——它们度量不同的东西。 B13→B14 之间会出现三个 κ,对象各不相同:
- LLM-vs-真值 κ(今天):裁判标签 vs 数据注入真值——回答「裁判跟数据真相多一致」。
- 模型间 κ(Day 126):DeepSeek-V4 vs Qwen3 两裁判——回答「换个裁判结论稳不稳」。
- judge-human κ(仍 gated):LLM 裁判 vs 人工金标(≥50 手标)——回答「裁判能不能替代人」,这是终极证据。
- 三者都用同一个
cohensKappa,但喂进去的两列来源不同,结论也不能互相冒充。
今天只回答第一种:LLM 裁判的标签,扣除偶然后,和数据真值有多一致。
2. 代码走读:cohensKappa.ts 与待拆的 evalBaseline
Read 了 src/agent/eval/cohensKappa.ts 与 src/aml/evalBaseline.ts,确认 κ 工具与待拆的循环结构均真实存在:
src/agent/eval/cohensKappa.ts(已 built+tested,纯 + 确定性,RNG 可注入,无 key):
cohensKappa(a: Label[], b: Label[]): KappaPoint— 两个等长标注数组求 κ。先算 po(agree/n),再用两边各自的类别边际频率算 pe(pe += (c1[c]/n)·(c2[c]/n)),κ =(po − pe)/(1 − pe)。退化边际(两裁判都只用同一个类)时 pe=1,约定 po=1 报 1、否则报 0。cohensKappaWithCI(a, b, {bootstrap, rng})— 在点估计上加百分位 bootstrap 95% CI:重采样 N 个配对 B 次(默认 2000),对每个重采样算 κ,排序后取 2.5/97.5 百分位为ci95。返回KappaResult(含n、ci95、bootstrapSamples)。文件头注释直说用途:「校准 LLM-judge 对人工金标——κ 量化裁判超越随机的一致程度;CI 告诉你校准集是否够大可信(小 N → 宽 CI,正是 P1 要 N≥50 的理由)」。percentile(sorted, p)— 线性插值百分位,bootstrap CI 的底层工具。
src/aml/evalBaseline.ts(循环自评,待拆):
evalRuleBaseline(ds)里predicted = assessCase(c).topTypology ?? 'normal'跟同源c.label比——这就是要拆掉的循环。今天把 grader 调用从这里抽出,接独立 grader 接口(裁判 = LLM 标签,真值 = AMLSim/金标 label),让度量不再「规则评规则」。
走读结论:cohensKappa/cohensKappaWithCI 真实存在且 BUILT+tested;evalRuleBaseline 是待拆的循环 baseline。今天复用 κ 工具算 Day 124 LLM 标签 vs 真值,无需新写统计逻辑。
接线方式上有个细节值得记:cohensKappaWithCI 的 rng 是可注入的(opts.rng),所以单测时传一个确定性 PRNG 就能让 bootstrap CI 完全可复现——这与本仓「纯 + 确定性、无 key 可单测」的纪律一致。生产跑校准时用 Math.random、测试时注入种子 RNG,同一份 κ 逻辑两用。
2.1 推导:一个最小 κ 手算
设 10 个案件,LLM 标签 a 与真值 b:8 个一致、2 个不一致;类别只有 {laundering, normal},a 中 laundering=5/normal=5,b 中 laundering=6/normal=4。
- po = 8/10 = 0.80
- pe = (5/10·6/10) + (5/10·4/10) = 0.30 + 0.20 = 0.50
- κ = (0.80 − 0.50) / (1 − 0.50) = 0.30/0.50 = 0.60
裸一致率 0.80 看着很高,但扣除偶然期望 0.50 后,κ 只有 0.60——恰好踩在「实质一致」门槛上。这就是为什么不能只报裸一致率:0.80 的一致里有 0.50 是瞎猜也能撞上的。(此为教学手算,非本日真实 200 条产出。)
把它推向极端更能看清 κ 的意义:若某类占 95%,裸一致率轻松到 0.90+,但 pe 也会逼近 0.90,κ 反而趋近 0——κ 诚实地告诉你「这一致几乎全是基率撞的,裁判没贡献信息」。对 5 类的 typology 场景(normal 是多数类),这正是必须用 κ 而非裸一致率的根本原因。多裁判(≥3)时再升级到 Fleiss' κ,本日两方(LLM vs 真值)用 Cohen's κ 即可。
3. 今日实战
- 复用
src/agent/eval/cohensKappa.ts的cohensKappa(a, b)算 Day 124 LLM 标签(数组 a)vs AMLSim/金标真值(数组 b)的点估计 κ。 - 用
cohensKappaWithCI(a, b, {bootstrap: 2000})加 95% bootstrap CI;测试时注入确定性 RNG 保证可复现。 - 把 grader 调用从
evalBaseline.ts(evalRuleBaseline)中拆出,接独立 grader 接口——裁判=LLM 标签,真值=注入 label,度量不再「规则评规则」。 - 补 1 个 passing test 锁住 κ 计算行为;记录 κ 落在 Landis & Koch 哪个解读带,判断裁判是否达「实质一致」下限。
4. 今日实测 / 产出
- 已完成:
src/agent/eval/cohensKappa.ts的cohensKappa/cohensKappaWithCI已 BUILT+tested。 - 待跑:κ 数字(1 个)= 待跑(需 Day124 LLM 输出 + key),将产出 LLM-vs-真值 κ + test green。
- 仍 gated:judge-human κ(≥50 手标)仍待跑——这是裁判可信度的终极证据,目前缺人工金标。
- 状态诚实:上面手算 κ=0.60 是教学示例,不是 200 条 LLM 标签 vs 真值的真实 κ;真实 κ 未跑出前不得臆造。
5. 常见误区 / 陷阱
- 继续用
evalBaseline.ts循环自评作最终指标:同一套规则自当裁判会虚高——这正是 B14 要「杀死」的循环 baseline。今天起 grader 必须独立。 - 报裸一致率而非 κ:类别不平衡时裸一致率被偶然一致抬高,κ 才扣除了基率偏差。κ>0.6 才算实质一致。
- 小 N 报 κ 点估计不给 CI:N 小时 κ 极不稳,必须配
cohensKappaWithCI看 95% CI 宽度——这正是 P1 要校准集 N≥50 的原因。 - 把 LLM-vs-真值 κ 当成裁判可信的终极证据:真正的终极证据是 judge-human κ(vs 人工金标),目前仍待跑——别用前者冒充后者。
6. 学习资源(每条带 YYYY-MM)
- Anthropic — Demystifying evals(2026-01):LLM-judge 与人工金标的 κ 校准、grader 解耦、N≥50 校准集要求。
- Cohen, J. — A Coefficient of Agreement for Nominal Scales(1960,经典原始定义):κ = (po−pe)/(1−pe) 的来源(历史经典,最新实践见上)。
- scikit-learn —
cohen_kappa_score文档(持续维护,执行当周查版本):κ 的标准实现与边界处理。 - 本仓代码:
src/agent/eval/cohensKappa.ts(cohensKappa/cohensKappaWithCI,已 built+tested)、src/aml/evalBaseline.ts(evalRuleBaseline,循环自评待拆)。
SOTA检查 (2026-06 更新)
- 现役主线:Cohen's κ 仍是标注一致性金标准,2026 年评测裁判校准的现役度量;配 bootstrap CI 应对小样本不确定性是当前最佳实践。多裁判场景可升级 Fleiss' κ(≥3 裁判)。
- 避免/禁用:避免继续用
evalBaseline.ts的循环自评作最终指标——同一套规则自当裁判会虚高,这是 B14 要杀死的循环 baseline;避免只报裸一致率(被类别基率抬高)。 - 下次复查点:执行当周确认 Day 124 LLM 输出 + key 后跑出 LLM-vs-真值 κ;judge-human κ(≥50 手标)仍 gated,需先攒人工金标——这是 B13→B14 之间最关键的待补证据,复查人工标注进度。
自测问答(讲得出才算掌握)
- Q:裸一致率 80% 为什么不能当裁判可信的证据? A:类别基率会带来偶然一致——若 normal 占 60%,瞎猜也能撞对一大堆。κ=(po−pe)/(1−pe) 扣除了偶然期望 pe,只留超越随机的真实一致。手算例子里 po=0.80 但 κ 只有 0.60,那 0.50 的偶然一致被扣掉了。
- Q:κ 点估计为什么要配 bootstrap CI?
A:κ 在小 N 下极不稳。
cohensKappaWithCI重采样 N 个配对 B 次(默认 2000)看 κ 分布宽度——小 N → 宽 CI → 点估计不可信。这正是 P1 要求校准集 N≥50 的统计依据。 - Q:解耦「被测系统」和「裁判」具体解耦了什么?
A:把
evalBaseline.ts里「预测来源=规则、真值来源=同一套规则生成」拆成「预测来源=独立 LLM、真值来源=AMLSim 注入标签」两个异源标签流,对它们算 κ 才有意义——这是 B14 杀死循环 baseline 的核心动作。
衔接
- 昨天:Day 124 — LLM-as-classifier prompt 设计(独立 LLM 做 typology 判定,产出 200 条
{case_id, predicted, reason})。 - 今天:把循环自评换成独立 grader,用 Cohen's κ 度量 LLM 标签 vs AMLSim 真值的超随机一致性(B13 度量收口)。
- 明天:Day 126 — Qwen3 对照与模型间一致性(引入第二模型做裁判,算 DeepSeek-V4 vs Qwen3 的模型间 κ,暴露模型特异性误判)。