ealeng
Menu

Episode 13 of 25 · Evals

A test gate you can trust

Evals in CI: an exam of test questions runs on every change, and a gate blocks the change if a score drops past its allowance or a critical question fails. How to read a red, break the assistant on purpose, measure run-to-run wobble, and set the allowance from it.

Lab 05 · Evaluation Systems9 min read
Video transcript8:10

0:00Cold open

0:01

I built a help-center assistant. Customers type a question, and it answers from our help articles.

0:08

Before any change to it goes live, an automatic test runs. If the answers got worse, it blocks the change. That’s called a test gate.

0:17

Day one, I ran it on the assistant exactly as it was. Nothing had changed. And it said: blocked.

0:25

So is the gate wrong, or is the assistant? And when it says pass, can I believe it?

0:32Intro

0:33

Hands-on AI engineering: testing AI automatically, part one.

0:38

Today: what a test gate checks, and how to tell a real failure from random wobble.

0:4501 · The assistant

0:46

I’m Chloe, an AI engineer. First, how the assistant answers. Three steps.

0:52

Step one, search. It finds the three paragraphs in the help center that best match the question.

0:59

Step two. It hands those paragraphs to an AI model, with instructions: answer only from them. Name the article you used, in square brackets. And if the answer isn’t there, say “I don’t know”.

1:13

Step three, the answer. “How long does an invite link last?” gets: invitations expire after seven days. And the tag, teams: the article it came from.

1:24

The help center and the questions are made up for this video. Every answer, and every score, is real.

1:3202 · Why test

1:33

Why test at all? Small changes, like new wording in the instructions, a new model, or more paragraphs, can quietly break answers that used to be right.

1:44

Nobody rereads every answer after every change. So I wrote an exam, before I ran anything. Twenty-one questions.

1:52

Sixteen have an answer in the help center. For each, I noted the paragraph that holds it. That’s the answer key.

2:00

Five have no answer there, like “Do you offer a student discount?”. The right reply is “I don’t know”. Not a made-up discount.

2:1003 · Grading

2:11

Here’s how one question is graded. “Why is my account locked for fifteen minutes?”

2:17

Did search find the right paragraph? Yes. Did it rank it first? Yes.

2:22

Does the answer stick to what the paragraphs say? To check that, a bigger AI model reads both, and judges. Yes.

2:31

Does the answer name its source? No. The answer is right, but the tag is missing. So this question fails a check.

2:40

Each score is the share of questions that pass one check. On day one, nine of sixteen answers named their source.

2:4904 · The gate

2:50

Now the gate. It compares the new scores with the scores saved from an earlier run. That saved run is called the baseline.

2:59

Think of a bathroom scale. You don’t compare yourself with a perfect number. You compare with last week.

3:06

And a scale wobbles a little, so you don’t panic over a few grams. The gate forgives a small drop too, for the answer scores. I’ll call it the allowance; engineers say tolerance. Here: out of sixteen answers, one miss is fine. Two is not.

3:22

Search gets no allowance. It gives the same results every time, so any drop is real.

3:29

And two questions are critical: the student discount, and the refund. They must pass every check, every run. One miss blocks the change.

3:3905 · Read the red

3:40

Back to day one. There was no earlier run, so day one was compared with itself, and no score could drop. It was blocked because a critical question failed.

3:51

In all, eight of twenty-one questions failed some check. Before touching anything, I read every one. They came in three kinds.

4:00

Four gave the right answer, but no source tag. Like the locked account.

4:05

Two said “I don’t know”, though the answer was right there. “Can I pay without a credit card?” had the payment paragraph ranked first. It lists PayPal.

4:15

And two where search missed. One is the critical one: “I was charged by mistake, can I get my money back?”. The refund paragraph came fourth, so it was never sent. The assistant said “I don’t know”. The gate was right to block.

4:3106 · A fire drill

4:33

Next: does the gate catch a real break? Like a fire drill, I broke the assistant on purpose. I deleted the “say I don’t know” instruction.

4:43

The five questions with no answer stopped getting the fixed sentence. Most got polite replies in the model’s own words. But “Can I pay with Apple Pay?” got a flat “No”. The help center lists the cards it takes, and never mentions Apple Pay either way.

4:59

The test only counts the exact sentence, so a program can tell “no answer” from an answer. That score fell from five of five, to zero. Blocked.

5:09

Now, a guess: did any score go up?

5:15

One did. Answers naming a source rose, from nine of sixteen to fifteen. Without that rule, it tagged almost every answer. One score looked better, while the assistant got worse at saying “I don’t know”. That’s why the gate watches all of them.

5:3107 · Wobble

5:32

Then the opposite test: will it block a change that did nothing? I ran the exact same code five times.

