Module 9 of 10, lesson 1 of 5
Evaluating an agent
Build an evaluation set that tells you whether a change really improves the agent.
Without evaluation, every prompt change is a bet, and a successful demo proves nothing.
Three levels to measure
- Final answer: was the task accomplished?
- Trajectory: did it use the right tools, with the right arguments, without needless steps?
- Operations: cost per task, latency, rate of escalation to a human.
Building the test cases
- Start from real requests, including observed failures.
- Add edge cases, out-of-scope cases and injection attempts.
- For each case, write the success criterion before running the agent.
- Hold some cases back, so you don’t tune the agent to its own tests.
An LLM as judge
When no exact test is possible, a model can grade outputs, either with a rubric or by comparing two answers. This judge has biases: it tends to prefer longer answers, and sometimes whichever comes first. Swap the order to check, and calibrate it on hand-labeled cases.
Reading the numbers honestly
An agent isn’t deterministic, so replay each case several times. On a small set, a score impresses more than it informs. 14 passes out of 15 is 93%, but the 95% confidence interval runs from about 70% to 99%. A few points of difference between two versions then means nothing.
Observing in production
Record every step: prompt, tool, result, duration, cost. Then turn each incident into an evaluation case. The set becomes the memory of your mistakes, and CI keeps them from coming back.
No number, no improvement.
Worked example: Comparing two versions case by case
Two averages can hide a regression. Comparing the same cases shows what a change fixed and what it broke.
function compare(before, after) {
const fixed = [];
const broken = [];
for (const id of Object.keys(before)) {
if (!before[id] && after[id]) fixed.push(id);
if (before[id] && !after[id]) broken.push(id);
}
return { fixed, broken };
}
const before = { damaged: true, late: false, fraud: true };
const after = { damaged: true, late: true, fraud: false };
compare(before, after);
{ fixed: ['late'], broken: ['fraud'] }
Both versions pass two cases out of three, so the average says nothing changed. In fact the new version now lets a fraud case through.
Try it in the editor
function wilson(passes, n, z = 1.96) { // TODO return { low: 0, high: 0 };}function clearlyBetter(a, b) { // TODO return false;}