LLM evaluation in practice: how to know your AI feature actually works
Build an LLM evaluation harness from real failure cases rather than synthetic examples: collect fifty to two hundred labelled cases from actual usage, score each against an explicit rubric, use deterministic checks wherever possible and a model judge only where necessary, and run the whole set in CI so a regression fails a build rather than reaching a user.
Conventional tests assert that a given input produces a given output. Model-backed features do not work that way: the same input produces a different string each time, and most of those strings are fine. You are not asserting equality — you are measuring a distribution against a standard.
That sounds academic until the first time someone edits a prompt to fix one complaint and silently breaks a category of inputs nobody was watching. The harness below is the minimum that prevents that, and it is smaller than most teams fear.
Step 1 — Collect real cases, not invented ones
The most common mistake is generating the evaluation set with a model. Synthetic cases inherit the model's idea of what your problem looks like, which is precisely the assumption you are trying to test. They produce reassuring scores and catch nothing.
- Start from real traffic, real documents, real tickets — whatever the system will actually see.
- Aim for fifty to two hundred cases. Bigger sets are not better if nobody maintains them.
- Deliberately over-weight the awkward ones: ambiguous inputs, unusual formats, out-of-scope requests, the long tail that generated complaints.
- Include cases where the correct answer is a refusal. A system that never declines is not calibrated.
- Every time something goes wrong in production, the failing case joins the set. This is how the harness stays honest.
An evaluation set built from real failures is worth ten times one built from imagined successes.
Step 2 — Write down what "correct" means
Vague criteria produce vague scores. "Is this a good summary?" is unanswerable consistently, even by people. Decompose it into things that can be judged independently and, where possible, mechanically.
| Criterion | How it is checked |
|---|---|
| Structurally valid output | Deterministic — schema or parser |
| Contains the required fields | Deterministic — key presence |
| Every claim is supported by the source | Model judge with the source in context |
| No information outside the source | Model judge, scored separately from support |
| Correct refusal on out-of-scope input | Deterministic — did it decline, yes or no |
| Within length and register constraints | Deterministic — length; judge for register |
Step 3 — Use a model as judge, carefully
For genuinely subjective criteria, having a model score the output is the practical option. It works well enough to be useful and badly enough to need guardrails.
- Score one criterion per call. Composite quality scores are noise.
- Use a coarse scale. Binary or three-point judgements are far more stable than one to ten.
- Give the judge the rubric and a worked example of each score, not just the label.
- Validate the judge against human labels on a sample before trusting it. If it disagrees with your domain experts, fix the rubric.
- Beware self-preference: a model judging its own output rates it generously. Use a different model as judge where you can.
Track judge-versus-human agreement as its own metric. When it drops, the harness is lying to you and the whole edifice is unreliable.
Step 4 — Evaluate retrieval separately from generation
If the system retrieves before it answers, measure the two halves independently. Otherwise a poor score is uninterpretable — you cannot tell whether the model reasoned badly or was handed the wrong material.
Label which passage answers each question, then measure how often it appears in the top-k results. That recall number is a hard ceiling on end-to-end quality, and it is usually where the actual problem is. More on that in RAG vs fine-tuning.
Step 5 — Run it in CI, on every change
An evaluation set that someone runs manually before a big release is a document. One that runs on every pull request is a safety net.
- Run on every change to a prompt, a model version, a retrieval parameter, or a tool definition.
- Fail the build on a regression beyond an agreed threshold, not on any movement — there is inherent variance.
- Report per-category scores, not one aggregate. An average conceals the segment that collapsed.
- Pin the model version explicitly. Silent provider-side updates are a real source of drift.
- Keep it fast enough that people do not route around it. A subset on every commit and the full set nightly is a reasonable compromise.
Step 6 — Close the loop with production signals
Offline evaluation tells you whether a change is safe. It cannot tell you whether the feature is useful. Pair it with signals from real use: how often a suggestion is accepted unedited, how often output is corrected in the review queue, how often users escalate to a human, how often they simply stop using the feature.
When production signals and offline scores disagree, believe production and fix the evaluation set — usually it is missing the cases users actually bring.
The minimum viable harness
If this feels like a lot, start here. It is a couple of days of work and it is most of the value:
- Fifty real cases in a file, with a labelled expected outcome for each.
- Deterministic checks for structure, required fields, and refusals.
- One model-judged criterion for the thing you care about most.
- A script that runs it and prints per-category pass rates.
- A rule that every production failure becomes a new case.
Frequently asked questions
How do you evaluate an LLM application?
Collect fifty to two hundred real cases from actual usage rather than synthetic examples, decompose "correct" into criteria that can be judged independently, check as many as possible deterministically with a parser or schema, use a model judge only for genuinely subjective criteria, and run the whole set in CI so regressions fail a build.
How many test cases do I need in an LLM eval set?
Fifty to two hundred is usually right. Bigger sets are not better if nobody maintains them. What matters more than volume is composition: over-weight ambiguous inputs, unusual formats, out-of-scope requests, and every case that has already failed in production.
Is using an LLM as a judge reliable?
Reliable enough to be useful, with guardrails. Score one criterion per call, use a binary or three-point scale rather than one to ten, give the judge worked examples of each score, validate it against human labels before trusting it, and prefer a different model from the one being judged to avoid self-preference bias.
Why did my AI feature get worse after a prompt change?
Almost always because the change fixed one visible complaint while degrading a category of inputs nobody was measuring. Without an evaluation set covering the segments separately, that trade is invisible. Report per-category scores rather than one aggregate — an average conceals the segment that collapsed.
- evaluation
- engineering
- testing
- quality
The systems behind this article
Production builds from our portfolio that this piece draws on.

ScanQueue
An AI radiology worklist that flags suspected critical findings on incoming CT, MR and X-ray studies and orders every read by acuity and SLA — so the sickest patient is read first, not FIFO.
Healthcare AI
Notewell
An AI meeting assistant that records and transcribes every meeting, extracts the decisions and action items, assigns owners and due dates, and tracks follow-through until it's done.
Productivity AIIndustries this applies to
Read next
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team — and how fast we can ship.
Start the conversation