AnyLearn
All lessons
AIintermediate

LLM evaluation: how to know your model output is actually good

Why traditional software testing falls apart on LLMs, the four evaluation regimes that work in practice (golden sets, LLM-as-judge, human review, online metrics), and how to wire them together without drowning in ungrounded scores.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

Why "just write tests" doesn't work

Traditional unit tests assert exact outputs: assert f(2) == 4. LLMs produce text that's correct in many possible forms — different phrasing, different structure, different tone — all acceptable. There's no canonical equality.

You also have failure modes traditional tests don't catch: hallucinations, tone drift, format violations, partial correctness, regression-on-edge-cases. Every behaviour you care about needs its own way of being measured.

LLM evaluation is closer to product analytics than to unit testing. You're sampling behaviour over a population of inputs and watching aggregate quality move. You can't assertEqual your way through it.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. Why "just write tests" doesn't work

Traditional unit tests assert exact outputs: assert f(2) == 4. LLMs produce text that's correct in many possible forms — different phrasing, different structure, different tone — all acceptable. There's no canonical equality.

You also have failure modes traditional tests don't catch: hallucinations, tone drift, format violations, partial correctness, regression-on-edge-cases. Every behaviour you care about needs its own way of being measured.

LLM evaluation is closer to product analytics than to unit testing. You're sampling behaviour over a population of inputs and watching aggregate quality move. You can't assertEqual your way through it.

2. Four regimes you'll use

Practical eval strategies, roughly in order of cost and signal:

  1. Golden-set tests — a small curated dataset of inputs with expected behaviours. Cheap, deterministic, catch regressions. Doesn't generalise.
  2. LLM-as-judge — use another LLM to score outputs against a rubric. Scales, but the judge has its own biases.
  3. Human evaluation — paid raters or experts. Gold standard but slow and expensive.
  4. Online metrics — real users in production: click-through, completion rate, thumbs-up, retention. Most ecologically valid but lagging.

A mature eval stack uses all four. Each catches what the others miss.

3. Golden sets: small, sharp, maintained

Build a fixture set of 20–200 (input, expected-behaviour) pairs that cover your tricky cases. Behaviours are checked by assertions, not exact match:

def test_no_refund_made_up(case):
    out = pipeline(case["input"])
    assert case["customer_id"] in out                      # cites the right customer
    assert "$" in out                                       # mentions money
    assert not re.search(r"\$[0-9]{3,}", out)               # but not big numbers we don't have
    assert any(p in out for p in ["refund", "credit"])     # mentions the action

The checks combine must-include, must-not-include, regex shape, and occasionally LLM-judge for fuzzier rules. Run on every prompt change. New failure modes you encounter in production go into the set as new cases.

4. LLM-as-judge: power and pitfalls

An LLM scores outputs against a rubric. Looks like:

judge_prompt = """Given USER_QUESTION and ASSISTANT_REPLY, score the reply 1-5 on:
- Factuality: does it match the source?
- Relevance: does it answer what was asked?
- Tone: is it neutral and professional?
Return JSON: {"factuality": int, "relevance": int, "tone": int, "reasoning": str}"""

Known biases:

  • Length bias: judges prefer longer answers, often regardless of quality.
  • Position bias: when comparing A vs B, the first option scores higher more often than chance.
  • Self-preference: an LLM rates outputs from its own model family higher.

Mitigations: randomise A/B order, use a different model family as judge, calibrate the judge against human scores on a small subset.

5. An eval loop that doesn't lie to you

Golden + LLM-judge + occasional human spot-check.

flowchart LR
  Change["Prompt or model change"] --> Run["Run on golden set"]
  Run --> Asserts["Deterministic checks"]
  Run --> Judge["LLM-as-judge"]
  Asserts --> Report["Aggregate scores"]
  Judge --> Report
  Report --> Sample["Sample for human review"]
  Sample --> Report

6. Eval drift and the silent-degradation problem

Two kinds of drift bite even well-tested systems:

  1. Model drift: the vendor updates the model. "gpt-4o" today is not exactly "gpt-4o" six months ago. Your eval scores can move without any code change.
  2. Input drift: real-world usage diverges from your golden set. The set was hard six months ago; today's hard cases look different.

Mitigations: pin model versions where the API allows (Anthropic's date-stamped IDs, OpenAI's date-suffix names). Refresh golden sets quarterly with new examples from production logs — especially failure cases users complained about.

