Who Checks the AI Judge? Calibrate It Before It Gates
A judge that agrees with your reviewers 60% of the time produces numbers that look like a good one. LLM judge validation in ai-eval: held-out labels, rates computed by calibrate.py, and a lint that fails any gate on an unvalidated judge.
Key takeaways
- Try a code assertion for every failure mode before writing a judge; a judge is only for what code cannot decide.
- Human labels are split once into train, dev and test, and the test split is scored once, with at least 30 Pass and 30 Fail labels.
- A judge is VALIDATED when TPR and TNR are at least 0.80, kappa at least 0.60 and TPR minus FPR at least 0.50.
- Lint recomputes the rates from the labels and fails a validation.md that states different ones, and fails a gate that names an unvalidated judge.
- A new prompt or a new judge model is a new version that must be validated again, and a failed version stays on file.
Contents
FAQ
Why must the judge be a different model family from the system?
Same-family judging inflates scores, so lint error E020 fires when a gated suite uses a judge from the same family as the system under evaluation.
What happens to a suite whose judge is not validated?
It can still run in exploratory mode, but every number carries the UNVALIDATED marker and the suite verdict is NOT-EVALUABLE, never PASS.
Sources
Only you and the owner see your answer and rating. Send private feedback
Enjoyed it? Support fx-blog with a coffee.
Related posts
Read the Scorecard: Intervals, Gates and a Frozen Baseline
A scorecard that says 87% can hide that 60 cases cannot support it. An LLM evaluation scorecard in ai-eval: every rate with its 95% interval, gates on the lower bound, and a regression gate on the largest drop in the total or any failure mode.
Read 100 Real Traces Before You Write One Test
Most LLM test suites measure failures their author imagined. LLM error analysis in ai-eval: read real traces, name failure modes that need three trace references each, and build a dataset with at least 30% real inputs.
An AI Eval Pipeline That Can Say NOT-EVALUABLE
A quality number for an AI feature means little when nobody can say who checked the judge, how many cases it rests on or how sure it is. Five questions to ask of any setup, and the route ai-eval takes from traces to a scorecard that can say NOT-EVALUABLE.
Comments
Oldest firstNo comments yet. Start the conversation.
The orders support assistant has a failure mode that code cannot decide, a refund timeline that contradicts the policy. Part 2 ended there: "in about a week" and 7 days look the same to an assertion. A model has to judge it, and that judge is now part of your measurement. This part asks who checks it.
This series, in four parts:
Every number labelled "sample" comes from the illustrative scenario in the bundle's showcase,
docs/showcase/pipeline.html, not from a measured product.Try a code assertion before any judge
/eval-judgetries a code assertion for every failure mode first. FM-001 is decided in code: every id-shaped token in the answer must appear in a tool result. No labels, no model, no variance. The bundle ships seven such assertions, listed inassertions/registry.yaml.Only what survives this table becomes a judge: one binary decision about one failure mode,
JDG-<slug>-vN.Score the judge on labels it never saw
The human labels are split once into train, dev and test, and never reshuffled. You tune the prompt against dev. The test split is scored once, at the end, and each of dev and test needs at least 30 Pass and 30 Fail labels.
Then
/eval-validaterunscalibrate.py, which computes the result from the judge's saved verdicts on the test split. Here it is on the bundle's broken test fixture, where the split is far too small:TPR is the share of real failures the judge catches, TNR the share of good answers it lets through. The thresholds come from
EVAL-PROFILE.md: both at least 0.80, kappa at least 0.60, and TPR minus FPR at least 0.50. Below that last one, the bias correction only amplifies noise.Version 1 fails, and stays on file
In the sample,
JDG-refund-timeline-v1scored TPR 0.698 on a test split of 43 Fail and 42 Pass labels. It missed 13 labelled failures, all hedged ranges such as "3 to 5 days" that do not contain the policy figure. Verdict:UNVALIDATED.The fix was a sentence in the prompt naming that case as a failure condition. A new prompt is a new version, so
v2was scored on the same test split, again in the sample: TPR 0.860, TNR 0.905, kappa 0.765,VALIDATED. Version 1 stays in the history.Every false positive and false negative is listed one by one in
validation.md. The rates decide the verdict. The list is what you read when you rewrite the prompt.The numbers are computed, not typed
Lint recomputes the rates from the labels and refuses a
validation.mdthat states different ones:The runner enforces it too: a suite with an unvalidated judge ends
NOT-EVALUABLE, decided byjudge not validated. And a new judge model, even a provider-side bump, invalidates the validation (E022): bump the version and score it again.Try it
Before you trust any judge you already run, find its test split and count the Pass and Fail labels. If either is under 30, its TPR is a number, not evidence. In the sample scenario both judges are now validated, so the assistant can finally be run on 220 cases. What a run reports, and when it says NOT-EVALUABLE anyway, is the job of part 4, Read the Scorecard: Intervals, Gates and a Frozen Baseline.
Read this with your AI