Any modality, Many Applications, One Unified Verification Framework
| Documentation | Website | Paper | Claude Code Plugin | Twitter/X | Slack |
๐ฅ LLM-as-a-Verifier achieves SOTA performance across agentic benchmarks, including Terminal-Bench, SWE-Bench Verified, MedAgentBench, RoboRewardBench and more. We invite the community to contribute more use cases!
Installation
pip install llm-verifier
To install the latest from a clone:
pip install -e .
What's new in 0.2.0 (full notes in CHANGELOG.md):
- Prefix-cache optimization: ~3.4ร fewer uncached input tokens on trajectory-heavy benchmarks
- Terminal-Bench 2.1 self-verification benchmark
deepseek-v4-flashverifier backend- Token accounting (
llm_verifier.token_usage())
About
LLM-as-a-Verifier is a general-purpose framework that provides fine-grained feedback for any agent. The key idea is simple: 1) use fine-grained scoring granularity, 2) take the expectation over the full logprob distribution of LLM score tokens, and 3) scale repeated evaluation and criteria decomposition. The resulting fine-grained feedback can be used for test-time scaling, progress tracking, and reinforcement learning.
Quickstart
Simple Best-of-N Selection
Run a first end-to-end selection (requires DEEPSEEK_API_KEY or VERTEX_API_KEY in .env, or an OpenAI-compatible server that returns
logprobs โ e.g. vllm serve Qwen/Qwen3.5-9B with
OPENAI_BASE_URL=http://localhost:8000/v1):
import llm_verifier
problem = "Write a function that reverses a string."
candidates = [
"def rev(s): return s[::-1]", "def rev(s): return s", "def rev(s): return ''.join(sorted(s))",
]
result = llm_verifier.select(
problem=problem,
candidates=candidates,
criteria={"Correctness": "Does the code actually reverse the string?"},
)
print(result.index) # index of the best candidate: 0
print(result.scores) # candidate scores: [0.73104, 0.38446, 0.38449]
Score a pair of candidates directly
select is built on a pairwise reward model. For the raw fine-grained rewards
of a single comparison, call compare:
reward_a, reward_b = llm_verifier.compare(
problem, candidates[0], candidates[1],
criteria={"Overall": "Does the code solve the problem?"},
)
print(reward_a, reward_b) # fine-grained rewards in [0, 1]: 0.99994 0
Fine-grained Progress Tracking
The same fine-grained reward can also score an agent's progress after each
step with track:
steps = [
'Read the problem statement',
'Wrote def rev(s): return s ',
'Tested: rev("abc") returned "abc"',
'Changed to def rev(s): return s[::-1]',
'Tested: rev("abc") returned "cba"',
]
result = llm_verifier.track(problem=problem, steps=steps,
checkpoint_steps=[1, 2, 3, 4, 5], n_evaluations=4)
print(result.scores) # progress after each step: [0.00106, 0.02417, 0.03143, 0.62004, 0.99978]
Self-Verification (Terminal Bench 2.1)
Can a model verify its own rollouts? On Terminal-Bench 2.1 we generate 5
mini-swe-agent trajectories per task with deepseek-v4-flash and use the
same model as the verifier. Selection lands well above Pass@1 even though
the verifier is judging its own model's work:
| Config | Pass@1 | LLM-as-a-Verifier | Oracle |
|---|---|---|---|
| Best-of-3 | 79.4% | 85.4% | 92.1% |
| Best-of-5 | 78.7% | 88.8% | 96.6% |
The trajectories ship in data/terminal_bench_2.1_trajs/; scoring only needs
DEEPSEEK_API_KEY in .env. Each configuration has its own reproduction
script:
python scripts/run_bo3.py # best-of-3
python scripts/run_bo5.py # best-of-5
Test-Time Scaling for Agentic Benchmarks
Each benchmark ships with its agent trajectories (data/). We use Gemini 2.5
Flash (gemini-2.5-flash) as the verifier for all benchmarks below. Expected
results:
| Benchmark | Base Model | Harness | Pass@1 | LLM-as-a-Verifier | Oracle |
|---|---|---|---|---|---|
| Terminal-Bench V2 | GPT-5.5 (Best-of-5) | Capy | 83.1% | 86.5% | 92.1% |
| SWE-Bench Verified | Opus 4.5 / Opus 4.6 / Gemini 3 Flash (Best-of-3) | mini-swe-agent | 76.1% | 78.2% | 84.4% |
| MedAgentBench | Claude Opus 4.8 (Best-of-5) | AgentBench | 70.2% | 73.3% | 75.0% |
Reproduce Results
Run a benchmark by name (python scripts/run.py with no argument lists them):
python scripts/run.py terminal_bench
python scripts/run.py swe_bench
python scripts/run.py medagentbench
The tournament defaults can be overridden on the command line:
python scripts/run.py swe_bench --pivots 2 --n-evaluations 8 --seed 0 --max-workers 50
Benchmarks are defined in llm_verifier/benchmarks.py โ add or tweak one there.
Select Best of N agent trajectories
Given a task and a pool of agent trajectories, pick the best one in a few lines of code.
import llm_verifier
problem = "Fix the failing test in utils.py."
candidates = [traj_1, traj_2, traj_3, traj_4, traj_5]
result = llm_verifier.select(
problem=problem,
candidates=candidates,
criteria={"Root cause": "Did the agent fix the real cause?",
"Verification": "Did the agent confirm the fix?"},
model="gemini-2.5-flash", # verifier model
n_evaluations=4, # repeated evaluations per criterion
pivots=2, # pivots < N; reduced verification cost
)
print("Best candidate:", result.index)
print("Ranking:", result.ranking)
Under the hood, select runs the
Probabilistic Pivot Tournament to rank all
N trajectories using O(Nk) pairwise verifications instead of a full
O(Nยฒ) round-robin. pivots trades cost for accuracy: more pivots = more
comparisons = higher accuracy.
Adapt LLM-as-a-Verifier for your own use case
Use the verifier for your own task in three steps โ Claude Code does the rest (generates the criteria, writes a runner, and selects the best-of-N for you):
- Add your data. Copy your agent trajectories into
data/task_name_trajs/. - Update naming. Replace every
task_nameinadd_new_benchmark.mdwith the name of your task. - Spin up Claude Code in this repo (or Codex, or whatever you like โ with
permissions disabled) and paste the contents of
add_new_benchmark.mdto let it run.
Progress Tracking for Coding Agents
The same fine-grained reward can score a trajectory at every step (see
track in the Quickstart). Below, we track two Terminus-2 runs of the Terminal-Bench task pytorch-model-cli. The successful trajectory exhibits consistently increasing verifier scores, whereas the failed trajectory is characterized by erroneous behaviors, resulting in lower scores throughout the execution. Reproduce it with:
python scripts/terminal_bench_progress.py # scores both runs then plots
Online progress tracking
track scores a finished trajectory. To monitor an agent while it
runs, use ProgressTracker: feed it each step as it happens and get a live
progress score back โ e.g. to stop a hopeless rollout early or decide when to
resample. Since the verifier only ever sees the steps so far, it cannot peek
at the future.
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
...
Replay the two Terminal-Bench trajectories step-by-step through
ProgressTracker โ printing a live score bar after every step, as an agent
harness would see it:
python scripts/terminal_bench_progress.py --online
Multi-Modal Support
With a multimodal verifier model (e.g. Gemini 2.5 Flash or
vllm serve Qwen/Qwen3.5-9B), every
entry point accepts images โ a single image (images="frame.png") or a
list of images, each a local file path, an http(s) URL, or raw bytes:
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 frame
Per-step frames stay part of the trajectory for all later updates, so the verifier always sees the full visual history โ e.g. camera frames while tracking a robot rollout. See the multimodal documentation for accepted input forms, backend notes, and verified examples.
Claude Code Plugin
TurboAgent brings LLM-as-a-Verifier to Claude Code as a drop-in LLM API proxy. It sits between your client and the model provider, generating multiple candidate responses in parallel and selecting the best one with a Probabilistic Pivot Tournament.
pip install git+https://github.com/llm-as-a-verifier/TurboAgent
Point Claude Code at the proxy and run as usual:
turbo-agent # starts on port 8888
ANTHROPIC_BASE_URL=http://localhost:8888 claude
It ships a built-in visualizer at
http://localhost:8888/visualizer that shows the pipeline DAG, progress scores, candidate
responses, and the final selection. See the
TurboAgent repository for
configuration and setup details.
Directory Structure
.
โโโ scripts/ # command-line entry points
โ โโโ run.py # registry-driven benchmark launcher
โ โโโ run_bo3.py # reproduce the best-of-3 self-verification run
โ โโโ run_bo5.py # reproduce the best-of-5 self-verification run
โ โโโ terminal_bench_progress.py # re-score + plot the progress-tracking example
โโโ criteria/ # verifier criteria + ground-truth notes
โ โโโ TEMPLATE.md # copy this to write your own
โ โโโ terminal_bench.md
โ โโโ swe_bench.md
โ โโโ medagentbench.md
โโโ llm_verifier/ # the reusable framework (import llm_verifier)
โ โโโ __init__.py # llm_verifier.select(...) / .compare(...)
โ โโโ __main__.py # python -m llm_verifier <file.md>: preview criteria
โ โโโ benchmarks.py # BENCHMARKS registry (one Benchmark / launch)
โ โโโ fine_grained_reward.py # R(x,ฯ): logprob scoring + score cache
โ โโโ progress.py # llm_verifier.track(...): per-step progress curve
โ โโโ pivot_tournament.py # PPT: O(Nk) selection (Bradley-Terry)
โ โโโ prompts.py # load criteria/*.md + normalize criteria args
โ โโโ loaders.py # per-benchmark trajectory loaders
โโโ data/ # agent trajectories per benchmark
Runs write their verifier score caches to cache/ and result tables to
results/; both are created on demand and git-ignored.
How it works
Fine-grained Reward Estimation
Rather than reducing each distribution into a single discrete score (as in LLM-as-a-Judge), LLM-as-a-Verifier approximates the reward of a trajectory $\tau$ on task $x$ as:
$$ R(x, \tau) = \frac{1}{CK} \sum_{c=1}^{C} \sum_{k=1}^{K} \sum_{g=1}^{G} p_{\theta}(v_g \mid x, c, \tau),\phi(v_g) $$
- $C$ = number of evaluation criteria
- $K$ = number of repeated verifications
- $G$ = number of score tokens (granularity level)
- $p_{\theta}(v_g \mid x, c, \tau)$ = probability assigned by model $\theta$ to score token $v_g$
- $\phi(v_g)$ = maps each scoring token to a scalar value
- $V_{\text{score}} = {v_1, \ldots, v_G}$ = ordered set of discrete score tokens
This lives in llm_verifier/fine_grained_reward.py.
Probabilistic Pivot Tournament
To pick the best of N candidate trajectories, a round-robin tournament scores
all $\binom{N}{2}$ pairs โ O(Nยฒ). Probabilistic Pivot Tournament (PPT) is a
cost efficient ranking algorithm in which every candidate is compared only
against a small set of pivots, reducing the budget from $\mathcal{O}(N^2)$ to
$\mathcal{O}(Nk)$.
- Candidates: the pool ${\tau_1,\dots,\tau_N}$ to be ranked.
- Ring pass: a random Hamiltonian cycle scores the $N$ adjacent pairs so every candidate appears once in the "A" slot and once in "B", canceling the model's positional bias.
- Pivot selection: candidates are ranked by their ring-pass scores $w_{(i)}$, and the top-$k$ candidates form the pivot set $\mathcal{P}$.
- Pivot tournament: every non-pivotโvsโpivot and pivotโvsโpivot pair is scored via the pairwise preference $p(a \succ b) = \sigma(R_a - R_b)$, concentrating the budget on uncertain top candidates and cutting cost from $\mathcal{O}(N^2)$ to $\mathcal{O}(Nk)$. Repeated evaluations of a pair alternate the A/B prompt slots, so positional bias cancels here as well.
- Selection: comparisons are aggregated into win mass $w_i$ and count $c_i$, and the candidate with the highest normalized $w_i/c_i$ is returned.
This lives in llm_verifier/pivot_tournament.py.
Prompt Templates
Pairwise Comparison Prompt
You are an expert [domain] reviewer. You will see a task description and two
trajectories.
Evaluation Criteria: [domain specific criteria]
Task: {task prompt}
Trajectory A: {A}
Trajectory B: {B}
Carefully analyze each trajectory, then provide your final scores:
<score_A> INTEGER_1_TO_20 </score_A>
<score_B> INTEGER_1_TO_20 </score_B>
Rating Rules: Rate correctness on a 1-20 scale based on evaluation criteria
(1 = incorrect, 10 = borderline, 20 = correct)
Progress Tracking Prompt
You are an evaluator of [domain] agent attempts. Trust observed output โ NOT the agent's narration.
Task: {task prompt}
Agent trajectory ({N} steps): {trajectory}
You will score the trajectory at {N} checkpoints. Given everything the agent has done up to and including this step, would the agent's CURRENT state already complete the task?
Score each checkpoint INDEPENDENTLY, then output exactly N lines:
<c1> INTEGER_1_TO_20 </c1>
...
<cN> INTEGER_1_TO_20 </cN>
Rating Rules: Rate completion on a 1-20 scale (1 = certainly not complete,
10 = uncertain, 20 = verified complete)
Note: we use a letter-based scale (A-T) instead of digits in the actual implementation to enable logprob extraction for granularity scaling.
Prefix-Cache Optimization
Each verification prompt carries two full trajectories (~80k tokens on
Terminal-Bench 2.1) and is re-scored per criterion and repeat, so on a backend
that caches prompt prefixes almost all of that input can be reused. Two things
make it happen: the prompt keeps the criterion at the tail, so everything
before it (task, both trajectories, rating scale) is a shared prefix, and
scoring warms one request per distinct prefix to completion before fanning out
the rest. Together these take the cache hit rate from 5.2% to 78.4% on
terminal_bench_2.1, cutting uncached input tokens by ~3.4ร.
Token Accounting
Every verifier call records what it was billed for, so the cache hit rate above
is measured rather than assumed. scripts/run.py prints the totals under the
result table (and writes them to results/<benchmark>.txt):
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,144
Only calls this run actually made are counted โ comparisons served from the
score cache add nothing. Reasoning tokens are a subset of output tokens, and
cached input is a subset of input. The counter is process-wide and
thread-safe, so library users get the same numbers out of select /
compare / track:
import llm_verifier
llm_verifier.USAGE.reset()
result = llm_verifier.select(problem, trajectories, criteria="terminal_bench")
print(llm_verifier.token_usage())
# {'calls': 24, 'input_tokens': 1512480, 'cached_input_tokens': 1190208,
# 'uncached_input_tokens': 322272, 'output_tokens': 180224,
# 'reasoning_tokens': 145408, 'cache_hit_rate': 0.787}
llm_verifier.USAGE is a TokenUsage: .snapshot() for the dict above,
.reset() to zero it, and format_usage(...) for the report block. Counts
come from the backend's own usage block; a backend that reports no usage
simply contributes zeros.
Citation
If you find this work useful, please cite:
@misc{kwok2026llmasaverifiergeneralpurposeverificationframework,
title={LLM-as-a-Verifier: A General-Purpose Verification Framework},
author={Jacky Kwok and Shulu Li and Pranav Atreya and Yuejiang Liu and Yixing Jiang and Chelsea Finn and Marco Pavone and Ion Stoica and Azalia Mirhoseini},
year={2026},
eprint={2607.05391},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2607.05391},
}
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_verifier-0.2.0.tar.gz.
File metadata
- Download URL: llm_verifier-0.2.0.tar.gz
- Upload date:
- Size: 44.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5eb85902344ca3680c9b045f32e07edb5cac668e4bc42ff2a96b80778d628aa
|
|
| MD5 |
3f1c86987f1920b40ae59f0a85a6119a
|
|
| BLAKE2b-256 |
27b76f91acc8898dc8e00e5a0826feb9437c6ac80ea335f7b6558ba76e2b59b6
|
File details
Details for the file llm_verifier-0.2.0-py3-none-any.whl.
File metadata
- Download URL: llm_verifier-0.2.0-py3-none-any.whl
- Upload date:
- Size: 40.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d1678c93d19874acd15999026117371c73367e3faac1712affe6dd7f38303af
|
|
| MD5 |
678b7bc16d89ab989ea45cbd14b1b1e8
|
|
| BLAKE2b-256 |
193ea6ee4a258e048811c3061730b85bfe0e5cebaf5d9747bd1f7f65a5604f45
|