Why your AI agent needs evals (and how to build them in an afternoon)
You never ship an improvement you haven't measured. Golden datasets, deterministic scorers vs LLM-as-judge, negative test cases, and the data flywheel that turns every user correction into a regression test.
Here’s a scenario every AI engineer recognizes. You tweak a prompt. You run it on two examples. It looks better. You ship it.
Three days later something else is broken, and you have no idea whether your change caused it — because you have no baseline, no test set, and no number.
The habit that separates a demo from a product is small and unglamorous:
You never ship an improvement you haven’t measured.
That’s it. That single habit is the profession. Everything below exists to make it cheap enough that you actually do it.
Why normal tests don’t work
Traditional tests assert exact outputs. LLM outputs are non-deterministic and often correct in many different phrasings, so assert result == expected fails immediately.
The answer isn’t to give up on testing. It’s to change what you assert:
- Assert on structure (did it return valid JSON matching the schema?)
- Assert on behavior (did it call the right tool?)
- Assert on properties (does the answer mention the required fact? is it under N tokens?)
- Assert on judgment (does a model rate this as answering the question?)
Start with a golden dataset
A golden dataset is just a list of cases: an input, and what should happen.
Twenty cases is enough to start. Where they come from:
- Real usage. The best source, by far.
- Failures you’ve seen. Every bug you fix becomes a permanent case.
- Edge cases you’re afraid of. Ambiguous requests, adversarial inputs, empty results.
- Negative cases. See below — these matter more than people expect.
Keep it in a plain JSON or YAML file in the repo. This is not infrastructure you rent.
Include negative cases
The single most underrated eval case is one where the agent should do nothing.
Ask “what’s the capital of France?” of an agent with file and shell tools. The correct behavior is to answer directly — no tool calls at all. Agents that have been over-tuned toward tool use will reach for web_search or start reading files, burning latency and money on a question the model already knows.
If your eval suite only contains cases where a tool should fire, you will never catch this.
Two kinds of scorer
Deterministic scorers — use these wherever possible. They’re free, instant, and never flaky:
def scores_tool_choice(trajectory, expected_tool):
tools_used = [s.name for s in trajectory if s.type == "function_call"]
return expected_tool in tools_used
Did it call the right tool? Is the output valid JSON? Did it stay under the step limit? Did it avoid touching files outside the project? All checkable with plain code.
LLM-as-judge — for the genuinely subjective parts. Give a model the question, the answer, and a rubric, and have it return a structured verdict:
verdict = client.responses.create(
model=JUDGE_MODEL,
instructions=(
"You grade agent answers. Reply with JSON: "
'{"passed": bool, "reason": str}. '
"Pass only if the answer is factually correct and directly addresses the question."
),
input=f"Question: {q}\n\nAnswer: {a}",
)
Two rules that keep judges honest: make the rubric specific (vague rubrics produce generous grades), and require a reason alongside the verdict so you can spot-check when the judge is wrong.
Establish a baseline before you optimize
Run the suite once, write down the number. That’s your baseline.
Without it, every subsequent change is an opinion. With it, you get to say “tool description rewrite: 62% → 81%” — which is both a much better engineering conversation and a much better thing to put in a standup.
Expect surprises. Some changes you’re sure about won’t move the number at all. That’s not a failed experiment; that’s the suite doing its job and saving you from shipping complexity for nothing.
Score trajectories, not just answers
For agents, the final answer isn’t the whole story. A correct answer reached through nine unnecessary tool calls is a latency and cost bug hiding inside a passing test.
Worth scoring:
- Steps taken vs. the minimum required
- Tool selection accuracy at each step
- Retries caused by malformed calls
- Total tokens and cost per case
These are the metrics that reveal interface problems — usually a vague tool description rather than anything about the model.
The data flywheel
The final piece is what makes the suite compound: every user correction becomes a test case.
When someone reports the agent did the wrong thing, you don’t just fix it. You add the case to the golden dataset. The suite grows with usage, and it grows specifically in the places your users actually care about.
That’s the flywheel:
usage → failures → new eval cases → measured fixes → better agent → more usage
An eval suite built this way stops being a chore and becomes the most valuable artifact in the repo — a precise, executable specification of what your agent is supposed to do, written by reality.
Start today
You can build the first version in an afternoon:
- Write 20 cases in a JSON file, including 3 negative ones.
- Write 2 deterministic scorers and 1 LLM judge.
- Run it. Record the number.
- Change one thing. Run it again.
That’s a real eval suite. Everything after that is refinement.
Evals are the spine of Week 2 in the AI Engineering mentorship — golden datasets, scorers, a baseline, and then an entire half-week of measured improvements.
Build it, don't just read about it
The 12-week AI Engineering mentorship takes you from an empty file to a complete AI coding agent — every subsystem, real code, no frameworks.
Prompt caching and what an AI agent actually costs
Your agent resends the entire conversation on every single turn, so cost grows quadratically with steps. Prompt caching fixes most of it — but only if your prefix is stable. Here are the four rules, the mistake that silently triples your bill, and why caching and context trimming pull against each other.
Claude Code vs. Grok CLI: how two production coding agents talk to their models
A source-level comparison of two shipping AI coding agents — which API shape each uses, why Grok speaks OpenAI's wire format on its own servers, and the multi-backend abstraction that lets one client target three APIs.
Chat Completions vs. the Responses API: a practical migration guide
The mental-model shift from messages to items, a field-by-field migration map, tool calling with call_id, conversation state, and how the same code runs through the OpenAI SDK, a gateway, and LangChain.