LLM-as-a-Verifier:当打分从离散分类升级成概率分布,agent 验证发生了什么
posts posts 2026-08-27T11:42:00+08:00Stanford 系(Kwok / Li / Atreya / Liu / Jiang / Finn / Pavone / Stoica / Mirhoseini,2026-07-07 arXiv 2607.05391)的 LLM-as-a-Verifier 框架:把 LLM 打分从「给一个离散标签」改成「读打分 token 的 logprob 期望」,再用 O(Nk) 的概率化 pivot tournament 选最优轨迹——Terminal-Bench 86.5% / SWE-Bench Verified 78.2% / RoboRewardBench 87.4% / MedAgentBench 73.3%,且不需要额外训练。技术笔记LLM-as-a-Judge, Logprob, Bradley-Terry, Probabilistic Pivot Tournament, Test-Time Scaling, Agent Verification, StanfordLLM-as-a-Verifier:当打分从离散分类升级成概率分布,agent 验证发生了什么
llm-as-a-verifier/llm-as-a-verifier 是 2026-04 在 GitHub 上线、2026-07-07 上 arXiv (2607.05391) 的开源框架,2026-08-14 发布 0.2.0 版本,目前 2.9k stars / 230 forks / MIT / Python。它的核心是用同一套「打分 token 的概率分布」在四个完全不同的 agent benchmark 上拿到 SOTA——同一行代码既能给 GPT-5.5 选轨迹,又能给机器人 SAC 和数学 GRPO 当奖励信号。
一句话定位:
Discrete judgment → continuous expectation. 把 LLM 当评审员的范式从「读完给一个分」升级成「读打分 token 的 logprob 并算期望」,再用概率化 pivot tournament 把 O(N²) 的两两比较砍到 O(Nk)。这套打分不需要训练、不需要 reward model,并且在四个跨域 agent benchmark 上 SOTA。
读完代码 + 论文摘要 + 全部 criteria + 三套 scripts + 论文里的四个 benchmark 数字之后,得出四条判断:
- 它不是「又一种 LLM-as-a-Judge」——它把 judge 的输出从离散 label 升级成连续分布,粒度本身就是 scaling 轴。
- 它的算法选算法不是「全打一遍」——概率化 pivot tournament 把 pairwise 次数从 N² 砍到 Nk,靠的是 Bradley-Terry 软胜出 + Hamilton 环消除位置偏置。
- 它的成本不靠模型变小——靠 prefix-cache 把 uncached input 砍到 3.4× 之一,靠 A-T 字母表让打分 token 离散化后能被 logprob 稳定读取。
- 它的副业比主业更值钱——同一个细粒度打分既能做 best-of-N 选最优,也能做 progress tracking 做早停,最后还能喂给 RL 当 dense reward。
下面分十节展开这四条。
一、问题域——agent 选最优轨迹这道题,为什么一直没好解
Pass@1 和 Pass@k 之间隔着一道很具体的工程题:怎么从 k 次采样里挑出最好的那一条。
这道题的难度在三个层面同时上升:
- 任务域变宽了:2024 年的「写代码」已经扩散到「改 PR」(SWE-Bench Verified)、「跑终端命令」(Terminal-Bench)、「调 FHIR 服务器」(MedAgentBench)、「给机器人做奖励 shaping」(RoboRewardBench)。同一个 reward model 想跨这四个域,传统做法要么每个域训一个 RM,要么放弃。
- 轨迹变长了:mini-swe-agent 跑 SWE-Bench 一条轨迹动辄 80k tokens,agent 越强轨迹越长,reward model 看得越贵。
- reward hacking 越来越严重:agent 已经学会「声称自己成功了」——「All tests pass!」「Patch looks correct!」这种 agent narration 几乎一律不可信。Verifier 必须只看实际输出,不看 agent 自评。
过去三年的应对大致是两条路:
- 学一个 reward model:训练一个 RM 在每条轨迹上判分。代价:要训、跨域差、容易被 hack。
- LLM-as-a-Judge:让 LLM 直接读两条轨迹后输出
<score_A>15</score_A>这种离散分数。代价:粒度太粗(1-5、1-10),位置偏置(模型偏好 B 槽),噪声大。
LLM-as-a-Verifier 走第三条路:不改 LLM,只改打分的读法——让 LLM 输出 <score_A>A</score_A> 这种 20 档字母,然后读打分那个位置的 logprob 分布,算期望分。
二、从 Judge 到 Verifier,一张表说清差异
| 维度 | LLM-as-a-Judge | LLM-as-a-Verifier |
|---|---|---|
| 打分单位 | 1 个离散数字 | 20 个 token 的 logprob 分布 |
| 输出形式 | <score_A>15</score_A> | <score_A>A</score_A> |
| 取值范围 | 整数 1-10 | 期望分 ∈ [0, 1](连续) |
| 位置偏置 | 普遍存在,无法消除 | Hamilton 环对消(A/B 槽各出现一次) |
| Scaling 轴 | 仅「多次采样」 | 粒度 × 重复次数 × 准则数 三轴 |
| 校准度 | 粗粒度,边界模糊 | 细粒度,positive / negative 分离度更好 |
| 训练需求 | 无(但要 prompt 工程) | 无(且 token 选择是 first-class design) |
A = 20 分(最优),T = 1 分(最差)。仓库
fine_grained_reward.py的SCALE["valid_tokens"]把 A/B/C…T 都映射成 20/19/18…/1,小写 a/b/c…/t 等价。然后extract_score读打分位置的 top-20 logprob,归一化到 [0, 1]。
这个设计的隐性后果是:g=20 是性能曲线的拐点。论文里反复跑 g ∈ {5, 10, 20} 的消融,g=5 时 positive / negative 几乎重叠;g=20 时分离度大到足以让 pairwise 比较有 80%+ 一致性。再往上 g=50 / g=100 也试过,但边际收益骤降——token 序列需要更多 logprob 槽,推理反而变贵。
三、核心机制 1——细粒度打分:读分布而不是读标签
打分数学化在论文公式 1 里:
$$ R(x, \tau) = \frac{1}{C \cdot K} \sum_{c=1}^{C} \sum_{k=1}^{K} \sum_{g=1}^{G} p_{\theta}(v_g \mid x, c, \tau),\phi(v_g) $$
- $C$ = 准则数(terminal_bench 用了 3 条)
- $K$ = 同一对轨迹上重复评分次数(默认 4,0.2.0 起 terminal_bench_2.1 调到 2)
- $G$ = 粒度(=20)
- $v_g$ = 字母表里的第 g 个 token(A/B/…/T)
- $\phi(v_g)$ = 把字母映射成标量值(A→20, B→19, …, T→1)
- $p_\theta(v_g \mid \dots)$ = 模型在打分位置对字母 $v_g$ 分配的概率
直觉上是这样:模型在 <score_A> 后面给 A 的概率是 0.6、给 B 是 0.3、给 C 是 0.1——这一次打分的期望分 = (0.6×20 + 0.3×19 + 0.1×18) / 20 = 0.975(归一化后)。这里有个关键细节:离散打分只能看"最可能的那个字母",会丢掉 0.6/0.3/0.1 这层置信度信息;logprob 期望把这层信息接住了。同样是判 A 的两条轨迹,一条可能 0.9 坚挺、另一条 0.55 勉强,离散版看起来一样"都是 A",logprob 版却能把它们分开——这就是 Judge 只看见标签、Verifier 看见分布的分界线。
仓库实现细节(fine_grained_reward.py):
GRANULARITY = 20
SCALE = {
"valid_tokens": {
**{chr(65 + i): float(GRANULARITY - i) for i in range(GRANULARITY)},
**{chr(97 + i): float(GRANULARITY - i) for i in range(GRANULARITY)},
},
}
def extract_score(text, tokens, position_logprobs, tag):
"""Expected score over the verifier's token distribution at `tag`,
normalized to [0, 1]. Falls back to parsing the literal text token."""
# 1) 找 tag 位置:最后一个匹配(verdict 在文末)
# 2) 读该位置 +1 的 top-20 logprob
# 3) 对 valid_tokens 取 exp(logprob) 求期望
# 4) 归一化到 [min(SCALE), max(SCALE)] → [0, 1]为什么用字母而不是数字——这是论文里没说但源码里写了的设计:logprob API 一次最多返回 20 个候选 token(OpenAI 把 top_logprobs 上限设为 20)。如果用 1-10 的数字,模型大概率在「15」和「16」之间反复横跳,但 top-20 槽位会被「15」「16」「17」三个相近数字填满,信号被自己稀释。换成 A-T 字母,字母表天然离散化,top-20 槽位能装下整个 20 档粒度,分布更干净。
为什么是 g=20 而不是 g=100——logprob 上限卡死,加上 26 个英文字母里 21 个之后就是 U/V/W…,这些字母在自然语言里几乎不出现在词首,prompt 训练时几乎不会被分配概率,强行扩到 100 个 token 反而会让分布被噪声支配。
四、核心机制 2——概率化 pivot tournament:O(N²) → O(Nk)
N=5 的 best-of-5 全 pairwise 两两比需要 N(N−1)=20 次 directed 比较;N=8 就 56 次;N=20 就 380 次。工程上不可持续。
仓库 pivot_tournament.py 把这砍成两步:
第一步:Hamilton 环打分(消除位置偏置)
def ring_cycle(n, rng):
"""The N directed adjacent pairs of a random Hamiltonian cycle."""
perm = list(range(n))
rng.shuffle(perm)
return [(perm[t], perm[(t + 1) % n]) for t in range(n)]N 个轨迹排成一个随机环,每个轨迹在 A 槽出现一次、在 B 槽出现一次。位置偏置(模型总是偏好 B 槽或偏好 A 槽)在环上求和后被对消。这一步用掉 N 次 verifier 调用。
第二步:选 pivot + 跟所有非 pivot 比
def select_pivots(w, c, k):
"""Top-k candidates by mean preference w_i / c_i (ties broken by index)."""
order = sorted(range(n),
key=lambda i: (-(w[i] / c[i] if c[i] else 0.0), i))
return order[:k]
def pivot_round_pairs(n, pivots):
"""Every non-pivot vs pivot, plus pivot vs pivot."""
non_pivots = [i for i in range(n) if i not in pivot_set]
pairs = [(i, p) for i in non_pivots for p in pivots]
pairs += list(combinations(sorted(pivots), 2))
return pairs环打分后取 top-k 作 pivot set,所有「非 pivot × pivot」对 + pivot 内部 pair 全部跑一遍。总次数 = N + k(N-k) + C(k,2)——N=5、k=2 时只有 5 + 2×3 + 1 = 12 次(对比全 pairwise 的 20 次省 40%);N=20、k=2 时只有 20 + 2×18 + 1 = 57 次(对比 380 次砍 85%)。**
为什么 pivot 数 2 就够——论文给的直觉是:top-2 pivot 把「真正可能赢」的候选覆盖了,剩下的候选只需要跟这两个对比就行。k 越大越准但越贵,k=2 是工程上的甜点。仓库 benchmarks.py 里 pivots: int = 2 是全部 4 个 benchmark 的默认值。
胜负怎么算——仓库用 Bradley-Terry:
def bradley_terry(ra, rb):
"""p(a beats b) under the Bradley-Terry model on rewards in [0, 1]."""
return 1.0 / (1.0 + math.exp(-(ra - rb)))每次 directed 比较返回一个软胜率 p ∈ [0,1],聚合到 $w_i = \sum p(a \succ b)$ 和 $c_i = $ 比较次数。最终赢家是 $\arg\max_i w_i/c_i$。Bradley-Terry 比简单 win/lose 好的地方是它对「微弱优势」和「压倒性优势」区分得开——A 比 B 好 0.001 和好 0.5 在 BT 里给出截然不同的软胜率。
五、核心机制 3——prefix-cache 把成本砍 3.4×
每个 pairwise prompt 都长这样:
Task: {prompt}
Trajectory A: {trajectory_A}
Trajectory B: {trajectory_B}
Rating Scale: ...
Criterion: {criterion}轨迹 A + 轨迹 B + task + scale 几乎是所有 prompt 的共享前缀——只有 criterion 在尾端变。一个 benchmark 有 C 个 criterion + K 次重复 + 每任务最多 N(N-1) 个 pair,prompt 数能到数千。
仓库的优化是双管齐下:
- 把 criterion 放尾部:让所有 prompt 共享最大前缀(仓库
build_prompt里 criterion 是最后一段)。 - warm-up 一发再 fan-out:仓库
score_directed_pairs先挑每个 unique prefix 跑一发完整请求(让后端把 prefix 写进 cache),剩下的同 prefix 请求并发跑。
# Warm-up wave: one job per distinct prompt prefix, then the rest.
seen = set()
warm, rest = [], []
for job in jobs:
prefix = job[7]
if prefix in seen:
rest.append(job)
else:
seen.add(prefix)
warm.append(job)
log(f" {len(jobs)} scoring jobs ({len(cached)} cached); "
f"warming {len(warm)} prefixes")效果在论文里给得很硬:terminal_bench_2.1 上 prefix-cache 命中率从 5.2% 涨到 78.4%,uncached input 砍 3.4×。
仓库把这个数字落到了 USAGE 计数器里,实测而不是估计:
def format_usage(usage, title="Verifier tokens"):
return [
f"{title} ({s['calls']:,} verifier calls)",
f" input {s['input_tokens']:>16,d}",
f" cached input {s['cached_input_tokens']:>16,d} "
f"({100 * s['cache_hit_rate']:.1f}% hit rate)",
f" uncached input {s['uncached_input_tokens']:>16,d}",
f" output {s['output_tokens']:>16,d}",
]跑一次 scripts/run.py terminal_bench 会在 result 表下打出:
Verifier tokens (4,320 verifier calls)
input 272,551,552
cached input 214,712,320 (78.8% hit rate)
uncached input 57,839,232
output 32,441,600
reasoning 26,102,14478.8% 的输入 token 是从 cache 里读出来的,真正计费的是 uncached 那一半。0.2.0 CHANGELOG 里单独把这条提出来——这是 v0.2.0 的主推升级。
六、任务流案例——一条 mini-swe-agent 的轨迹如何被打分
把上面三节串成一次真实调用。场景:SWE-Bench Verified 任务 django__django-11039,mini-swe-agent 跑了 3 条轨迹(traj_1 / traj_2 / traj_3),仓库 select 调用如下:
import llm_verifier
problem = "Fix the failing test in utils.py."
candidates = [traj_1, traj_2, traj_3]
result = llm_verifier.select(
problem=problem,
candidates=candidates,
criteria="swe_bench", # → criteria/swe_bench.md
n_evaluations=4, # K
pivots=2, # k
seed=0,
max_workers=50,
model="gemini-2.5-flash",
)
# 假设选中了 traj_2(最优)
print(result.index) # 1
print(result.scores) # [0.62, 0.97, 0.41] —— traj_2 胜
print(result.n_comparisons) # 6 directed pairs = Hamilton 环 3 + pivot rounds 3
print(result.ranking) # [1, 0, 2]任务流转顺序:
select收口 →pivot_tournament.ring_cycle:seed=0 决定环顺序是 (0,1)、(1,2)、(2,0),3 个 directed pair 进 Phase A。score_directed_pairswarm-up:3 个 unique prefix 各跑一发让后端写 cache;剩下的同 prefix 请求并发。- 读打分位置 logprob:
extract_score对每对 pair × 3 个准则 × 4 次重复 = 12 次 verifier 调用得到 (R_a, R_b),归一化到 [0,1]。 accumulate聚合成 (w, c):每个 pair 走 Bradley-Terry,w[i] 是 i 的总胜率、c[i] 是 i 的总出场次数。select_pivots选 top-2:假设 w/c 排序后是 [1, 0, 2],pivot = {1, 0}。- Phase B 跑 pivot rounds:
pivot_round_pairs(3, {1, 0})返回 [(2,1), (2,0), (1,0)],3 个新 pair 再走 12 次 verifier 调用(cache 命中率从 Phase A 暖出)。 - 聚合 + 选赢家:
accumulate合并两轮,(w_i / c_i) 最高的轨迹 1 胜出。
总成本:72 次 verifier API(Phase A 环 3 对 × 12 + Phase B pivot 3 对 × 12),与全 pairwise directed(3 条轨迹 × 2 个方向 = 6 directed pairs × 12 calls/pair = 72)的 72 calls 相比完全持平——N=3 时 PPT 调用量不减,它换的是「位置偏置对消 + Bradley-Terry 软胜出」;省钱要到 N≥5 才显现。
PPT vs 全 pairwise directed 的成本对照(每对 directed pair × 3 criteria × n_eval=4 = 12 calls/pair;全 pairwise directed = N(N−1) 对):
| N | directed pairs 全 pairwise | PPT pairs(k=2) | 全 pairwise calls | PPT calls | PPT 节省 |
|---|---|---|---|---|---|
| 3 | 6 | 6 | 72 | 72 | 0%(持平) |
| 5 | 20 | 12 | 240 | 144 | 40% |
| 8 | 56 | 21 | 672 | 252 | 62.5% |
| 20 | 380 | 57 | 4560 | 684 | 85% |
| 50 | 2450 | 147 | 29400 | 1764 | 94% |
PPT pairs 公式:N 条环向边 + (N−2)×2 条非 pivot×pivot + C(2,2) 条 pivot 内部 = 3N−3。N=5 就是省钱起点——它比全 pairwise 的 20 对直接省 40%;N≥8 后降到三分之一以下。论文给的 4 个 benchmark 多在 N=3-5(尤其是 best-of-3 的 SWE-Bench),这一区间 PPT 的主要收益确实是验证质量而非调用成本,但一旦 N 上去就两端通吃。
七、benchmark 解读——四个 SOTA 数字背后分别证明什么
论文和 README 列了 4 个 benchmark 的对照数字。每个数字回答的问题都不一样,不能简单地说「都 SOTA」:
| Benchmark | Base Model + Harness | Pass@1 | LLM-as-a-Verifier | Oracle | 测的是什么 |
|---|---|---|---|---|---|
| Terminal-Bench 2.0 | GPT-5.5 (Best-of-5) | 83.1% | 86.5% | 92.1% | 终端命令类任务的 Pass@1 |
| SWE-Bench Verified | Opus 4.5/4.6/Gemini 3 Flash (Best-of-3) | 76.1% | 78.2% | 84.4% | 真实 GitHub issue 修复 |
| MedAgentBench | Claude Opus 4.8 (Best-of-5) | 70.2% | 73.3% | 75.0% | FHIR 医疗数据库查询 |
| RoboRewardBench | (论文披露) | — | 87.4% | — | 机器人轨迹 reward shaping |
| Terminal-Bench 2.1 (self-verification) | deepseek-v4-flash (Best-of-3) | 79.4% | 86.5% ± 1.1% | 92.1% | 同模型既是 agent 又是 verifier |
| Terminal-Bench 2.1 (self-verification) | deepseek-v4-flash (Best-of-5) | 78.7% | 88.0% ± 0.6% | 96.6% | 同模型既是 agent 又是 verifier |
Terminal-Bench 2.0 那一行回答「同 agent 同轨迹池,verifier 选得比随机好多少」——86.5% vs 83.1% Pass@1 = +3.4pp 绝对提升(相对约 4%),这是 verifier 这种轻量级方案的天花板了。Oracle 92.1% 意味着如果 verifier 是完美 oracle,N=5 还能再提 5.6pp。
SWE-Bench Verified 那一行回答「真实软件工程任务能不能也用」——78.2% vs 76.1% = +2.1pp,相对收益约 2.8%。这个数字看起来比 Terminal-Bench 低,但绝对难度高一个数量级——SWE-Bench Verified 的隐藏测试是真的仓库跑测试,Terminal-Bench 是看 stdout。Oracle 84.4% 说明即使 oracle 也只能到 84%,verifier 已经吃到 78.2% 距离上限只剩 6pp。
MedAgentBench 那一行回答「医疗这种高 stakes 域能不能用」——73.3% vs 70.2% = +3.1pp,Oracle 75.0% 距离上限只剩 1.7pp。这意味着 verifier 几乎吃到了 oracle 的能力——医疗 query 这种 FHIR JSON 模板化任务上 verifiers 比 verifier-judged 这种主观打分准得多。
RoboRewardBench 87.4% 在论文里是机器人 RL reward shaping 的专用 benchmark,证明同一个打分信号不仅能做 best-of-N 选优,还能直接喂 SAC/GRPO 做 dense reward——这是论文第 5 节的核心贡献。
self-verification 那两行是 0.2.0 新加的(terminal_bench_2.1),最有反直觉价值:让 deepseek-v4-flash 既是 agent 又是 verifier——同模型审自己,结果 86.5% / 88.0% 仍然显著高于 Pass@1 的 79.4% / 78.7%。这意味着 verifier 的能力不依赖于「比 agent 更强」——同模型就能当好 verifier。
能推出什么:
- 跨 4 个域(终端、代码、医疗、机器人)都成立 = 不依赖域内 RMs;
- Pass@1 → LLM-as-a-Verifier 的 gap ≈ Pass@1 → Oracle 的 50-70% = 已经吃掉了大半 oracle 红利;
- self-verification 跑通 = verifier 不需要比 agent 强 = 部署成本砍掉一半(不需要额外维护一个更强的模型)。
不能推出什么:
- 4 个 benchmark 不等于「所有 agent benchmark」——OCR/视觉问答/长文档检索这些任务没测过;
- +2-3pp 的绝对收益在大规模生产里不一定值回 verifier 调用成本(见 §10);
- 87.4% 是 verified reward 的精度,不等于策略最终回报——RL 用 verifier 当 reward 的实际增益论文里给了数字,但泛化性仍是开放问题。
八、副业——细粒度打分的两个高价值外延
打分一旦有了连续期望,它能做的远不止 best-of-N 选优。仓库另外暴露了两个一等公民 API:
8.1 Progress tracking(在线早停)
ProgressTracker 让 verifier 边看 agent 跑边打分:
tracker = llm_verifier.ProgressTracker(problem, n_evaluations=4)
score = tracker.update('Read the problem statement') # 0.00002
score = tracker.update('Wrote def rev(s): return s') # 0.00013
score = tracker.update('Changed to def rev(s): return s[::-1]') # 0.73938
score = tracker.update('Tested: rev("abc") returned "cba"') # 0.98604
if score < 0.05: # after any step: abandon a hopeless rollout early
break关键工程细节:tracker 每次 update 只喂 verifier 「前缀」,绝不喂未来的步骤。如果 verifier 看到未来,它会按未来结果给当前步打分(早步骤看着就高),曲线失去信息。仓库 ProgressTracker.update 实现里严格只传 self._step_texts[:k]。
应用场景:
- 早停没救的 rollout:agent 跑了 10 步还在 A 区间([0, 0.05]),就别等它跑完了;
- 决定何时 resample:第 8 步还停在 J([0.5, 0.6]),就重新生成一条;
- 拟合 RL 早期 reward:agent 自己也能看到自己的进度曲线。
8.2 多模态扩展
所有入口(select / compare / track)都接受 images:
result = llm_verifier.select(problem, candidates, criteria=criteria,
images=["before.png", "after.png"])
tracker = llm_verifier.ProgressTracker(problem)
score = tracker.update(step, images="camera_frame.png") # per-step framePer-step frame 会被纳入 trajectory history,所以 verifier 永远看到完整的视觉历史——这正好对应机器人/具身场景里「相机每帧拍一张」的需求。仓库 _score_tags_by_prefill 这一段用 add_generation_prompt=False, continue_final_message=True 强制模型在 prefilled tag 后只输出打分字母,让打分位置稳定。
注意:多模态需要 verifier 模型本身就是多模态——仓库 README 推荐 Gemini 2.5 Flash 或本地 vLLM Qwen/Qwen3.5-9B(带 vision)。
九、社区与生态——Stanford 系背书的工程化深度
论文 9 位作者里,4 位是 Stanford 终身教授:
- Chelsea Finn(CRFM 主任,meta-learning / robotics)
- Marco Pavone(自动驾驶 + 机器人,NVIDIA 兼职)
- Ion Stoica(Databricks 创始人 + Berkeley RISELab)
- Azalia Mirhoseini(Google Brain / Apple 出身,AI for systems)
这个阵容跟论文 2026-04 在 GitHub 上线、2026-07-07 上 arXiv、2026-08-14 发 0.2.0 的速度对得上——背后有完整 RL 团队 + robotics 团队 + systems 团队在并行跑实验。仓库 CHANGELOG 里 0.2.0 同时上线的还有「deepseek-v4-flash verifier backend」和「self-verification benchmark」,深度依赖 deepseek 的 reasoning API + logprob——这不是一个学生作业能 hold 住的工程节奏。
生态布局:
- TurboAgent(
llm-as-a-verifier/TurboAgent)——同一个组织下的姊妹项目,把 LLM-as-a-Verifier 接到 Claude Code 上当 LLM API 代理,用户无感地拿到 best-of-N:pip install git+...→turbo-agent起在 :8888 →ANTHROPIC_BASE_URL=http://localhost:8888 claude。 python -m llm_verifier <file.md>—— 看 criteria 文件会被 verifier 看到成什么样,无 API key。- Slack 社区 + Twitter/X (
@jackyk02) —— 项目方主动运营。 - 加新 benchmark 协议(
add_new_benchmark.md)——写一份 criteria.md + 一个adapt_run.py就能把 verifier 接到新任务上,Claude Code / Codex 都能直接照着跑。
十、采用顺序与边界——谁该先上,谁可以等等
10.1 该先上
做 best-of-N 选优但还没上 RM 的团队:
- 已经在跑 Pass@1 → 想升级到 Pass@k 但懒得训 RM;
- 跨多个 agent benchmark(终端 / 代码 / 医疗 / 机器人),不想每个域训一个 RM;
- 接受「+2-3pp 绝对收益」+「verifier 调用成本」这笔账算得过来。
已经上 LLM-as-a-Judge 但对准确度不满意的团队:
- 当前 LLM-as-a-Judge 在 1-5 离散分上摇摆大;
- 需要在线 progress tracking 做早停;
- 想让同一个打分信号同时进 RL reward shaping。
做 RL(GRPO / SAC)需要 dense reward 的团队:
- 已经有 environment + agent,但 reward sparse;
- RoboRewardBench 87.4% 这个数字值得严肃对待——它是真在 RL 训练里跑出来的,不是离线打分。
10.2 可以等等
任务域没有「实际输出可读」的:
- 比如纯开放式创意写作——verifier 没法只看实际输出就判断好坏,必须靠 LLM subjective judgment,g=20 letter 的精细度在这里没优势;
- 比如长文档检索——verifier 看到「retrieved 4 docs」这种输出判断不了 relevance,必须看下游 LLM 用了什么。
生产环境对 verifier 调用延迟敏感:
- 一次
select(problem, 5 trajs, 3 criteria, n_eval=4)要 144 次 verifier 调用(12 directed pairs × 12 calls/pair),并发 50 也得等几秒——见 §6 的成本表; - progress tracking 是 1 次/step,N 步 agent 跑完得多花 N 次 verifier;
- 如果产品场景是「用户输入 → 30 秒内返回」,verifier 这块成本可能接不住。
已经有成熟 RM 的团队:
- RM 已经训到 Pareto frontier;
- RM 的训练数据足够覆盖你的 agent 输出分布;
- LLM-as-a-Verifier 对你的定位属于「能力替代」而非「能力提升」——RM 已经训到 Pareto frontier 的情况下,迁移到 verifier 拿不到什么新东西,除非 RM 的训练成本实在高得离谱。
10.3 上手顺序
- 先跑一次官方 benchmark:
pip install llm-verifier→python scripts/run.py terminal_bench确认它在你的数据上能跑通; - 再写自己的 criteria:照
criteria/TEMPLATE.md写 2-3 条窄准则(不要写「整体正确性」这种宽口径); - 接
select做 best-of-N:用cache="cache/your_task.json"让重复 run 不花钱; - 接
ProgressTracker做早停:在已有 agent loop 里加 5 行代码就能拿到 step-level progress; - 最后接 TurboAgent:如果你用 Claude Code 或类似 CLI agent,TurboAgent 是 0 成本升级路径——只是换一个
ANTHROPIC_BASE_URL。
10.4 必看边界
- verifier 必须能返回 token-level logprob:仓库 README 写明「Vertex AI only — logprob extraction needs the Vertex API」, plain Gemini API 不返回 logprob——这点 vLLM 自托管能解,本地 OSS 替代方案是
OPENAI_BASE_URL=http://localhost:8000/v1+ vLLM serve; - 必须用 letter scale 而不是 1-20 数字:论文消融里 1-20 数字版的 g=5 性能接近 baseline,字母版 g=20 性能显著高——这是 token 分布的离散化设计,不是 prompt engineering;
- prefix-cache 是默认假设:如果你的后端不缓存 prefix(比如裸 HTTP API),warm-up 优化无效,回到全 uncached 成本。
十一、回到系统层——这个工作真正贡献的是什么
它真正的贡献不在 +3pp Pass@1 这种数字上——这种收益早就被各种 RM 论文刷过。它的不可替代处在于把验证这件事从「给一个离散标签」升级成「读打分 token 的概率分布」——同一个 LLM,只换打分读取方式。
这套机制一次性解锁了五件事:
- 三轴 scaling(粒度 × 重复 × 准则)——以前 LLM-as-Judge 时代只有「多次采样」一轴;
- prefix-cache 友好——读分布让 prompt 高度结构化、缓存粒度细到准则级,5.2% → 78.4% 命中率就是这条线上的红利;
- 跨域通用——同一套打分机制在终端/代码/医疗/机器人四个完全不同的域都跑通,不需要每个域训 RM;
- dense reward——同一个分数既能做 best-of-N 选优,又能做 RL reward shaping(SAC/GRPO),reward 形状不需要为不同任务重训;
- self-verification——同模型当 verifier 的可能性被打开(terminal_bench_2.1 上 deepseek-v4-flash 既是 agent 又是 verifier 还拿到 86.5%),部署时不需要额外维护一个更强的模型。
更底层的视角:token-level logprob 这个 OpenAI 2023 年就暴露在 API 里的接口,在 2026 年终于有人找到了它的「distribution-aware verification」这个对应用例——之前大家要么忽略(直接要离散 label)、要么滥用(拿它做 jailbreak 检测)。LLM-as-a-Verifier 是第一个把这个原语用到正事上的工程化框架。
如果你正在做 agent / RL / LLM 系统,这个仓库值得花一个下午把它吃透——核心 943 行的 fine_grained_reward.py + 92 行的 pivot_tournament.py 已经全部,剩下的脚本和 criteria 都是声明式的。这个框架的工程价值在于**「LLM 输出 = 概率分布」这个事实一旦被严肃对待,整条 verification pipeline 就从启发式工程变成了有数学支撑的工程**。
参考资料
- 论文:Kwok et al. “LLM-as-a-Verifier: A General-Purpose Verification Framework” arXiv 2607.05391v2 (2026-07-07) https://arxiv.org/abs/2607.05391
- 代码:https://github.com/llm-as-a-verifier/llm-as-a-verifier (MIT, 2.9k stars, 0.2.0 / 2026-08-14)
- 文档:https://llm-as-a-verifier.com/docs/
- 姊妹项目 TurboAgent(Claude Code 插件):https://github.com/llm-as-a-verifier/TurboAgent
- 作者主页:Jacky Kwok, Shulu Li, Pranav Atreya, Yuejiang Liu, Yixing Jiang, Chelsea Finn, Marco Pavone, Ion Stoica, Azalia Mirhoseini(Stanford / Berkeley 系)
- 相关工作:Pan et al. “LLM-as-a-Judge: A Survey” 2024;Self-Refine;STaR;Process Reward Models;Bradley-Terry 模型
- 可对比的工程实现:Anthropic Claude API(无 logprob)、vLLM(开源、可 logprob)、SGLang(开源、可 logprob)、DeepSeek API(
thinking+ logprob)
参与讨论
使用 GitHub 登录。欢迎补充事实、异议与实践。
讨论暂时无法加载。