Skip to main content
Single-run benchmark scores are unreliable. NAACL 2025 research showed that variance between runs is large enough to reverse model rankings — a model that looks 5% better in one run may simply be lucky. multivon-eval operationalizes this: CIs on every report by default, power warnings, multiple comparison correction, and judge calibration against human ground truth.

CIs shown by default

Every suite.run() report now includes confidence intervals without any extra code:
Access them programmatically:
The percentiles reveal what avg_score hides. A model that scores 0.95 or 0.40 (never in between) has the same avg_score as one that always scores 0.67 — but they behave very differently. A bimodal distribution usually means the evaluation criterion has a sharp decision boundary and your model is straddling it.

Why single-run scores lie

LLMs are non-deterministic. Even with temperature=0, hosted APIs introduce variance through hardware parallelism and batching. A 91% pass rate on 50 cases could be anywhere from 80% to 97% if you ran the same cases again. The fix: run more cases, run each case multiple times, and use confidence intervals to understand what your score actually means.

Confidence intervals with wilson_interval

The Wilson score interval is the most reliable CI for binomial proportions — it handles small n and extreme pass rates much better than the normal approximation.
experiment.compare() shows these automatically:

Know how many cases you need

Before running an eval, calculate whether your test suite is large enough to detect the improvement you care about.
Rule of thumb: A 2% improvement requires ~8,000 cases to confirm statistically. Most teams shouldn’t chase differences that small.

Power hints in compare()

When compare() finds a difference that doesn’t reach p < 0.05, it tells you how many more cases you’d need:
This is the difference between “we improved” and “we think we improved but can’t tell yet.”

What a 100% pass rate actually tells you

New in 0.16.0. A suite at 100% can no longer detect improvement — and, less obviously, it is bad at detecting regressions too. Two properties on every report quantify what a perfect score can still claim:
saturated is built on evaluated, not total — a 100% assembled from judge outages doesn’t count. min_detectable_regression anchors its variance near the observed rate, capped at a 0.95 baseline, so a perfect score can’t flatter its own sensitivity. Concretely, a 40-task suite at 100%:
The Wilson lower bound is the honest floor: 40/40 is consistent with a true pass rate of 91.2%. And at n=40, a real 14pp drop is the smallest this suite would reliably notice (~6% at n=200). This is always a warning, never a gate. Graduation. Declare the suite’s intent with the purpose kwarg — '' (unset), 'capability', or 'regression':
A saturated capability suite (n ≥ 3) gets the graduation warning above. A purpose='regression' suite inverts it: 100% is the expected steady state, and any task below ceiling prints a triage warning instead (“N previously-passing task(s) below ceiling — something broke; triage before shipping”). The purpose is copied onto the report and serialized in the JSON summary alongside saturated and min_detectable_regression; view --dir shows a saturated badge. The 0% end of the scale has its own detector — see zero-pass suspects.

Multi-run flakiness detection

Combine runs=N with statistical rigor for per-case stability analysis:
The same multi-run data also yields pass@k (capability) and pass^k (consistency) with cluster-bootstrap CIs — see pass@k and pass^k.

Multiple comparison correction

Running N evaluators and reporting N raw p-values inflates the false positive rate. At α=0.05 with 10 evaluators, you’d expect ~0.5 spurious “significant” results per run just by chance. exp.compare() applies Benjamini-Hochberg correction automatically when comparing evaluator scores, showing adjusted p-values with * for those that survive correction:
For standalone use:
BH is less conservative than Bonferroni — it controls the rate of false discoveries rather than the probability of any false discovery.

Judge calibration

A passing eval score only means something if your judge actually agrees with human judgment. suite.calibrate() measures this directly.
Interpreting the results:
  • Agreement ≥ 85%: judge is reliable for CI gating
  • Agreement 70–85%: usable for iteration but don’t gate deploys on it alone
  • Agreement < 70%: judge and humans disagree too often — reconsider your evaluator or threshold
Low precision means the judge passes cases humans would reject (over-permissive). Low recall means the judge rejects cases humans would pass (over-strict). Both affect CI reliability differently.

Interpretation checklist

Before trusting an eval result, ask:
  1. Is the improvement statistically significant? (exp.compare() shows p-value)
  2. Are the confidence intervals non-overlapping? If CI(before) and CI(after) overlap, the difference is inconclusive.
  3. Do I have enough cases? The power warning tells you automatically; use runs_needed() to plan ahead.
  4. Are there flaky cases inflating the variance? Check report.flaky_count.
  5. Are multi-evaluator comparisons corrected? exp.compare() applies BH correction automatically; watch the * markers.
  6. Is my judge calibrated? Run suite.calibrate() once on a labeled sample before using eval scores to gate deploys.
  7. Is the suite saturated? A 100% pass rate only proves a Wilson floor; check report.min_detectable_regression for what the suite can still see, and graduate it to purpose='regression'.