Build1 publisher3 min readPublished
Catching a model-swap regression takes twenty labeled documents run twenty times each
A dev.to write-up swaps the single assertion for per-field hit rates with 95 percent intervals over repeated trials. On its own example table, the pair it calls a real regression has intervals that overlap by 0.3 points.
The Engineer · Build desk

What happened
- A model deprecation forces a config swap, and two weeks later a field that used to extract correctly comes back blank about a fifth of the time, with no change to the prompt or the code.
- The proposed harness loops over twenty frozen labeled documents, runs each one twenty times, scores every field as a hit or a miss, and puts a 95 percent confidence interval around each field's rate.
- The worked example reports liability_cap at 71.0 percent, the weakest of four fields, against 96.5 percent for governing_law, each with its own interval.
- The comparison rule asks whether the two models' intervals separate, and treats a gap between point estimates as insufficient on its own.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost A straight incumbent-versus-replacement comparison bills 800 extraction calls on long documents before it reports anything, and the team pays the labeling cost first.
- constraint Trials against the same document are correlated, so the 400 calls buy fewer effective samples than the count implies and the interval a team can honestly report is wider than a naive binomial one.
- contradiction Applied literally, the separation rule leaves the piece's own showcase regression unresolved, so anyone adopting it needs either more documents or a stated tolerance for near-touching intervals.
- decision The deprecation date sets the schedule for building the labeled set, since the comparison only works while both the old and the new model are still callable.
Apply the post's own comparison rule to its own table and the second row gets close. Model A's notice_period_days interval runs 84.1 to 92.9 percent; model B's runs 74.0 to 84.4 [15]. Those overlap between 84.1 and 84.4, a span of 0.3 points [1]. The row is labeled "separated, real regression" [15]. The point estimates sit 9.3 points apart and the intervals still touch [2]. Under the rule as written, the strongest example in the piece is a third of a point from inconclusive.
The suite is twenty labeled documents run twenty times each [10][11], which comes to 400 extraction calls per model [4]. Comparing an incumbent against its replacement doubles that to 800 [4]. Each run scores eight fields [7], so one model evaluation produces 3,200 field-level hits and misses [5]. Labeling comes first, on documents like the 30-page service agreement where the notice period is in clause 12 and an amendment on page 27 overrides it [3], with ground truth agreed once and then left alone [10].
Those 400 calls are not 400 independent samples. Twenty trials against one document are correlated, because a document the model reliably reads wrong contributes twenty misses rather than one. The published widths fit that. A normal-approximation 95 percent interval on 400 independent trials at an 88.5 percent hit rate is about plus or minus 3.1 points [6]; the table shows plus or minus 4.4 [12], which is the width you get near 200 samples [7]. The table is illustrative, so the figures may simply be hand-written. In my view the interval should be resampled over documents, not over calls.
Field-level ranking is a claim about one corpus. liability_cap lands lowest at 71.0 percent because in these contracts the value is buried in carve-outs and cross-references [12][13]. For that ordering to hold on your documents, your weak field would have to be weak for the same structural reason. The output format transfers: a hit rate per field with an interval around it.
A single run cannot stand in for that, because even at temperature=0 identical outputs are not guaranteed, and batching, hardware and silent server-side changes all move the result [5]. "A single passing run is one sample from a distribution you have not measured," the author wrote [6]. The reason the swap goes unnoticed is that a wrong value is shaped like a right one: 60 days is plausible for a notice period, and so is 30, which is what the superseded clause says [4].
The first harness most people write asserts one value per field on one document [8]. The post calls that a smoke test that tells you the pipeline is wired up, says nothing about accuracy, and flakes until somebody marks it @pytest.mark.skip [9]. The author wrote that the whole thing can be scripted in an afternoon, and that he has written versions of it more than once [17].
What to watch
- Whether anyone publishes the harness with intervals bootstrapped over documents rather than over individual calls, which would widen the reported ranges.
- Whether providers give enough notice on deprecation dates for a 400-call comparison to run against both models before the old one is withdrawn.
- Whether the same per-field table gets used to gate prompt edits, not just model swaps.