4. Evaluate your agent
Your agent works when you try it. But does it work every time, for every kind of question? After you change the prompt or swap the model, is it better or just different? Trying a few questions by hand won’t tell you. Evals will.
An eval is a test for an AI system: an input, and a way to decide whether the output is good enough.
Why AI testing is different
Section titled “Why AI testing is different”Normal tests expect an exact output. AI output changes from run to run, and many different answers can be correct. So we combine different kinds of checks:
| Check | Example | Cost | Good for |
|---|---|---|---|
| Tool checks | Did it call search_faq? |
Free, instant | Behaviour you can see in the trace |
| Text checks | Does the reply contain 12:30? |
Free, instant | Facts that must appear (or must not) |
| LLM judge | Does the reply meet this rubric? | One model call | Meaning, tone, completeness |
Use cheap, exact checks wherever you can. Use the judge for what only a reader could decide.
The test set
Section titled “The test set”Open code/04-evaluate/dataset.jsonl. Each line is one case:
{"id": "faq-lunch", "input": "When is lunch, and is there vegan food?", "expect_tools": ["search_faq"], "must_contain": ["12:30"], "rubric": "Says lunch is 12:30 to 13:30 and that vegan options are available."}A case only uses the checks it lists. Some cases have only a rubric, and some have no rubric at all.
Exercise: write the checks
Section titled “Exercise: write the checks”Open code/04-evaluate/starter/run_evals.py. The runner is already there: it loads your agent, runs each case in a fresh conversation, applies the checks and prints a report. You write three checks.
TODO 1: check_tools. Every expected tool must have been called. An entry like "a|b" means either one is fine.
def check_tools(case, result): called = {c["name"] for c in result["tool_calls"]} missing = [t for t in case["expect_tools"] if not set(t.split("|")) & called] return (not missing, f"missing {missing}" if missing else f"called {sorted(called)}")TODO 2: check_contains. Every required phrase must appear in the reply, ignoring case.
def check_contains(case, result): reply = result["reply"].lower() missing = [p for p in case["must_contain"] if p.lower() not in reply] return (not missing, f"missing {missing}" if missing else "ok")TODO 3: judge. Ask a model to grade the reply against the rubric. The prompt template, JUDGE_PROMPT, is at the top of the file.
def judge(case, result): prompt = JUDGE_PROMPT.format(question=case["input"], answer=result["reply"], rubric=case["rubric"]) verdict = llm.chat([{"role": "user", "content": prompt}], model=config.JUDGE_MODEL, temperature=0) text = verdict["content"].strip() first_line = text.splitlines()[0].upper() if text else "" return ("PASS" in first_line and "FAIL" not in first_line, text.replace("\n", " ")[:160])Run the evals against your Module 3 agent:
python 04-evaluate/starter/run_evals.py[PASS] beginner-day1 (3.1s)[PASS] session-detail (2.4s)[PASS] full-session (2.9s)[FAIL] faq-wifi (2.2s) expect_tools: missing ['search_faq'] must_contain: missing ['badge'] reply: "I can't provide the wifi password. You can find it in the event FAQ or ask the conference staff directly."...7/8 passed (88%)Saved details to results/eval-20261104-142210.jsonThat failure is real: it’s what happened when we tested with qwen3:4b-instruct. The model decided a password was too sensitive to look up, even though the FAQ has the answer. Your results will differ. That’s the point. Open the JSON file in results/ to see every reply and tool call.
Exercise: make it better
Section titled “Exercise: make it better”- Run it more than once. AI is not deterministic.
A case that passes 2 out of 3 times is a flaky feature, not a passing one.
Terminal window python 04-evaluate/starter/run_evals.py --runs 3 - Pick one failing case and fix it. Change the system prompt in
03-agent-loop/starter/agent.py, or improve a tool description inagentlab/tools.py. Run the evals again. Did your fix break anything else? - Add two cases of your own. One for something the agent should do, and one for something it should refuse or say it doesn’t know.
- Compare models. If you have another model pulled, change
LLM_MODELin.envand run the evals again. Now you can pick a model with data, not vibes.
Who judges the judge?
Section titled “Who judges the judge?”An LLM judge is also an AI system, and it makes mistakes too. A small judge model may be too strict or too lenient. Some habits help:
- Write rubrics that are specific and checkable (“says lunch is 12:30 to 13:30”), not vague (“is helpful”).
- Read some of the judge’s verdicts yourself. If you disagree often, fix the rubric.
- Use a stronger model as the judge if you can. Set
JUDGE_MODELin.env. It can even be a different provider from the agent.
Try this
Section titled “Try this”- Run
python 04-evaluate/starter/run_evals.py --only faq-wifi --runs 5. - Add
--min-pass-rate 0.8to make the command fail when quality drops. That’s how you’d use evals in CI.
Checkpoint. You can measure your agent instead of guessing. You have deterministic checks, an LLM judge, repeated runs and a pass rate you can track over time. In Part 3 you’ll use the same runner to prove your security fixes work.
Stuck? Use python 04-evaluate/solution/run_evals.py