5:40

Search gave the same results all five times. But the AI model doesn’t write the same answer twice. Like a person asked the same question twice: same meaning, different words.

5:53

“Why is my account locked?” Run one: no tag. Run two: nearly the same answer, one more sentence, and the tag.

6:01

Three questions flip like that, from run to run. So the source score wobbled, by two questions.

6:08

Remember the allowance: one question. This wobble is two.

6:13

Say I had saved the lucky run as the baseline: eleven. Same code, next run: nine. A drop of two, and only one is allowed. Blocked, for nothing.

6:25

My scale wobbled more than the allowance I gave it. So first, measure the wobble, by rerunning code you haven’t changed. Then set the allowance from it.

6:3508 · The fix

6:37

Now a fix, for the refund question. Its paragraph came fourth. So I send four paragraphs instead of three.

6:45

Every answer now sees an extra paragraph, so I reran the whole exam, five times.

6:51

Answers naming their source got steadier: fourteen of sixteen, every time. The three that flipped stopped flipping, though the judge still wobbled once.

7:01

And the refund question gets the right answer: a full refund within fourteen days of your first purchase.

7:10

But the gate still says blocked. The refund paragraph is found, but ranked fourth. And a critical question must pass every check, including “ranked first”.

7:20

That red is fair. The answer only arrives because we now send a fourth paragraph. One slip in search, and it’s gone. The next fix is search itself.

7:3109 · What I do now

7:33

So now, I read every red before I touch the gate.

7:37

I break the assistant on purpose, to see the gate block. And I rerun unchanged code, to see it pass.

7:45

And I set each allowance from the wobble I measured, and save the baseline from the typical run, not the lucky one. Next time: which changes to the instructions really help, measured on questions I didn’t tune on.Chloe: Read the red. Measure the wobble.

7:59Summary

8:00

Read the red. Break it on purpose. Measure the wobble.

8:04

Then trust what the gate says.Chloe: Thanks! 😄

Key takeaways

  • ✓A test gate grades every change on an exam (here 21 questions: 16 with an answer key, 5 where the right reply is "I don't know"), one check at a time, and compares each score with a saved baseline. Search gets no allowance; answer scores get 0.10; two critical questions must pass every check.
  • ✓Day one, on unchanged code, it was blocked: 8 of 21 questions failed a check, and one was the critical refund question, whose paragraph ranked 4th and was never sent. Read every red before touching the gate: 4 right answers without a source tag, 2 "I don't know" with the answer in hand, 2 search misses.
  • ✓Break it on purpose. Deleting the "say I don't know" rule took that score from 5 of 5 to 0 and blocked the change, while answers naming a source went up from 9 to 15 of 16. One score can improve while the assistant gets worse, so the gate watches all of them.
  • ✓Rerun unchanged code. Over five runs search was identical, but answers naming a source wobbled from 9 to 11 of 16 (0.125), more than the 0.10 allowance: with the lucky run saved as the baseline, the same code is blocked. Measure the wobble first, then set the allowance, and save a typical run as the baseline.
  • ✓Sending 4 paragraphs instead of 3 fixed the refund answer and steadied the tags at 14 of 16 in all five runs. The gate stayed red, fairly: the refund paragraph still ranks 4th, one slip from gone. The next fix is search.

Every change to an AI assistant can quietly break answers that used to be right: new wording in the prompt, a new model, more paragraphs sent. Nobody rereads every answer after every change, so you write an exam of test questions and have it run automatically, before a change goes live. A test gate grades the answers and blocks the change if they got worse. This chapter covers what a gate checks, why it was red on day one, how to see that it catches a real break, and how to tell a real failure from the model's random wobble.

The code is Lab 05's eval suite, run unchanged: its RAG pipeline (gpt-5.4-nano), its checks, its gpt-5.4-mini judge and its baseline gate (evals/gate.py), all wrapped in run_evals.py, which CI runs on every pull request. The help center and the 21 questions are made up for the video and written before any run; the lab's own docs and 18 questions work the same way. Every answer and judge verdict is a real recorded run: the first run, five reruns of unchanged code, two changes that break it on purpose, and five runs of the fix.

How the assistant answers

  1. Search: find the 3 paragraphs in the help center that best match the question.
  2. Answer: hand them to the model with instructions: answer only from them, name the article in square brackets, and if the answer isn't there, reply exactly "I don't know based on the provided documents."
  3. The reply: "How long does an invite link last?" gets invitations expire after seven days, tagged [teams].

An exam, graded one check at a time

21 questions, written before anything ran. 16 have an answer in the help center, and for each the answer key names the paragraph that holds it. 5 have none, like "Do you offer a student discount?", where the right reply is "I don't know", not a made-up discount.

