AI Engineer Dojo Contents
Chapter 2

The Four Parts of Every Eval

Once you accept that quality is a measurement, you need to decide what to measure and when. This chapter gives you a small mental model that every eval tool and framework follows, so you can recognize the same pieces wherever you work.

Case study

Fathom ships blind for a year

For a whole year, Fathom released changes to its AI feature after an engineer tried a few prompts and decided things "seemed fine." There was no fixed dataset and nothing that produced a number. Quality drifted. When churn jumped, the team had fifty changes to look through and no way to tell which one had hurt.

They set up four things: a dataset of real cases, a harness that ran the system on each one, a scorer that turned each output into a number, and a report broken down by customer segment. They also added a check that blocked releases when the score fell. Within a quarter, "did this change help?" had an answer the team could look up.

Offline and online

Offline evals run before you deploy, on a fixed set of cases, usually in CI. They're fast and repeatable, and they answer "should we ship this change?" Online evals run on live traffic and answer "is it working for real users right now?" Offline covers the cases you thought of in advance. Online is how you find the ones you didn't.

With a reference or without

Some outputs have a known right answer you can compare against, like a refund amount or a date. That's reference-based scoring. Many don't: there's no single correct reply to "can you explain this charge?" For those you judge the output on its own qualities, such as whether it's supported by the source, correctly formatted or polite. That's reference-free scoring, and later chapters cover how to do it well.

The four parts

Any eval you'll meet has the same four pieces. There's a dataset of cases. There's a task, the harness that runs your system on each case. There's a scorer that turns each output into a number or label. And there's a report that rolls those scores up into something you can decide with, broken down by slice and tracked over time. If you can name the four for a system, you understand how its quality is measured. If one is missing, you've found the gap.

A rule of thumb

Do as much checking as you can with cheap, fast, deterministic code, and save the slow and expensive scorers (model judges, human reviewers) for the questions that need them. An eval suite that takes an hour won't get run before every change.

Try it · ~15 minFree · no API key

Build a minimal eval harness

A billing support bot answers 200 generated tickets: balance questions, plan prices, and refunds. Nobody hand-labels the answers. Each correct answer is computed from a written refund policy, and the bot is code you can read, or a real model if you set USE_REAL_API. Run it and decide from the report whether the bot is safe to ship. The key parts are below; the full file is labs/02_eval_harness.py in the repo.

Lab code (key parts) · runs free

POLICY = """Refund policy
1. Duplicate charge: refund the duplicate charge in full.
2. Monthly plan cancelled: refund the unused days, prorated: price x (30 - days used) / 30.
3. Annual plan cancelled within 14 days of purchase: full refund.
4. Annual plan cancelled after 14 days: prorated: price x (365 - days used) / 365.
Round refunds to the cent."""

def correct_answer(t):
    """The answer the POLICY requires. This is the ground truth."""
    if t["kind"] == "balance":
        return f"${t['balance']:.2f}"
    if t["kind"] == "price":
        return f"${PRICES[t['plan']]:.2f}"
    price, used = PRICES[t["plan"]], t["days_used"]
    if t["kind"] == "duplicate":
        return f"${price:.2f}"
    if t["kind"] == "cancel_monthly":
        return f"${price * (30 - used) / 30:.2f}"
    if used <= 14:
        return f"${price:.2f}"
    return f"${price * (365 - used) / 365:.2f}"

def bot_reply(t):
    if USE_REAL_API:
        return real_model_reply(t)
    # A stand-in for a model that read the policy. Read it closely.
    if t["kind"] == "balance":
        return f"Your balance is ${t['balance']:.2f}."
    if t["kind"] == "price":
        return f"The {t['plan']} plan is ${PRICES[t['plan']]:.2f}."
    price, used = PRICES[t["plan"]], t["days_used"]
    if t["kind"] == "duplicate":
        return f"Sorry about that. I've refunded ${price:.2f}."
    if used <= 14:                                   # the 14-day rule...
        return f"You're within 14 days, so I've refunded the full ${price:.2f}."
    days_in_period = 30 if t["plan"] == "monthly" else 365
    return f"I've refunded ${price * (days_in_period - used) / days_in_period:.2f} for the unused time."