7. Tools worth knowing

You don't have to build this from scratch:

  • LangSmith — paid, polished, integrates with LangChain/LangGraph; good UI for traces + eval comparison.
  • Promptfoo — open source, YAML-config evals, great for CI integration.
  • Inspect AI — built by the UK AI Safety Institute, strong for capability/safety evals.
  • Phoenix — Arize's open-source LLM observability + evals, runs locally.
  • Helicone, Langfuse — observability-leaning; you can attach evals.

For most teams: start with golden tests in pytest + LLM-as-judge in a small script. Add a managed tool when you have multiple developers iterating on prompts.

8. Pragmatic advice

If you're starting evals from zero, do these in order:

  1. Today: collect 20 inputs that worry you. Run them on your current pipeline. Note what you see.
  2. This week: write deterministic assertions for the 5 most important failure modes. Wire them into CI.
  3. Next week: add an LLM-judge for the fuzzy ones (factuality, tone). Sanity-check 10 of its scores against your own judgement.
  4. Once you have logs: stream a sample of production conversations into the eval pipeline. The new bugs will be there.
  5. Quarterly: refresh the golden set with real failures, retire stale cases.

The single biggest mistake is waiting for the "right" eval framework. Start with pytest and a JSON fixture file. You'll learn what you actually need to measure.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why don't traditional unit tests transfer well to LLM output?
    • LLMs are too slow for unit tests
    • There's no canonical correct output — many phrasings can be acceptable, and many failure modes are subtle (tone, hallucination, partial correctness)
    • Python doesn't support testing LLMs
    • LLMs only output JSON, not text
  2. Your LLM-judge consistently rates longer answers higher than shorter ones. What is this called and how do you mitigate?
    • Position bias — randomise the order of A/B comparisons
    • Length bias — calibrate the judge or normalise scores for length
    • Self-preference — use a judge from a different model family
    • Recency bias — clear the judge's context between calls
  3. Your eval scores suddenly drop 10% with no code change. What's the most likely cause?
    • Random variance — re-run and ignore
    • The model provider silently updated the underlying weights
    • Pytest version changed
    • Your golden set corrupted itself
  4. Which is a *deterministic* check you'd write in a golden test?
    • An LLM judges whether the answer is "polite"
    • Assert the output contains the correct customer ID and does not contain dollar amounts above $1,000
    • A human rater scores the answer 1–5
    • The answer's embedding is close to a reference
  5. What's the recommended *first* eval move if you have none today?
    • Adopt an enterprise eval platform with full observability
    • Hire annotators for a 10,000-example dataset
    • Collect 20 worrying inputs, run them through your current pipeline, write assertions for the top failure modes
    • Switch to a more capable model and skip evals for now

Related lessons

Programming
intermediate

What Is Worth Testing, and the Shape That Follows

Every test costs time to write, time to run, and time to maintain when the code moves. This lesson works out what that budget should buy: what each level of test can and cannot catch, why the pyramid has the shape it does, why some teams invert it, and the arithmetic that decides both questions before anyone argues about it.

8 steps·~12 min
Programming
intermediate

Benchmarks That Hold Up, and Knowing When to Stop

A benchmark is an experiment, and most are badly designed enough to produce confident wrong answers. This lesson covers what a measurement must control to mean anything, the ways microbenchmarks lie including code the compiler deletes, how to catch regressions in continuous integration despite noisy machines, and how to recognise the point where optimising stops paying.

7 steps·~11 min
Programming
intermediate

Flakiness, Coverage, and Suites That Survive

A test suite is a product with a maintenance cost, and two forces decide whether it stays useful: flakiness, which destroys the signal, and the metrics teams use to judge it, which mostly measure the wrong thing. This lesson covers where flakiness comes from, the arithmetic that makes it fatal at scale, what coverage research actually found, and what to measure instead.

8 steps·~12 min
Programming
intermediate

Property-Based Testing: Assert the Law, Not the Example

An example-based test checks the cases you thought of, which is exactly the set that excludes your bugs. Property-based testing inverts it: state a law the code must obey for all inputs, let the machine hunt for a counterexample, and let it shrink that counterexample to something you can read. This lesson covers the property patterns, generators, shrinking, and where the technique stops fitting.

7 steps·~11 min