Skip to main content
An evaluation estimates performance on a particular case distribution with a particular grader. Sampling variation, repeated outputs, and grader mistakes can all affect the result. Separate these sources of uncertainty before using a score to approve a release. multivon-eval operationalizes this: CIs on every report by default, power warnings, multiple comparison correction, and judge agreement checks against supplied labels. Label provenance and held-out task validation remain the caller’s responsibility.

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 confidence interval across sampled cases does not predict the range of repeated runs on a fixed suite. Repeated trials measure a different source of variation. 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 an approximate interval for a binomial proportion. It behaves better than the simple normal approximation near zero or one, but assumes independent observations. Correlated cases from the same document or conversation require analysis at that cluster level.
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 percentage-point 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. An all-error run is not saturated, but a run with one pass and nine errors can be. Inspect errors and skips separately; saturation does not establish coverage. 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”). That warning assumes you intend these cases to pass; the purpose flag does not load or verify a historical baseline. 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.
The intervals above do not include uncertainty from missing cases, grader bias, or changes to the case distribution. Do not treat repeated cases as independent new tasks, or repeatedly stop at the first significant result without a sequential-testing procedure.

Starting configurations (not acceptance guarantees)


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() displays Benjamini-Hochberg-adjusted p-values derived from per-evaluator pass rates (the nearby score columns are descriptive). Its summary comparison uses an unpaired two-proportion approximation. Per-evaluator tests use total case counts, which can overstate evidence when evaluators skipped cases or runs had errors. Use complete, comparable runs; for matched cases, prefer baseline_report.compare(proposal_report) and its McNemar test. The following output is illustrative:
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

suite.calibrate() measures agreement with labeled examples. Despite the name, it does not fit or update thresholds. Choose thresholds on a development split, then measure performance on a separate test split. Review missing evaluator coverage as well as the reported agreement.
Interpreting the results:
  • Inspect false accepts and false rejects separately, with sample sizes and uncertainty.
  • Check each consequential task slice; overall agreement can hide rare failures.
  • Set acceptance criteria from the consequences of a mistake. No universal agreement percentage establishes fitness for CI gating.
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. Did I test the difference directly? Separate confidence intervals can overlap even when a paired difference is significant. Use a comparison appropriate to the sampling design.
  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? Measure agreement on held-out labeled samples and repeat after changes to the judge, rubric, parser, or task distribution.
  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'.