def score(t, reply):
    want = float(correct_answer(t)[1:])
    got = first_amount(reply)
    ok = got is not None and abs(got - want) < 0.01
    dollars_wrong = 0.0 if ok or t["intent"] != "refund" else (got or 0.0) - want
    return ok, dollars_wrong
Worked solution
overall accuracy   0.96  (192 of 200)
  account          1.00  (89 of 89)
  plan             1.00  (69 of 69)
  refund           0.81  (34 of 42)
    duplicate      1.00  (14 of 14)
    cancel_monthly 0.50  (8 of 16)
    cancel_annual  1.00  (12 of 12)

money: over-refunded $59.93, under-refunded $0.00 on 200 tickets
       about $2,996 refunded wrongly per 10,000 tickets

failures grouped by pattern:
    8 x cancel_monthly, used <= 14 days
        e.g. "I'm cancelling my monthly plan after 5 days. What's my refund?"
        bot said: You're within 14 days, so I've refunded the full $29.00.
        policy says: $24.17

verdict: HOLD (limit $1,000 wrong per 10,000 tickets)

What to look at, in order. The top line says 96%, which on its own looks ready to ship. The per-intent lines show where the misses are: account and plan questions are perfect, refunds are at 81%. The per-type lines narrow it further: monthly cancellations are right only half the time.

What the misses have in common. The failure groups answer that without any guessing: all eight misses are monthly cancellations within the first 14 days. The example shows why. The bot applied the annual plan's "full refund within 14 days" rule to a monthly plan, so a customer who used 5 days got $29.00 back instead of $24.17. Open bot_reply() and you'll find the line: the 14-day check runs before the code looks at which plan it is. A real model that misreads the policy tends to make the same kind of mistake, and you can check that by running with USE_REAL_API = True.

What it costs, and the decision. The money line turns the misses into dollars: about $3,000 refunded wrongly per 10,000 tickets. You set the limit from your own business (here, $1,000), and the verdict follows from it: hold the release, fix the rule order, run the eval again.

None of those conclusions were typed into the lab. The tickets were generated, the correct answers came from the policy, and the report surfaced the pattern. That's the job an eval does, and the rest of the book uses the same four parts (dataset, task, scorer, report) with harder data and smarter scorers.

Running case · Meridian × Remi

Meridian sketches Remi's four parts on a whiteboard. The dataset will be real billing questions from support logs. The task runs Remi on each one. Refund amounts get a deterministic check, and tone gets a model judge they'll need to validate. The report shows accuracy by intent. They agree to gate every Remi change on this eval before launch and to put a live dashboard up after it.

If you're starting out

The four parts make a good answer to one of the most common opening questions in interviews: "How would you evaluate X?" Walk through them in order. What cases would you collect, how would you run the system, how would you score each output, and how would you report the results? Candidates who answer this way sound organized even when they're unsure about details.

The lab above is also the start of a portfolio project. By Chapter 12 you'll have grown it into a small framework you can put on GitHub.

If you run the team

You don't need to build any of this, but you should be able to recognize a setup that works. A healthy one has two halves: an automated check that blocks a release when quality drops, and a dashboard that tracks quality on live traffic. The team should be able to name its dataset, its scorer and how results are split up.

Be concerned if you hear "we test by hand before each release," or if quality is whatever the last person to look at it felt. Suppose two teams use the same model. One ships when things look good to an engineer; the other has a gate and a dashboard. Only the second team can tell you whether its last six months of work made things better. Paying for the gate and the dashboard early is what makes the rest of the roadmap believable.

Quiz · Chapter 2

  1. What are offline evals mainly for?
  2. "Reference-free" scoring means:
  3. Which list names the four parts of any eval?
  4. In the lab, the bot scored 96% overall. What showed that it wasn't ready?
  5. Which heuristic does the chapter recommend for designing an eval suite?
Back Continue

The AI Evaluation Engineer · aiengineerdojo.com