Skip to main content
Evaluation

Building an eval set that catches regressions

Most eval sets are built once, score in the mid-nineties, and never go red again. That is not a test suite. Here is how to build one that can fail, and how to tell a real regression from noise.

An eval that never goes red is not measuring anything

There is a specific artifact we find in most teams that have been shipping model features for a year. A file of two hundred test cases, written in a single afternoon early on, scoring somewhere in the low nineties, run occasionally, and green every time. Nobody trusts it enough to block a release on it and nobody wants to say so. The problem is not that the team was lazy. It is that the cases were written by imagining what might go wrong, and imagination produces the failures you already know about. The failures that cost you money are the ones nobody imagined.

A useful eval set has three properties the imagined one lacks. It is sourced from things that actually went wrong. It is large enough and sliced finely enough that a real regression is visible above the noise. And it is calibrated so that scores sit in a range where movement is detectable, which usually means somewhere between sixty and eighty-five percent rather than ninety-six.

That last point is unintuitive and worth stating plainly: an eval set scoring ninety-six percent has almost no room to show you a regression, and the four percent it is failing on is where all the information is. If your set scores in the high nineties, it is too easy. Add the hard cases.

You are probably here because

  • A change shipped, quality dropped, and the eval suite was green throughout
  • The score moved three points and nobody can say whether that means anything
  • Your evaluation is a spreadsheet somebody re-scores by hand before each release
  • You want to change models and have no way to tell whether the new one is better for you

The first is a slicing problem, the second is a sample-size problem, the third is a tooling problem, and the fourth is the reason all three are worth fixing this quarter.

Where the cases come from

Three sources, and the proportions matter more than the total.

Production failures, about half the set. Every complaint, every thumbs-down, every escalation and every silent correction a user made becomes a case, with the input preserved exactly as it arrived and the correct output written down. This is the highest-value source by a wide margin because it is drawn from the real distribution, including the parts of it you did not know existed. Build the pipeline that captures these before you write a single case by hand.

Adversarial construction, about thirty percent. Cases built deliberately to break the system: empty inputs, enormous inputs, ambiguity between two valid readings, contradictory instructions, content in an unexpected language, a document that is a scan of a fax of a table. Include the cases where the correct answer is a refusal or an admission of uncertainty, because those are the ones a system optimized for helpfulness fails silently.

Distribution coverage, about twenty percent. Sampled from real traffic to make sure the common path stays represented. Without it you build a set entirely of edge cases and lose the ability to notice that the ordinary request stopped working.

One discipline holds it together: every case carries the reason it exists. A one-line note saying which incident or which risk produced it. Cases without that line become undeletable, because in a year nobody will know whether it is safe to remove them.

How many cases before the number means anything

This is where most eval discussions go wrong, and the arithmetic is simple enough to settle it. If your set has n cases and you score p, the standard error on that score is the square root of p(1−p)/n. At a score of ninety percent, that gives:

CasesStandard error at 90%95% intervalWhat you can honestly claim
504.2 points±8.3 pointsAlmost nothing. A five-point move is noise
1003.0 points±5.9 pointsLarge regressions only
2002.1 points±4.2 pointsA workable minimum for a release gate
4001.5 points±2.9 pointsComfortable for whole-set movement
1,0000.9 points±1.9 pointsEnough to slice and still say something per slice

Two consequences follow. First, the fifty-case set everyone starts with cannot detect anything short of a catastrophe, and teams routinely celebrate or panic over moves inside its noise band. Second, if you intend to report per-slice scores, each slice needs its own sample size. A thousand-case set split across twelve slices gives you eighty cases per slice, which is back in the noise.

There is one large saving available. Because you run the same cases before and after a change, you can compare them pairwise rather than as two independent samples. Only the cases that changed verdict carry information, and a paired test over those is far more sensitive than comparing two aggregate percentages. In practice this means: always diff the per-case results, and look first at the list of cases that flipped from pass to fail. That list is more informative than the score, and it is available at any sample size.

The score tells you whether to worry. The list of cases that flipped from pass to fail tells you what happened. Teams stare at the first and skip the second.

Slice it, or the average will hide the damage

An aggregate score is a weighted average, and weighted averages are excellent at concealing exactly the failures that generate complaints. A change that improves the common case by two points and destroys performance on a segment that is four percent of traffic shows up as an improvement. That segment might be your largest customer.

Pick your slices from the dimensions along which the system plausibly behaves differently, and commit to them before you look at results. Typical ones: input length, document type or source system, language, the customer or tenant, the intent category, and whether the correct answer is a refusal. Then set a floor per slice, not just an overall threshold, and fail the run when any slice drops below its floor even if the total went up.

