Engineering note
Evaluations that gate a deploy
An evaluation suite that nobody can fail is a dashboard. This is how we build one that blocks a release, and what it costs to keep it honest.
Most teams build evaluations after the first quality complaint. By then the system is in production, the corpus has changed twice, and there is no baseline to compare against. The suite that gets built in that state measures the current behavior and calls it correct.
Three suites, three jobs
The golden set is human-labeled and versioned like code. It is small, usually one to three hundred cases, and it covers the work the system actually receives rather than the work someone imagined. Adding a case is a pull request with a reviewer.
The adversarial set holds the things that must never happen: prompt injection carried in a retrieved document, a request for data outside the caller's scope, a refusal the system should not make. These are pass or fail, not scored.
The regression set is everything that broke once. Every incident adds a case. It is the cheapest suite to maintain and the one that catches the most on a Tuesday.
Score what you can check deterministically
Before you reach for a model judge, check what a program can check: valid schema, every claim carrying a citation to a retrieved passage, no identifier the caller may not see, latency inside budget. These are fast, free, and unambiguous.
Use a judge for what remains, and calibrate it. Have humans label a sample, measure the judge against those labels, and record the agreement rate alongside the score. A judge you have not calibrated produces a number, not a measurement.
# The gate is a comparison against the recorded baseline, not a
# threshold someone remembers.
def gate(candidate: RunSet, baseline: RunSet) -> Verdict:
checks = [
("schema_valid", candidate.schema_valid_rate, ">=", baseline.schema_valid_rate),
("citation_recall", candidate.citation_recall, ">=", baseline.citation_recall - 0.01),
("judge_score", candidate.judge_mean, ">=", baseline.judge_mean - 0.02),
("p95_latency_ms", candidate.p95_latency_ms, "<=", baseline.p95_latency_ms * 1.1),
("cost_per_task", candidate.cost_per_task, "<=", baseline.cost_per_task * 1.15),
("injection_refusal", candidate.injection_refusal_rate, ">=", 0.99),
]
failures = [c for c in checks if not compare(c)]
return Verdict(passed=not failures, failures=failures, run_id=candidate.run_id)
Gate on a baseline, not a threshold
Absolute thresholds rot. The team lowers them after a bad week, and nobody records why. Comparing a candidate against the recorded baseline of the current production version makes the question concrete: is this change better or worse than what is running now.
| Check | Rule | Why this one |
|---|---|---|
| Schema valid | no worse than baseline | a parse failure is a total failure |
| Citation recall | within 1 point of baseline | unsupported claims are the visible harm |
| Judge score | within 2 points of baseline | noisy, so allow a small band |
| p95 latency | within 10% of baseline | tail latency is what users feel |
| Cost per task | within 15% of baseline | quality bought with money is not free |
| Injection refusal | at least 99%, absolute | the one case with no acceptable regression |
What it costs
A serious suite runs on every pull request and costs real money in inference. Budget for it explicitly rather than discovering it. The alternative is a system whose quality you cannot state, and a system whose quality you cannot state is a system no one will approve for a workload that matters.
Bring us the problem.
Tell us the outcome you are trying to create, what you have already attempted, and where the constraints are.
Contact nuperX