Each answerable question is checked on search and on the answer separately, so a failure points at the part that broke:

Check Graded by Allowed drop
Search found the right paragraph (recall) Code, against the answer key None
Search ranked it first (hit@1), and how high (MRR) Code None
The answer names a real source (citations) Code 0.10
The answer sticks to the paragraphs (faithfulness) gpt-5.4-mini as a judge 0.10
"I don't know" on the 5 with no answer (refusals) Code: the exact sentence 0.10

"Why is my account locked for 15 minutes?": found ✓, first ✓, sticks to it ✓, names its source ✗. A score is the share of questions passing one check: on day one, 9 of 16 answers named their source.

The gate

The gate compares each new score with the baseline, the scores saved from an earlier accepted run. Think of a bathroom scale: you compare with last week, not with a perfect number. A fixed target ("faithfulness ≥ 0.9") either blocks every change or lets real drops through.

  • Allowance (tolerance): a scale wobbles, so the answer scores may dip by up to 0.10. With 16 answers, one miss (0.0625) is fine; two (0.125) is not.
  • Search gets none. It returns the same results every run, so any drop is real.
  • Critical questions must pass every check, every run, however good the scores are. Here: the student discount and the refund.

Updating the baseline is a deliberate step a person reviews (--update-baseline, committed in the pull request). CI never does it on its own.

Try it · Run the gateRecorded runs · gpt-5.4-nano, judged by gpt-5.4-mini · the lab's gate rule

Pick a saved baseline and a new run. The gate blocks if any score drops below the baseline by more than its allowance, or if a critical question fails any check. Search gets no allowance.

Baseline (saved earlier)

Same code

New run

Same code
Broken on purpose
The fix: 4 paragraphs

Allowance on answer scores

One question of 16 = 0.0625
ScoreBaselineNewAllowed dropGate
search: ranked it first12 of 1612 of 16noneok
search: found the right paragraph14 of 1614 of 16noneok
search: how high it ranked (0 to 1)0.810.81noneok
answer names its source11 of 169 of 160.10✕ dropped too far
answer sticks to the paragraphs (judge)16 of 1616 of 160.10ok
says “I don’t know” when it should5 of 55 of 50.10ok
Scores dropped too far
1
Critical questions failing
refund
Gate
Blocked

Refund (“I was charged by mistake, can I get my money back?”) fails in every recorded run: its paragraph ranks 4th, so with 3 paragraphs it isn't sent, and with 4 it's sent but not first. Critical questions must pass every check.

Same code, five runs: did the answer name its source?

Will the app work on a plane?✓✕✕✓✓
If I quit, how long before my stuff is gone?✓✓✓✓✕
Why is my account locked for 15 minutes?✕✓✕✓✓

Answers naming their source: 10, 10, 9, 11, 10 of 16 across the five unchanged runs. A spread of 2 questions (0.125), more than the 0.10 allowance.

Read the red

On day one there was no earlier run, so the run was compared with itself and nothing could drop. It was blocked because a critical question failed. In all, 8 of 21 questions failed a check, in three kinds:

Kind Questions What happened
Right answer, no source tag 4 Like the locked account
"I don't know" with the answer in hand 2 "Can I pay without a credit card?" had the payment paragraph, which lists PayPal, ranked first
Search missed it 2 One is critical: "I was charged by mistake, can I get my money back?" Its paragraph ranked 4th, so with 3 sent it never reached the model

The gate was right to block. Read every failure before touching the gate, the prompt or the allowance.

Break it on purpose

A fire drill: does the gate catch a real break? Deleting the "say I don't know" instruction (one of the lab's Exercise 2 changes):

Score Day one Rule deleted Gate
"I don't know" when it should 5 of 5 0 of 5 Dropped too far
Sticks to the paragraphs (judge) 16 of 16 12 of 16 Dropped too far
Names its source 9 of 16 15 of 16 Improved

Without the rule, most unanswerable questions got polite replies in the model's own words, and "Can I pay with Apple Pay?" got a flat "No", though the help center never mentions Apple Pay either way. The check counts only the exact sentence, so a program can tell "no answer" from an answer. The gate blocked it, and the student-discount question, which is critical, failed too.

Meanwhile the source-tag score went up: without that rule, the model tagged almost every answer. One score looked better while the assistant got worse at saying "I don't know". That's why the gate watches every score at once. (One of the judge's four new flags was itself wrong: it called PayPal unsupported, but the billing paragraph lists it. The video leaves that drop out for that reason.)

The lab's other break, sending 1 paragraph instead of 3, dropped search's "found it" from 14 to 12 of 16 and was blocked too (recorded, not shown in the video).

