judgepanel
Estimate how accurate your LLM judges are — without gold labels — and aggregate their verdicts better than majority vote.
When several imperfect judges label the same items, the disagreement pattern itself reveals who is reliable. judgepanel fits the classic Dawid–Skene model (1979) to a judge panel and recovers, jointly:
- each judge's confusion matrix (sensitivity/specificity in the binary case),
- the class prevalence,
- a posterior over every item's true label, weighted by estimated judge quality instead of one-judge-one-vote.
Around the core: majority-vote baselines with explicit tie handling, chance-corrected agreement (Cohen's and Fleiss' kappa), bootstrap confidence intervals, and simulators for planning panels and studying failure modes. Pure NumPy; no model calls; every fit deterministic.
Install
pip install git+https://github.com/mohammadi-hadi/judgepanel
Sixty seconds
Your data is one JSON object per (item, judge) verdict:
{"item": "case-0017", "judge": "gpt-strict", "label": "faulty"}
judgepanel fit examples/data/demo_panel.jsonl --positive faulty --bootstrap 200
panel: 300 items, 5 judges, 2 classes (clean, faulty)
converged after 55 iterations
estimated prevalence: clean=0.577, faulty=0.423
judge sens spec youden
gpt-strict 0.906 0.931 0.836
gpt-lenient 0.715 0.999 0.714
small-model 0.692 0.710 0.402
rules 0.562 0.996 0.559
flag-happy 0.953 0.394 0.347
The demo panel is synthetic with known truth (examples/data/demo_gold.jsonl):
the true parameters are (0.92, 0.94), (0.70, 0.97), (0.66, 0.72), (0.55, 0.99)
and (0.97, 0.35) — every estimate above lands within 0.04 without ever seeing a
gold label, and the flag-everything judge is exposed immediately.
Same thing in Python:
from judgepanel import fit_dawid_skene, read_panel_jsonl
panel = read_panel_jsonl("panel.jsonl")
result = fit_dawid_skene(panel)
result.sensitivity("faulty") # {"gpt-strict": 0.906, ...}
result.labels() # posterior-aggregated label per item
Keys are configurable, so existing verdict files work directly
(--item-key trajectory_id --judge-key judge_id --label-key faulty; boolean
labels become "true"/"false").
Validation on a real judge panel
The trajectory-judge
study had five LLM judges label the same 400 agent trajectories as faulty or
clean, with gold labels known by construction. Fitting judgepanel on the
verdicts alone (gold hidden), then unblinding
(python examples/case_study.py --raw .../results/raw):
| judge | est sens | true sens | est spec | true spec |
|---|---|---|---|---|
| programmatic rules | 0.771 | 0.667 | 0.993 | 1.000 |
| outcome-only (14B) | 0.639 | 0.613 | 0.633 | 0.670 |
| step-rubric (14B) | 0.995 | 0.857 | 1.000 | 1.000 |
| step-rubric (8B) | 0.988 | 0.990 | 0.000 | 0.000 |
| self-consistency k=3 (14B) | 0.980 | 0.843 | 0.994 | 0.990 |
Three honest findings:
- The spammer is exposed exactly. The 8B judge flags nearly everything; its estimated specificity is 0.000 — matching the truth — with no gold label in sight. The judge quality ranking is right across the board, and the production-default outcome judge is estimated within 0.04.
- Correlated judges inflate their own estimates. Self-consistency is the step judge sampled three times, and the two agree at kappa 0.96. Dawid–Skene assumes conditionally independent judges, so this pair drags the latent truth toward its shared verdicts: both get overestimated by up to +0.14, and estimated prevalence drops to 0.645 against a true 0.750. The library prints the kappa matrix precisely so you check this before believing the fit.
- No aggregation recovers what every judge misses. Faults that leave the final answer correct are invisible to the whole panel, so posterior aggregation (0.895 accuracy) cannot beat majority (0.897) here. Weighted aggregation wins when judge quality is uneven, not when errors are shared.
When not to trust it
- Fewer than 3 judges: weakly identified; the fit warns.
- Highly correlated judges (same base model, ensembles of one judge,
shared blind spots): estimates bias toward the correlated cluster — check
judgepanel agreefirst, and prefer diverse judge families. - A panel that is wrong on average: if most judges are worse than chance, the labels-and-mirror solution is indistinguishable without gold.
- Tiny or extremely imbalanced panels: run
judgepanel simulatewith your expected operating points to see the estimation error you should expect before you rely on the numbers.
API
| call | what it does |
|---|---|
read_panel_jsonl(path, ...) |
long-format JSONL → Panel |
fit_dawid_skene(panel) |
EM fit → confusions, prevalence, posteriors |
bootstrap_judges(panel, n_boot=200) |
percentile CIs for judge parameters |
majority_vote(panel) |
baseline aggregation, ties made explicit |
cohen_kappa, fleiss_kappa, pairwise_cohen_kappa |
agreement diagnostics |
simulate_panel, simulate_confusion_panel |
synthetic panels with known truth |
CLI: judgepanel fit, judgepanel agree, judgepanel simulate,
judgepanel version (each supports --json).
Development
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest # 52 tests: hand-computed values, seeded recovery, CLI
.venv/bin/ruff check src tests && .venv/bin/mypy src
The suite checks statistical behaviour, not just plumbing: kappa against hand-computed tables, EM recovery on seeded synthetic panels, log-likelihood monotonicity, bootstrap coverage, and determinism.
Citation
If you use judgepanel, please cite it (see CITATION.cff).
MIT license.
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 judgepanel-0.1.0.tar.gz.
File metadata
- Download URL: judgepanel-0.1.0.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6317068733ee2266690a8965b83e27202c22d380c5a34e974629c0845426fa83
|
|
| MD5 |
69cadb44e07b825d048cb284730fb51b
|
|
| BLAKE2b-256 |
e1637035a7fec4ba6883c4ac2a0e54c1cab1b464051afe6f7bd3a01bede452ba
|
File details
Details for the file judgepanel-0.1.0-py3-none-any.whl.
File metadata
- Download URL: judgepanel-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2c3a574a9e4dd273ff7761faa7e5afd0f6287961b6b3d1e033e26247e7a6505
|
|
| MD5 |
3a7880a8bef89640945a54a12a046507
|
|
| BLAKE2b-256 |
18fa0a856b4cd687e158cae46f68b529110a6a102ab6b9ea7c29edfa57c7fc2a
|