三种 grader
昨天(Day 1)把 eval 切成 task / trial / outcome 三层,并立下「判分锚定可判定的最终产物」。今天往下钻一层:outcome 到底由谁来判。这就是 grader 三件套——code-based / LLM-judge / human——以及它们各自的盲点。这条线很关键,因为整个 P1 评测体系的可信度上限就是 grader 的可信度上限:grader 不准,下游 c
阶段: B1 · evals 方法论 + tokenization 起步(Day 1-10) 标签: #evals #grader #llm-judge #self-preference
今日导引(由浅入深)
昨天(Day 1)把 eval 切成 task / trial / outcome 三层,并立下「判分锚定可判定的最终产物」。今天往下钻一层:outcome 到底由谁来判。这就是 grader 三件套——code-based / LLM-judge / human——以及它们各自的盲点。这条线很关键,因为整个 P1 评测体系的可信度上限就是 grader 的可信度上限:grader 不准,下游 completion%、κ、A/B 全是沙上城堡。最小可判定产出:定位代码里「同一模型既答又判」的循环自评风险点,写出失效假设 + 替换方案草图。
1. 机理精读
grader 是 outcome 的「执行者」。Day 1 说 outcome 必须可判定,但「可判定」要落到一个具体机制——那个机制就是 grader。eval 体系的可信度上限 = grader 的可信度上限:grader 偏,下游 completion%、κ、A/B 全偏,且偏得很隐蔽(不是随机噪声,是系统性偏移,CI 都抓不住)。所以选对 grader、并知道每种 grader 的盲点,是评测能不能信的根。
三类 grader 及其权衡:
- code-based grader(确定性断言):用程序逻辑(正则、
JSON.parse、数值相等)判 outcome。最可信——同样输入永远同样输出,零成本可重复——但只覆盖可程序化的判据。开放式输出(如「这段 SAR 叙事写得是否完整」)无法用正则可靠捕捉。 - LLM-judge(模型判官):用一个 LLM 读「任务 + 期望行为 + 候选输出」给 pass/fail/score。覆盖开放式输出,能做部分给分,但引入系统性偏差:position bias、verbosity bias,以及最危险的 self-preference。
- human(人工):金标准,能判任何东西,但贵、慢、不可大规模重复。它的角色是「校准其余两者」,而非日常跑量。
三者的成本-覆盖-可信度三角。把三类 grader 放进一张权衡矩阵就一目了然:
| grader | 可信度 | 覆盖面 | 成本/可重复 | 主要盲点 |
|---|---|---|---|---|
| code-based | 高(确定性) | 窄(仅可程序化判据) | 极低 / 完全可重复 | 判不了开放式输出 |
| LLM-judge | 中(需校准) | 宽(含开放式) | 低 / 大体可重复 | position/verbosity/self-preference 偏差 |
| human | 最高(金标准) | 全 | 高 / 不可大规模重复 | 慢、贵、标注者间也有分歧 |
正确用法是分层叠加而非三选一:code 兜确定性、judge 扩开放式、human 校准前两者。本仓 agentEval.ts 的 runTaskEval 同时跑 codeCheck(code)与 judge(LLM-judge),并在有 humanLabels 时算 judge-human κ(human 校准)——三层在一个 harness 里同时存在。
self-preference 循环自评偏差。Hamel《AI Evals FAQ》(2026-01) 的核心警示:当同一模型「既答又判」,会产生 self-preference 循环——模型倾向于偏好与自己风格一致的输出,于是给自己(或同源模型)打的分系统性虚高,判分失真。这不是抽象担忧:只要 judge 模型 = 被测模型,分数就不可信。
其它系统性偏差。除了 self-preference,LLM-judge 还有两个常被忽略的偏差,今天一并记住(Day 17 会正式量化):
- position bias:成对比较时偏好先出现的候选(或后出现的,取决于模型)。缓解法是位置交换——A/B 和 B/A 各判一次取一致结果。
- verbosity bias:偏好更长、更啰嗦的答案,即使简短答案更对。这对 AML 任务尤其危险——一个简洁正确的「block + report」可能输给一段冗长但放行的解释。
为什么这是地基问题。grader 的偏差会无声地传导到所有下游数字:completion% 虚高 → 看起来作品很强 → 实际上只是判官偏爱自己 / 偏爱长答。所以治理 self-preference 有三条正路:(1) 判分模型 ≠ 被测模型(用一个不同家族的 judge,打断同源回路);(2) 凡能用 code grader 判的,退回 code grader(确定性最可信、零成本);(3) 用 judge-human κ 校准(Day 19),κ 未达 0.6 的 judge 不作准绳。三条可叠加。
grader 选择的决策树。实践里怎么选 grader?一条简单决策链:outcome 能写成确定性断言(JSON 合法、数值相等、含某关键词)→ code grader;outcome 开放但有清晰 rubric 且量大 → LLM-judge(异源 + κ 校准);outcome 主观或高风险且量小 → human(金标准,且用来校准前两者)。三者不是互斥而是分层:code 兜底确定性,judge 扩开放式,human 校准全局。
与昨天的边界。Day 1 立的是「outcome 必须明确可判」;今天回答「由谁判、判得准不准」。明天起转向 tokenization(Day 3 BPE 原理),grader 的「准不准」要到 Day 17-20(judge 偏差 + judge-human κ + bootstrap CI)才被量化校准——今天先把循环自评的结构性风险点标出来。
2. 推导 / 手算 / 代码走读
seed 让我审 src/aml/evalBaseline.ts 找「LLM 既答又判」的循环点。诚实校正(必须记录):通读后发现,evalBaseline.ts 不是 LLM 循环自评点——它是一个纯确定性规则基线,根本不调用任何 LLM。真实的「同模型既答又判」风险在 agentEval.ts + run-agent-eval.ts。两边都走读如下。
src/aml/evalBaseline.ts(实际:确定性规则基线,无 LLM):
evalRuleBaseline(ds: AmlDataset):对合成数据集逐案调用assessCase(c).topTypology(来自./typology),与金标c.label比对。- 产出
perTypology(各 typology 的 recall + n)、normalFalsePositiveRate(normal 案被误判为任一 typology 的比例)、confusion(label->predicted混淆矩阵)。 - 全程没有
generateText、没有判官模型——这是 code-based grader 的极致形式:规则引擎自己产出预测,再用确定性逻辑算 recall/FP。所以它天然没有 self-preference 问题(没有 LLM 参与判分)。
真正的循环自评点在 src/agent/eval/agentEval.ts + scripts/run-agent-eval.ts:
-
makeLlmJudge(model)用一个 LLM 当判官;makeModelGenerate(model)用一个 LLM 当被测者。两者接收的是各自传入的LanguageModel——如果调用方传同一个,就是经典的同模型既答又判。 -
scripts/run-agent-eval.ts第 92 行:const judgeModel = judgeId === modelId ? model : buildModel(...)——默认--judge缺省时judgeId = modelId(第 72 行arg('--judge', modelId)),即默认就是同模型自评。这就是循环自评的真实落点。 -
失效假设:judge 模型偏好自己的措辞 / 结构 → 给自己输出虚高分 → completion% 偏乐观,且偏差方向稳定(不是随机噪声,CI 抓不住)。
-
替换方案草图:(a) 跑评测时显式传
--judge <异源模型>(如被测 DeepSeek、判官 Qwen3),打断同源回路;(b) 对tasks.ts里能 code-grade 的任务(format-json-strict、reasoning-numeric-sum等)优先信codeCheck,把 LLM-judge 仅用于开放式 task;(c) 用 Day 17-20 的 judge-human κ 校准,κ 未达 0.6 的 judge 不作准绳。 -
judge 的容错解析:
parseVerdict(text)(agentEval.tsL189)从判官输出里抓第一个 JSON 对象,label非 pass/fail 一律落unknown,scoreclamp 到 [0,1],解析失败也回unknown——判官抖动 / 输出脏 JSON 时不会污染统计,而是计入 unknownRate。这是把判官不可靠性「显式化」而非「静默吞掉」的设计。 -
JUDGE_SYSTEM提示词的去偏意图:makeLlmJudge的系统提示明确写「Grade the OUTCOME, not the steps. Give partial credit. If insufficient … answer unknown」——把 Day 1 的 outcome-over-process + 三态 unknown 直接钉进判官指令,从 prompt 层减偏。但 prompt 层减偏不能替代 κ 校准(提示词写得再好,self-preference 仍可能存在)。
注:
agentEval.ts的JudgeVerdict已含'unknown'标签 +score∈[0,1]部分给分(parseVerdict默认回退unknown),比纯二元判分更新——这是仓库现状里比 seed 描述更进一步的设计。
3. 今日实战
- 读
src/aml/evalBaseline.ts,确认它是确定性规则基线、无 LLM——在笔记里如实纠正 seed 的「循环行号」假设。 - 转到
src/agent/eval/agentEval.ts与scripts/run-agent-eval.ts,定位真实的「同模型既答又判」默认点(run-agent-eval.ts第 72、92 行;agentEval.ts的makeLlmJudge/makeModelGenerate),落 transcript 标注行号。 - 写失效假设(self-preference → 虚高)+ 替换方案草图(异源判官 / 退回 code grader / κ 校准三选/叠加)。
4. 今日实测 / 产出
- 产出:标注真实循环点行号的代码审读 transcript + 替换方案草图。
- 状态:本日产物为代码审读记录;具体行号按实际文件填(已填:
run-agent-eval.tsL71/L92)。 - 诚实标签:seed 把
evalBaseline.ts当循环点,实测它无 LLM;真实循环点在agentEval.ts/run-agent-eval.ts默认同模型自评。不臆造数字。
审读 transcript(核心三行):
| 文件:行 | 代码 | 风险判定 |
|---|---|---|
run-agent-eval.ts:72 | const judgeId = arg('--judge', modelId) | --judge 缺省 → judge = 被测模型(self-preference 入口) |
run-agent-eval.ts:92 | judgeId === modelId ? model : buildModel(...) | 同 id 时复用同一 model 实例,回路闭合 |
agentEval.ts:173 | makeLlmJudge(model) | 判官模型由调用方决定,本身不强制异源 |
替换方案草图(优先级排序):
- 运行时强制
--judge <异源模型>(如被测 DeepSeek-V4 / 判官 Qwen3),打断同源回路——成本最低、立即可做。 - 对
tasks.ts中可 code-grade 的任务(format-json-strict/reasoning-numeric-sum/aml-ctr-threshold)优先信codeCheck,LLM-judge 仅用于开放式任务——降低对判官的依赖面。 - 用 Day 17-20 的 judge-human κ(≥50 人工金标)校准判官,κ<0.6 不作准绳——治本但需标注资产。
5. 常见误区 / 陷阱
- judge = 被测同模型(反模式):默认
--judge缺省即同模型自评,self-preference 让分虚高且偏差稳定,CI 抓不住——必须显式指定异源判官。 - 过度信 LLM-judge:能 code-grade 的判据(JSON 合法、数值相等)就别交给 LLM——确定性最可信、零成本。
- 误把规则基线当 LLM 评测:
evalBaseline.ts是 code grader,不存在 self-preference;别把两类问题混为一谈。 - 无校准就信 judge 分:κ 未达 0.6 的 judge 不可作准绳(Day 19 处理);今天只标结构风险。
4b. 一句话决策:这条任务该用哪种 grader?
拿 tasks.ts 几条任务演练 grader 选型:
reasoning-numeric-sum(「$4,200+$9,800+$15,000=?」)→ code grader:has(/29[,.]?000|29000/)确定性可判,零成本,不该交给 LLM。format-json-strict(「只返回 JSON 对象」)→ code grader:JSON.parse+ 键存在性,二元确定。aml-sar-5w1h(写 SAR 叙事)→ code grader 做 rubric 初筛 + LLM-judge 兜底:6 facet 正则给粗判,开放式表达质量交 judge。aml-dormant-spike(开放式风险评估)→ LLM-judge(异源)+ 人工抽检:正则太宽,需判官判语义。
规律:越是确定性、可程序化的判据,越往 code 推;越是开放式、语义性的判据,越往 judge 推,并用 human 校准 judge。这条选型直觉是今天最该带走的可操作产物。
6. 学习资源(每条带 YYYY-MM)
- Hamel Husain, AI Evals FAQ(2026-01)—— 本日 self-preference 循环自评偏差来源。
- Anthropic, Demystifying evals(2026-01)—— grader 与 unknown/partial credit 设计,Day 1 / Day 17 复用。
- 仓库代码:
src/aml/evalBaseline.ts(确定性规则基线)、src/agent/eval/agentEval.ts(makeLlmJudge/makeModelGenerate/parseVerdict)、scripts/run-agent-eval.ts(默认同模型自评落点)。
SOTA检查 (2026-06 更新)
- 当前主流:Hamel AI Evals FAQ (2026-01) 仍权威;judge 偏差(position/verbosity/self-preference)是活跃议题。
- 是否仍 SOTA:是。异源判官 + κ 校准 + 能 code-grade 就 code-grade,是 2026 评测默认纪律。
- 过时黑名单:避免「judge = 被测同模型」反模式;避免纯二元判分(repo 的
agentEval.ts已有unknown+ partial-credit,比二元更新)。 - 下次复查:跟踪 Hamel FAQ 与 Anthropic evals 文档是否出 2026 下半年偏差缓解新方法(如成对偏好 + 位置交换去 position bias)。
衔接
- 昨天:Day 1 — eval 方法论总览(task/trial/outcome 三层,outcome-over-process)。
- 今天:三种 grader 的权衡 + 同模型既答又判的 self-preference 循环风险点。
- 明天:Day 3 — BPE/tokenization 原理(转入 tokenizer 线,为成本测算打底)。