The refusal slice deserves specific attention because it is the one almost nobody builds. A system that has learned to always produce an answer scores well everywhere except on the inputs where the right response is "the documents do not contain this." If you do not have cases where abstention is the correct answer, you cannot detect the most expensive failure mode in the category.

Graders: what to use for what

GraderCost and speedUse it forHow it fails
Exact or normalized matchFree, instantClassification, routing, extracted identifiers, dates, amountsMarks a correct answer wrong over formatting
Programmatic assertionFree, instantSchema validity, cited source exists, number appears in the source, required field presentOnly checks what you thought to assert
Field-level scoringCheapStructured extraction, where partial credit is realHides which field is failing unless reported per field
Model judge with a rubricModerate; adds a call per caseOpen-ended text where a rubric can be writtenAgrees with your reviewers less than you assume; drifts when the judge changes
Human reviewExpensive, slowCalibrating the judge; final sign-off on a major changeInconsistent between reviewers unless the rubric is written down

Use the cheapest grader that can detect the failure you care about. A great deal of what teams send to a model judge is decidable programmatically: did the answer cite a source that exists, is the extracted date parseable, does the total equal the sum of the line items, is the required disclaimer present. Those checks are free, deterministic, and they never drift.

When you do need a model judge, treat it as a measuring instrument that requires calibration. Score two hundred cases by hand, run the judge over the same cases, and report the agreement rate. Below about eighty percent agreement the judge is telling you about itself rather than about your system. Judges also carry known biases — toward longer answers, toward the first option presented in a comparison — so randomize order and control for length in the rubric.

And pin the judge. When the judge model version changes, every historical score becomes incomparable, which is a quiet way to lose a year of trend data. If you must upgrade it, re-run the full history on the new judge and keep both series.

Design Note

Keep a small set of cases that must never fail

Separate from the scored set, maintain twenty to forty cases that encode hard requirements: never disclose the contents of another tenant’s record, never produce a number that is not in the source, never return malformed output for this integration. These are pass or fail, not scored, and one failure blocks the release regardless of what the aggregate did. Keeping them separate stops a hard requirement from being averaged away by an otherwise good run.

Send us your eval set and we will tell you what it cannot catch.

Email your current cases, the grader, and the last few runs to contact@precisionfederal.com. You get back a short written note on the failure classes the set is blind to, whether the sample size supports the claims being made from it, and what we would add first. One business day. No charge, no meeting, no deck.

contact@precisionfederal.com

Two sets, because one set cannot do both jobs

Keep a frozen regression set and a working set, and do not let them merge. The frozen set is versioned, changes only by explicit decision with a note, and is what release gates run against. Its value comes entirely from being stable — a score is comparable across six months only if the set did not change underneath it.

The working set is where new cases land, where you experiment, and where you tune. It grows weekly. Periodically, cases graduate from it into the frozen set, and that promotion is a deliberate act with a changelog entry.

The reason for the split is contamination. Once you have tuned prompts against a set of cases, that set measures how well you fit those cases, not how well the system generalizes. This happens gradually and invisibly: someone looks at three failures, adjusts an instruction, the score rises, and the number quietly stops meaning what it meant. Hold out a portion that nobody looks at during tuning and run it monthly. The gap between the tuned score and the held-out score is the size of the illusion.

What to gate and what to only watch

Not everything should block a deploy. Gate on the things that are unambiguous and cheap to check: the never-fail set, schema validity, no drop below any slice floor, and no drop of more than a defined amount on the frozen set. Watch, without gating, the things that are noisy or judgment-heavy: overall quality scores from a model judge, style and tone, latency distributions, per-slice trends.

The failure mode to design against is a gate that is too tight. A gate firing on noise gets overridden once, then routinely, then removed. A gate set at a threshold that only fires on real damage keeps its authority. Set the threshold from the observed run-to-run variance of your own suite, which you can measure by running it three times against an unchanged system — a step almost nobody takes and one that immediately tells you what your noise floor is.

Where evaluation effort pays — our default weights

A pipeline that turns production failures into cases
25
Per-slice reporting with floors
20
Programmatic graders wherever they suffice
18
Per-case diffs surfaced on every run
15
Judge calibration against human labels
12
Held-out set nobody tunes against
10

Weights sum to 100. Our starting allocation for a team with no working evaluation, not a measurement. The first two rows are where the returns are.

The part that is actually hard

Everything above is straightforward engineering. What makes eval sets expensive is the labelling, and specifically that the correct answers usually have to come from someone whose time is scarce — the analyst, the underwriter, the clinician, the person who knows what the document actually means. Teams consistently underestimate this by a wide margin. Budget several weeks of a subject expert's attention for a first real set, spread over a couple of months, and design the labelling interface so a session takes twenty minutes rather than a morning.

