The Eval Mindset
Most teams building with large language models start the same way: they write a prompt, try a few examples by hand, and ship it when the outputs look "good enough." This works for prototypes. It falls apart the moment you need to change anything — the model, the prompt, the temperature, the retrieval strategy — because you have no way to know if the change made things better or worse.
Evals fix this. An eval is a structured test that measures how well your LLM system performs a specific task. Not a vibe check. Not a spot-check. A repeatable measurement that runs the same way every time, on the same inputs, with the same scoring criteria.
The teams that ship reliable LLM products are the ones that treat evals as a first-class product concern, not an afterthought.
The shift is subtle but important: instead of asking "does this output look right?" you ask "does this output score higher than yesterday's?" That reframing turns prompt engineering from guesswork into science.
What Makes a Good Eval
A good eval has three properties: it is deterministic in setup, measurable in output, and representative of real usage.
Deterministic Setup
Every eval run should use a fixed dataset. If you're testing a RAG pipeline, pin the documents in the index. If you're testing a chat agent, use recorded conversation transcripts. Randomness in the setup makes results incomparable across runs.
Measurable Output
You need a scoring function — something that takes the model's output and returns a number. This can be as simple as exact-match accuracy or as nuanced as an LLM-as-judge rubric. The key is consistency: the same output should always get the same score.
Representative Data
Your eval dataset should reflect production traffic, not just the examples you thought of during development. Pull samples from logs, ask your support team for tricky cases, and include adversarial inputs that expose failure modes.
Building Your Pipeline
The simplest eval pipeline has four stages: load the dataset, run each example through your system, score the outputs, and report the results.
import json
from pathlib import Path
def run_eval(dataset_path: str, system_fn):
"""Run an eval suite and return scored results."""
dataset = json.loads(Path(dataset_path).read_text())
results = []
for example in dataset["examples"]:
output = system_fn(example["input"])
score = score_output(output, example["expected"])
results.append({
"input": example["input"],
"output": output,
"expected": example["expected"],
"score": score,
})
return {
"mean_score": sum(r["score"] for r in results) / len(results),
"results": results,
}This isn't production code — it's the skeleton. In practice you'll want parallel execution, retry logic for rate limits, and structured logging. But the four-stage pattern stays the same regardless of complexity.
Scoring and Iteration
The scoring function is where most of the nuance lives. For factual QA, exact match or F1 over tokens works well. For open-ended generation, you'll likely need an LLM-as-judge approach where a second model grades the output against a rubric.
Here's the iteration loop that actually moves the needle:
- Run your eval suite against the current system.
- Identify the lowest-scoring examples — these are your failure modes.
- Categorize failures: is it a retrieval problem, a prompt problem, or a model limitation?
- Make one change at a time and re-run the suite.
- If the mean score improves without regressions, ship it.
Make one change at a time. If you change the prompt and the model simultaneously, you won't know which one helped.
Evals in Production
Running evals in CI is table stakes. The more interesting challenge is running them against production traffic. Sample a percentage of real requests, run them through your eval pipeline offline, and track scores over time.
This gives you two things you can't get any other way:
- Drift detection — if scores trend downward, something changed (your data, your users' expectations, or the model's behavior after a provider update).
- Coverage gaps — production traffic will surface input patterns your hand-crafted dataset missed.
The goal isn't perfection — it's awareness. When you know your system's baseline performance, you can make informed decisions about tradeoffs. Without evals, every change is a leap of faith.