Without evals every prompt tweak is a guess, and quality can silently drop when you switch models or providers.
An eval is a test for an AI feature: a set of realistic inputs, a way to judge each output, and a score. It answers "is version B better than version A?" with numbers instead of a feeling from three tries.
Building one:
1. Define success concretely: "the label matches what a human would choose", "the answer cites a source", "the reply is under 80 words". 2. Collect test cases that mirror real inputs, including edge cases: typos, missing information, off-topic or hostile messages, ambiguous ones. Real (anonymised) inputs beat invented ones. Start with 20-50; grow it whenever you find a failure. 3. Choose a grading method, the fastest one that is reliable enough:
- Code-based: exact match, "contains", valid JSON, length checks. Fast and objective; ideal with structured output.
- Model-based ("LLM as judge"): another model grades against a written rubric and returns a fixed label such as pass/fail. Flexible for tone or helpfulness; check its judgements against a human on a sample first, and prefer a different model from the one being tested.
- Human review: the gold standard, but slow; use it for small samples and to calibrate the others.
4. Run it on every change to the prompt, model, tools or retrieval, and compare scores.
Remember outputs vary from run to run, so run each case more than once when differences are small. For agents, grade the final outcome (was the ticket actually resolved?) and also inspect the steps. Keep some cases aside that you never tune on, to catch prompts that only work on the examples you looked at.
Providers offer eval tooling (OpenAI's API platform has an Evals feature, for example), as do open-source frameworks; a spreadsheet and a short script are enough to start.
Example
# A tiny eval: test cases + code-based grading + a score.
cases = [
{"input": "My card was charged twice", "expected": "billing"},
{"input": "App crashes on login", "expected": "bug"},
{"input": "Do you ship to Canada?", "expected": "other"},
]
def classify(text: str) -> str:
# Replace with a real model call that returns a label (ideally via structured output).
return "billing" if "charged" in text else "bug"
results = []
for case in cases:
got = classify(case["input"])
results.append(got == case["expected"])
if got != case["expected"]:
print("FAIL:", case["input"], "->", got)
print(f"score: {sum(results)}/{len(results)}") # FAIL: Do you ship... -> bug / score: 2/3Remember
- An eval = realistic test cases + a grader + a score
- Prefer code-based grading; use LLM-as-judge with a clear rubric; calibrate with humans
- Rerun evals on every prompt, model or tool change
Watch out for
- Tuning the prompt until your handful of test cases pass, then shipping without a held-out set.