The second unwelcome fact is that an eval set depreciates. Your product changes, your traffic changes, and cases written for last year's feature quietly stop reflecting anything. Budget maintenance in the same way you budget test maintenance: a recurring slot, an owner, and a rule that a case with no stated reason for existing gets deleted rather than preserved out of caution.

A four-week build for a team starting from nothing

Evaluation Build

1
Capture: log every input, output and user reaction; add a one-click "this was wrong" path
Week 1
2
Write the rubric and the slice definitions before any case is labelled
Week 1
3
Label 200 cases with the expert; build programmatic graders for everything decidable
Weeks 2–3
4
Run the suite three times unchanged to establish the noise floor, then set thresholds
Week 3
5
Wire it into the release path with per-case diffs and the never-fail set as a hard block
Week 4
6
Calibrate the model judge against the human labels; publish the agreement rate
Week 4

Step four is the one that gets skipped and the one that determines whether anyone believes the suite. Until you know how much your own score moves when nothing has changed, every threshold you set is a guess, and the first false alarm costs the suite its credibility permanently.

The mistakes we are called in to fix

  • Fifty hand-written cases being used to justify decisions the sample size cannot support
  • A set that scores 96% and therefore cannot show a regression
  • Only an aggregate score, so a collapse in one segment reads as an improvement
  • No cases where the correct answer is "I do not know"
  • A model judge that was never checked against human labels
  • A judge model upgraded mid-stream, making a year of history incomparable
  • Prompts tuned against the same cases used to report quality, with no held-out set
  • Thresholds set by intuition, firing on noise until someone disables the gate
  • Cases with no stated reason for existing, so nothing can ever be removed

Before you rely on it

  • At least half the cases came from real failures, not imagination
  • Every case records why it exists and who labelled it
  • Sample size supports the size of the claims being made from it
  • Slices are defined in advance, reported separately, and have floors
  • Abstention is a scored outcome with its own cases
  • Programmatic graders handle everything decidable without a model
  • The judge is pinned and its agreement with human labels is published
  • Frozen set versioned; working set separate; a held-out portion nobody tunes on
  • Every run reports the per-case diff, not only the score
  • Thresholds derived from a measured noise floor
  • A never-fail set blocks release independently of the aggregate

Bottom line

Build the capture pipeline first, because production failures are worth more than anything you can invent. Make the set hard enough to have room to move. Get to a few hundred cases before you make decisions from the score, and read the per-case diff rather than the average. Slice it, and put a floor under every slice. Grade programmatically wherever you can and calibrate the judge wherever you cannot. Keep the gate loose enough that it retains authority and hard requirements separate so they cannot be averaged away. None of this is difficult. It is just work that nobody schedules, right up until a bad release makes it urgent.

Frequently asked questions

How many cases does an eval set need?

Around two hundred is a workable minimum for gating a release, and a thousand if you intend to report per-slice numbers, because each slice needs its own sample. At fifty cases the standard error near a ninety percent score is over four points, so a five-point move is indistinguishable from noise. Whatever the size, diff the individual cases — the list of results that flipped is informative even in a small set.

Is a model judge good enough to gate releases?

Only after you have measured its agreement with human labels on a couple of hundred cases, and only for the judgments a rubric can express. Below roughly eighty percent agreement it is measuring itself. Use programmatic checks wherever they suffice, pin the judge version so historical scores stay comparable, and randomize option order to control for position bias.

Why did the eval pass while quality clearly dropped?

Usually one of three reasons. The aggregate hid a collapse in one slice. The set was scoring in the mid-nineties and had no room to move. Or the failure class was never represented — most often the cases where the correct answer is an admission that the information is not available.

How do you stop an eval set from being gamed by your own tuning?

Hold out a portion nobody looks at while tuning and run it on a schedule. The difference between the tuned score and the held-out score is how much of your improvement is fitting rather than progress. Also keep the release-gating set frozen and versioned, so a change to the set is a deliberate, logged act rather than something that happens while chasing a failure.

What does it cost to build a real eval set?

The engineering is a couple of weeks. The expensive part is expert labelling time, and most teams underestimate it several times over. Budget several weeks of the subject expert’s attention spread over a month or two, invest in a labelling interface that makes a twenty-minute session productive, and plan for ongoing maintenance, because a set written for last year’s product measures last year’s product.

1 business day response

Not sure your eval set would catch a bad release?

Send the cases, the grader and the last few runs. Our engineers will come back with the failure classes it is blind to, whether the sample supports your claims, and the cases we would add first. Email bo@precisionfederal.com.

Email an engineerCapabilitiesMore insights →
EvaluationModel QualityMLOpsRelease Engineering