Core path - 5 of 8
Exercises: make the learning stick
Reading code teaches you less than predicting what it will do and then checking. This file turns each section of the README into a few quick active-recall prompts.
How to use it: work the section first, then come back. Commit to an answer before you run or reveal. The prediction is where the learning happens, even (especially) when you're wrong. Answers are hidden behind ▸ toggles.
Examples 01-04 are (offline): no API call, no cost. The rest make small, cheap calls; example 09 makes the most.
Section 2: The anatomy of an eval (offline)
Recall. Name the four parts of every eval, and which one this whole repo argues is the hardest.
▸ Answer
dataset → task → scorer → report. The dataset is the hard part: clever scorers and metrics can't rescue an eval built on unrepresentative or mislabeled examples.
Do. In examples/01_anatomy.py, the "task" is a rule-based classifier. Add a
word to the GOOD/BAD sets and rerun. Did accuracy move? Why is a keyword
baseline doomed to miss the "hard" rows?
▸ Answer
Because sarcasm and mixed sentiment ("Not as bad as the reviews said") have no reliable keyword signal; the words point the wrong way. That ceiling is the motivation for an LLM (example 05) and for measuring at all.
Section 3: Code-based scorers (offline)
Predict. Output is "Positive!" and the expected label is "positive". Does
exact_match pass? Does contains_expected?
▸ Answer
exact_match fails (the ! and capital P make the strings unequal);
contains_expected passes (case-insensitive substring). Choosing the scorer is
choosing what "correct" means, and why example 05's task normalizes the label
before scoring.
Section 4: Metrics (offline)
Recall. A spam filter labels every email as spam. What are its precision and recall for the "spam" class? Why does that show accuracy alone can lie?
▸ Answer
Recall is 100% (it caught all real spam) but precision is terrible (most flagged emails weren't spam). On an inbox that's mostly ham, plain accuracy could even look bad, and on mostly-spam data, a "flag everything" model could post high accuracy while being useless. Precision/recall/F1 expose what one number hides.
Do. In examples/04_metrics.py, change candidate so its scores barely beat
baseline. Does compare() still call it a real improvement? What does that
teach about "+2%" claims?
▸ Answer
Once the gap shrinks into the margin of error, likely_real flips to False. A
small improvement that's inside the noise isn't a result you can trust; you'd
need more data or runs to separate it from chance.
Section 5: Evaluating an LLM classifier
Predict, then run. Will examples/05_classify_eval.py score 100%? Where will
its errors cluster, and are those errors the model's fault?
▸ Answer
Almost certainly not 100%. Errors cluster on the "hard" rows (sarcasm, mixed signals). Some of those labels are genuinely debatable, so a "wrong" answer can be a disagreement with a shaky label, not a model failure. A good eval surfaces that rather than hiding it.
Section 6: LLM-as-judge
Recall. When should you reach for an LLM judge instead of a code scorer? Name one risk you take on when you do.
▸ Answer
Use a judge when correctness is about meaning and code can't check it (is this summary faithful? is this answer helpful?). The risk: the judge is itself a model with biases and costs a call per grade, so it must be calibrated, not trusted blindly.
Section 7: Pairwise win-rate
Predict. examples/07_pairwise.py compares a one-word prompt (A) against a
full-sentence prompt (B) on the rubric "more helpful." Who wins? Now imagine the
rubric is "most concise". Who wins then?
▸ Answer
B (helpful) under the helpfulness rubric; A (concise) under the conciseness rubric. Same answers, opposite winners, because the rubric defines "better." The rubric is the most important sentence in the eval.
Section 8: Judge bias
Recall. What is position bias, and what's the one-line fix used in
examples/08_judge_bias.py?
▸ Answer
Position bias is a judge favouring whichever answer is shown first. The fix: judge each pair in both orders and only count a win if the same answer wins both ways (otherwise call it a tie). This both detects and neutralizes the bias.
Section 9: Nondeterminism & statistics
Predict, then run. You run the same eval twice at temperature 0.7. Will you get the same pass rate? What does that mean for trusting a single eval number?
▸ Answer
Probably not; the score wobbles run to run. So one number is a point estimate, not
the truth. Report a mean with a confidence interval, and to claim system B beats A,
run enough times that the difference clears the noise (compare()).
Going further: three more kinds of eval (offline)
Recall (trajectory, 10). The "lucky" agent scores 100% on the final answer but
0% on tool use. What does that reveal, and why isn't answer-accuracy enough for an
agent?
▸ Answer
It guessed. It reached the right answer without doing the work (never called the required tool). For an agent, a right answer can hide a broken or unsafe process, so you grade the trajectory (steps + answer) on several axes: correctness, required tool used, no forbidden tools, within the step budget.
Predict (annotation, 11). Observed agreement is 83% but Cohen's kappa is only
0.75. Why is kappa lower, and which number should you trust?
▸ Answer
Some of that 83% is agreement you'd expect by chance (especially with imbalanced classes). Kappa subtracts the chance baseline, so it's the honest reliability number. A low kappa means your gold labels are noisy, so fix the guidelines and re-annotate before trusting any score built on them.
Recall (online eval, 12). Variant B beats A on satisfaction with a significant
gap. Name the two conditions that must both hold before you ship B.
▸ Answer
(1) The difference must clear the margin of error (a small sample can't prove a real gap), and (2) no guardrail metric (latency, refusal rate, cost) may have regressed. A headline win that quietly breaks a guardrail is still a regression.
Predict (faithfulness, 13). A RAG answer says "the reset link is valid for 30
minutes", and that happens to be true, but the retrieved context never mentions an
expiry. Does it pass a correctness check? A faithfulness check? Why does the gap
matter?
▸ Answer
It can pass correctness (it's true) but fails faithfulness (the context didn't support it; the model made it up and got lucky). That's the danger: an ungrounded answer that happens to be right today teaches you to trust a system that's actually hallucinating. Faithfulness needs no gold answer, only the context, so it catches exactly what correctness misses. The fix at answer time is the grounded prompt: answer only from context, decline when it's silent.
Capstone: eval_run.py
Do. Save a baseline (--save baseline.run.json), then run again with
--baseline baseline.run.json. The diff says "within noise" even though the
numbers differ slightly. Why is that the right answer?
▸ Answer
Because run-to-run variation makes small deltas meaningless. compare() only flags
a change as real when it exceeds the margin of error, so the tool refuses to cry
"regression!" (or "improvement!") over noise. That restraint is what makes an
automated gate trustworthy.
Stretch. Wire secrun python hands_on/eval_run.py sentiment --fail-under 0.7 into a
git pre-commit hook or CI step. Now a prompt change that tanks quality fails the
build: evals as a safety net, not a one-off.
Where to take it next
Invent your own. Take a task you actually care about, write ten honest examples, pick a scorer, and get a number. The first time an eval stops you from shipping a "better" prompt that was actually worse, the whole discipline clicks.