Evaluation first
Lesson 3 of 10
3Evaluation first
The big pictureBefore you change anything in your system, the model, the template or the chunking, you need a number to compare against. Fifty real questions with their right answers, run before and after every change. Without this every "improvement" is a feeling, and many improvements by feeling make the system worse.
What the picture shows: At the centre sits a golden set that does not change, and around it a cycle that never ends: one change, a run, a number, a comparison. Keeping the questions fixed is what makes two numbers comparable, which is why you never delete a question because your system failed it.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
Before improving a dish you need someone to taste it the same way every time, or you will not know whether it got better or you imagine it. Evaluation first means writing fifty questions whose right answers you know before any change, then scoring every new version on them.
A golden set of questions with reference answers and hard cases, then automatic scoring with an LLM-as-judge on written criteria, or a pairwise comparison of two versions. You calibrate the judge on a sample a human scores, and record the number before and after every change.
Before You Start
📏Metrics
You know a single number on fixed data is what decides between two models, not an impression.
🪟Context engineering
You saw that template and ordering changes are "measured on the golden set", and this lesson builds that set.
The Core Idea
Before you change anything in your system you need a number to compare against. In this lesson you build a golden set, learn where its questions come from, calibrate a model judge, then expose its bias in pairwise comparison.
1The golden set
A golden set is real questions with what their right answers must contain, fixed across experiments. The simplest measure: does the answer contain the required phrases?
Look at the picture: two versions of the system on the same five questions, and each version now has a number.
golden = [
{"q": "How many annual leave days?", "must": ["21"]},
{"q": "Who approves sick leave?", "must": ["manager"]},
{"q": "Can leave be carried over?", "must": ["10 days", "next year"]},
{"q": "Notice period for annual leave?", "must": ["two weeks"]},
{"q": "What is the parking fee?", "must": ["not covered"]},
]
v1 = ["You get 21 days.", "HR approves it.", "Yes, up to 10 days into next year.",
"Two weeks notice.", "The fee is 50 riyals."]
v2 = ["21 working days.", "Your direct manager.", "Up to 10 days, used by March next year.",
"At least two weeks before.", "Parking is not covered by these documents."]
def score(answers):
hits = [all(m.lower() in a.lower() for m in g["must"]) for g, a in zip(golden, answers)]
return sum(hits), hits
for name, answers in [("v1", v1), ("v2", v2)]:
n, hits = score(answers)
print(f"{name}: {n}/{len(golden)} {['ok' if h else 'FAIL' for h in hits]}")
The first version got wrong who approves sick leave and invented a parking fee that does not exist. The second passed all five. The fifth question matters: it is outside the documents, and the right answer is "not covered". Every golden set needs questions like this, or it rewards a system that makes things up.
2Where the questions come from
A good set has questions from inside the documents and questions from outside them. Look at the picture: of fifty questions, ten are outside the documents, and their right answer is that the information is not there.
From real users, not your imagination. Collect questions from email, tickets or a session with five people, and write each right answer with whoever holds the knowledge. Fifty varied questions beat five hundred similar ones.
3A model as judge
Text matching is enough for short facts, and not when the right answer can be phrased many ways. That is where a language model grades the answer against a written rubric of yes-or-no questions.
But the judge needs calibrating. Look at the picture: twenty answers graded by a person and by the judge, who agreed on 17 and differed on 3.
Twenty answers graded by a person (1 good, 0 bad) and by the model.
from sklearn.metrics import cohen_kappa_score
human = [1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0]
judge = [1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1]
agree = sum(h == j for h, j in zip(human, judge)) / len(human)
print(f"raw agreement: {agree:.0%}")
print(f"Cohen's kappa: {cohen_kappa_score(human, judge):.2f}")
fp = sum(1 for h, j in zip(human, judge) if j == 1 and h == 0)
print("judge said 'good' where the human said 'bad':", fp, "times")
85% agreement looks excellent, but kappa of 0.67 is more precise because it removes agreement that happens by chance. More important: all three errors go one way, the judge said "good" to bad answers. A lenient judge makes you think your system is better than it is. Read those three cases yourself and adjust the rubric to catch them.
Calibrate the judge on 30 to 50 answers you graded yourself before you trust it. A kappa above about 0.6 is a good start; more important is knowing where it errs.
4Pairwise comparison and position bias
Instead of "score from 1 to 10", ask the judge: which of the two answers is better? This is more stable, but the judge leans towards whichever answer comes first when the two are close.
Look at the picture: quality is random for both sides, so A should win about 100 times out of 200. Yet it wins 139 times when always shown first.
A judge that picks the better answer when the gap is clear, and leans towards the first one when the two are close. This bias is documented in real models.
import random
random.seed(3)
def biased_judge(first, second):
"""Prefers the better answer, but also prefers whatever is shown first."""
if abs(first["quality"] - second["quality"]) < 0.15:
return "first" if random.random() < 0.75 else "second"
return "first" if first["quality"] > second["quality"] else "second"
pairs = [({"name": "A", "quality": random.uniform(0.4, 0.9)},
{"name": "B", "quality": random.uniform(0.4, 0.9)}) for _ in range(200)]
one_order = sum(biased_judge(a, b) == "first" for a, b in pairs)
consistent = ties = 0
for a, b in pairs:
r1 = biased_judge(a, b) == "first" # did A win when shown first?
r2 = biased_judge(b, a) == "second" # did A win when shown second?
if r1 == r2:
consistent += 1
else:
ties += 1
print("A wins when always shown first:", one_order, "of 200")
print("verdicts that survive swapping the order:", consistent)
print("verdicts that flip with the order (count as ties):", ties)
Quality is random for both sides, so A should win about half the time. But when always shown first it wins 139 times out of 200. When we judge twice in both orders, 72 verdicts flip with the order, and we count those as ties. The lesson: never compare two versions by always showing the new one first; you will always find it "better".
How It Works, Step by Step: how to build a golden set and use it
Collect fifty real questions from users, ten of them outside the documents, and write each right answer with whoever holds the knowledge.
Grade short facts by text matching, and answers that can be phrased many ways with a model judge using a yes-or-no rubric.
Calibrate the judge on 30 to 50 answers you graded yourself, and read the cases where you disagreed.
Run every version on the same questions, and record its number and the questions that changed in the experiment table.
Worked example
A golden set needs no special tools. A file with a question and what the right answer must contain. We start with the simplest measure: does the answer contain the required phrases?
Try it without code
Myth vs Reality
| Myth | Reality |
|---|---|
| 85% agreement with people means an excellent judge. | Part of agreement happens by chance. In the example kappa was 0.67, and all three errors went one way: a lenient judge. |
| Five hundred similar questions beat fifty varied ones. | Similar questions measure the same thing many times. Fifty varied questions from real users reveal more. |
| One more question out of 50 is enough to adopt the new version. | A one-question gap may be chance. Look at which questions changed, and run each version two or three times before deciding. |
Try it yourself
Why does a golden set need questions whose answer is not in the documents?
A system that confidently answers everything would score perfectly on a set without such questions, then invent answers for your users. This is the most dangerous failure of RAG systems.
How do you expose position bias in a judge comparing two answers?
A verdict that survives swapping is about the content. One that flips is about the position.
Quick review cards
Check Your Understanding
The new version scored 41/50 and the old 40/50. Do you adopt the new one?
Not yet. A one-question difference may be chance, especially if answers vary between runs. Look at which questions changed: if the new one passed three and failed two that used to pass, it may have broken something. Run each two or three times, decide by the stable difference, and by cost and speed too.
In the two-version example, what mistake did v1 make on the parking fee question?
It invented a fee of 50 riyals that does not exist in the documents. The right answer is "not covered", which is why a golden set needs out-of-scope questions.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs1
Done with this lesson?
Sign in to save your progress.