Measure the wobble

The opposite test: will it block a change that did nothing? The same code, five times, without the lab's saved responses (EVAL_NO_CACHE=1, the lab's Exercise 4):

Score Runs 1–5
Search: found / first / MRR Identical every run
Sticks to the paragraphs 16 of 16, every run
"I don't know" when it should 5 of 5, every run
Names its source 10, 10, 9, 11, 10 of 16

The model doesn't write the same answer twice. "Why is my account locked?" got no tag in run 1, and in run 2 nearly the same answer, one sentence longer, with the tag. Three questions flipped like that, so the score wobbled by two questions (0.125), more than the 0.10 allowance. Save the lucky run (11) as the baseline, and the next run of the same code (9) is a drop of two: blocked, for nothing. The lab's own gate.compare marks it "regressed".

So first measure the wobble by rerunning code you haven't changed, then set the allowance from it, and save a typical run as the baseline, not the lucky one. (In normal CI runs the lab replays saved responses for unchanged inputs, so only the parts a change touched can wobble.)

The fix, and a red that stays

The refund paragraph ranked 4th, so the fix was to send 4 paragraphs. Every answer now sees an extra paragraph, so the whole exam was rerun, five times:

Score 3 paragraphs (5 runs) 4 paragraphs (5 runs)
Names its source 9–11 of 16 14 of 16, every run
Search found the right paragraph 14 of 16 15 of 16
Sticks to the paragraphs 16 of 16 16 of 16, once 15

The three questions that flipped stopped flipping in these runs, though the judge wobbled once. The refund question now gets the right answer: a full refund within 14 days of your first purchase.

The gate still says blocked. The refund paragraph is found but ranked 4th, and a critical question must pass every check, including "ranked first". That red is fair: the answer only arrives because a 4th paragraph is sent, and one slip in search would lose it again. The next fix is search itself.

The checklist

  1. Read every red before touching the gate, the prompt or the allowance.
  2. Break it on purpose and check the gate blocks; watch every score, since one can rise while the assistant gets worse.
  3. Rerun unchanged code and check the gate passes.
  4. Set each allowance from the wobble you measured, and none for scores that don't wobble, like search.
  5. Save a typical run as the baseline, update it only on purpose, and keep critical questions for what must never break.

Practice: the gate

Write the gate's comparison, its critical-question rule and its decision, then measure the wobble. The tests use the scores and checks recorded in the video: day one blocked by refund alone, the fire drill's "improved" source tags, the lucky baseline that blocks unchanged code at 0.10 but not at 0.15, and the 4-paragraph fix that improves two scores and is still blocked.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
gate_practice.py66 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.What does the gate compare each new score with?

  2. 2.Why does search get no allowance, while answer scores get 0.10?

  3. 3.Day one ran on unchanged code, compared with itself, so no score could drop. Why was it blocked?

  4. 4.With the "say I don't know" rule deleted, answers naming a source rose from 9 to 15 of 16. Did the assistant get better?

  5. 5.Five runs of unchanged code tagged 10, 10, 9, 11 and 10 of 16 answers. The allowance is 0.10 (one question is 0.0625). What can happen?

  6. 6.After sending 4 paragraphs, the refund question gets the right answer, but the gate still says blocked. Is that a false alarm?

Check your understanding

Try each question before opening its answer.

What does a test gate do?

On every change it runs an exam of test questions, scores the answers one check at a time, and compares each score with a saved baseline. It blocks the change if a score drops past its allowance or a critical question fails any check.

Why compare with a baseline instead of a fixed target?

A fixed target either blocks every change (if you're below it today) or lets real drops through (if you're far above it). The question a change check asks is "did this make things worse than before?"

Why was the gate red on day one?

Nothing could drop, since the run was its own baseline. But the critical refund question failed: its paragraph ranked 4th, was never sent, and the assistant said "I don't know". The gate was right.

How can a score improve while the assistant gets worse?

With the "say I don't know" rule deleted, refusals fell from 5 of 5 to 0, and source tags rose from 9 to 15 of 16 because the model tagged almost everything. The gate watches all the scores, so it still blocked.

How do you set the allowance?

Rerun unchanged code several times and measure how far each score moves. Here source tags moved between 9 and 11 of 16, a wobble of 0.125, bigger than the 0.10 allowance, so a lucky baseline would block unchanged code. Set the allowance from the wobble, give none to scores that don't wobble, and save a typical run as the baseline.

Why is a red that stays after a fix sometimes right?

Sending 4 paragraphs made the refund answer right, but its paragraph still ranks 4th, so the critical "ranked first" check fails. The answer depends on a 4th paragraph being sent; one slip in search and it's gone. The real fix is in search.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.