Metrics & Scorers
A scorer turns an output into a number or label. The art is matching the scorer to the task — and knowing each one's blind spots, out loud.
The families of scorers
- Deterministic / rule-based — exact match, regex, JSON-schema validity, "does it cite a source," code that compiles, math that equals the answer. For code and math, execution-based scoring (run it, check the result) is the gold standard. Cheap and perfectly reliable — use wherever the task allows.
- Statistical overlap —
BLEU,ROUGEmeasure n-gram overlap with a reference. Cheap but weak: they reward surface words and punish valid paraphrases. Coarse signals, never quality verdicts. - Semantic similarity — embed output and reference, take cosine. Captures meaning better than n-grams, but tells you "about the same thing," not "correct."
- Model-graded (LLM-as-judge) — the flexible, dominant approach for open-ended quality. Its own chapter (5).
Many "LLM tasks" are secretly classification
Routing, intent detection, safety filtering, extraction correctness — the old vocabulary applies and interviewers expect it. Precision: of what I flagged, how much was right. Recall: of what I should have flagged, how much I caught. F1: their harmonic balance. Know which to optimize: a safety filter cares about recall (missing unsafe content is the costly error); a "delete this email" action cares about precision.
There is no universal metric. The scorer is a design decision per task, and every scorer encodes assumptions. Your job is to pick one whose blind spots don't matter for the decision you're making — and to name those blind spots.
Score a safety filter — by hand, no libraries
You'll compute precision, recall, and F1 for a safety filter from its predictions vs. ground truth (pure stdlib, free). The question to answer: for a safety filter, is this filter's failure mode acceptable — and which number tells you?
Lab code — runs free
import json
def exact_match(out, ref):
return float(out.strip() == ref.strip())
def valid_json(out, _ref):
try:
json.loads(out); return 1.0
except ValueError:
return 0.0
# Safety filter: 1 = flagged unsafe. Compare predictions to ground truth.
y_true = [1, 1, 1, 0, 0, 0, 0, 0, 1, 1] # what was actually unsafe
y_pred = [1, 1, 0, 0, 0, 1, 0, 0, 1, 0] # what the filter flagged
tp = sum(t == 1 and p == 1 for t, p in zip(y_true, y_pred))
fp = sum(t == 0 and p == 1 for t, p in zip(y_true, y_pred))
fn = sum(t == 1 and p == 0 for t, p in zip(y_true, y_pred))
precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * precision * recall / (precision + recall)
print(f"precision={precision:.2f} recall={recall:.2f} f1={f1:.2f}")
precision=0.75 recall=0.60 f1=0.67
Precision 0.75 says three of four flags were right. But recall 0.60 is the number that matters for safety: the filter let through 40% of genuinely unsafe content. For a safety gate that's unacceptable — you'd tune the threshold to raise recall, accepting more false positives (lower precision) because a missed unsafe item costs far more than an over-cautious flag. Same metrics, but which one you optimize is set by the cost of each error, not by the math.
SafeGuard: optimizing the wrong number
SafeGuard built a content-moderation classifier and proudly drove its precision to 0.95 — of everything it flagged, 95% was truly harmful. Leadership was pleased until harmful content kept reaching users. The blind spot: they'd optimized precision while recall sat at 0.60 — the filter was missing 40% of genuinely unsafe content. For a safety gate, recall is the number that matters, because a missed harmful item is the costly error; an over-cautious flag is cheap.
They'd picked a metric that made the system look good instead of the one that matched the cost of each error. Re-tuned to prioritize recall (accepting more false positives, a human reviews those), missed-harmful content dropped sharply. The lesson: there is no universal metric — the scorer is a per-task design decision, and which number you optimize is set by which error is expensive, not by what looks impressive on a slide.
This chapter: Meridian picks Remi's scorers by task. A deterministic check verifies a processed refund matches policy (exact, cheap, perfectly reliable). Intent routing is really classification, so they track precision and recall — and for the "possible fraud" intent they optimize recall, because missing a fraud case is the expensive error. Tone and helpfulness, having no answer key, go to a validated judge. Each scorer's blind spot is named out loud. (Ch 5: they validate that judge.)
Quiz · Chapter 4
- The gold-standard scorer for generated code is:
- The main weakness of BLEU/ROUGE is that they:
- For a safety filter you most likely optimize for:
- "Precision" measures:
- Semantic similarity tells you an output is: