Module 3: The Eval as a Fitness Function
4. The eval as a quality gate (fitness function)
Overview
By the end of this lesson you'll have the heart of the module: the score you built in lesson 3 becomes a gate that governs the deploy. A score alone —0.90— describes the component but decides nothing; it's a grade with no passing rule. What turns it into a decision is an explicit threshold: "this feature needs a score of at least 0.80 to go to production." With the threshold, the score becomes a binary and verifiable gate —it passes (deploy allowed) or fails (deploy blocked)—, exactly like module 2's fits(value, budget) gate turned "fast" and "cheap" into verdicts. You're going to execute the eval_gate over two versions of Mercado's support agent: version A (score 0.90 ≥ 0.80) passes —deploy allowed, in green— and version B, which regressed (score 0.60 < 0.80), fails —deploy blocked, in red—. And you're going to understand why that gate deserves a name you already know from another guide: it's a fitness function for the quality of a probabilistic component.
This matters because the threshold is what gives the eval authority. Without a threshold, the eval is a thermometer: it tells you the temperature (0.90, 0.60) but makes no decision, and a team can look at a score of 0.60 and deploy anyway "because it looks acceptable." With a threshold, the eval is a gate: a score below the threshold doesn't pass, period, just as a batch of bulbs with too many defects doesn't leave the factory no matter how much of a hurry there is. That step —from thermometer to gate— is what makes AI quality stop being negotiable case by case and become a rule the system enforces. And it's the missing piece to complete module 2's trio: latency (gate), cost (gate), and now quality (gate). All three have the same shape —a metric against a threshold— and all three must pass for an AI feature to reach production.
Connection with the module: this lesson is the pivot. Everything before built toward here: lesson 2 showed why you need a score, lesson 3 manufactured it, and this one turns it into a gate. Everything that follows uses this gate: lesson 5 runs it over successive versions to catch regressions (the score of a change against a baseline), and lesson 6 puts it into CI so it blocks the deploy automatically. Lesson 7 opens the success criterion that feeds the score. Here the connection with architecture-decisions also becomes explicit: the fitness function as a general concept lives there; this module specializes it to probabilistic quality. In one sentence: here the score becomes a gate, and the gate is a fitness function.
The analogy: the quality control that rejects the batch
Go back to the bulb factory. At the end of the production line there's a quality-control station. It doesn't check bulb by bulb —it would be extremely slow—; it takes a sample of each batch, counts how many are defective, and computes the defect rate. And it has a written rule, decided in advance: if the defect rate exceeds 2%, the whole batch is rejected. It doesn't leave the factory. It doesn't matter that 98% of the bulbs work perfectly; it doesn't matter that the customer is waiting for them; it doesn't matter that redoing the batch costs money. The threshold is the threshold, and a batch that crosses it doesn't pass. That rule isn't an opinion of the inspector on duty: it's written, it's the same every day, and any inspector with the same sample gives the same verdict.
Translate the station piece by piece. The defect rate (or its complement, the good rate) is the score —the aggregate quality measurement—. The 2% threshold is the gate's threshold —the line that separates acceptable from unacceptable—. The decision to reject the batch is blocking the deploy. And the most important property: the station decides on its own, with a fixed rule, without anyone arguing case by case whether "this batch looks good enough." That's the difference between a thermometer and a gate. A thermometer measures the defect rate and reports it; a gate measures it and decides to reject or let through. The eval without a threshold is the thermometer; the eval with a threshold is the quality-control station. And like the station, it can be on the line permanently, checking each batch —each change to the component— before it reaches the customer. This lesson sets up that station; lesson 6 leaves it installed on the line (in CI).
Worked example: the eval_gate in green and in red
We're going to set up the gate and pass two versions of the support agent through it. The gate is simple: it runs the eval-set, gets the score, compares it against the threshold, and returns the verdict —PASS (deploy allowed) or FAIL (deploy blocked)—. Version A is the production candidate: it answers 9 of 10 well (score 0.90). Version B is one that regressed —maybe someone changed the prompt or dropped the model to save (module 2's cascade)— and answers only 6 of 10 well (score 0.60). The feature's quality threshold is 0.80.
# Lesson 04 — the eval as a quality gate (fitness function)
# Everything SIMULATED. Zero network, zero API, zero keys. Deterministic output.
# (Reuses EVAL_SET, GOLD, POOR_ANSWER, make_agent, and run_eval from lesson 3.)
EVAL_SET = [
{"id": "q1", "question": "where is my order", "must_contain": "tracking"},
{"id": "q2", "question": "how do i return a product", "must_contain": "return"},
{"id": "q3", "question": "how long does shipping take", "must_contain": "3 to 5 days"},
{"id": "q4", "question": "can i pay in installments", "must_contain": "installments"},
{"id": "q5", "question": "the product arrived broken", "must_contain": "refund"},
{"id": "q6", "question": "how do i change my address", "must_contain": "profile"},
{"id": "q7", "question": "i didn't get my invoice", "must_contain": "email"},
{"id": "q8", "question": "i want to cancel my order", "must_contain": "cancel"},
{"id": "q9", "question": "the coupon doesn't work", "must_contain": "expiration"},
{"id": "q10", "question": "how do i contact a seller", "must_contain": "messages"},
]
GOLD = {
"q1": "You can see the tracking of your order in your profile.",
"q2": "For a return, go to your order and click Return.",
"q3": "Standard shipping takes 3 to 5 days.",
"q4": "Yes, you can pay in installments with no interest by card.",
"q5": "We're sorry about that; you can request a refund from the order.",
"q6": "Change your address in the Profile, Addresses section.",
"q7": "We'll resend the invoice to your account email.",
"q8": "You can cancel the order if it hasn't shipped yet.",
"q9": "Check the coupon's expiration; it may have already expired.",
"q10": "Message the seller from the Messages section.",
}
POOR_ANSWER = "Sorry, I don't have information about that."
def make_agent(competent_ids):
def agent(question, case_id):
return GOLD[case_id] if case_id in competent_ids else POOR_ANSWER
return agent
def run_eval(agent):
passed = sum(
case["must_contain"] in agent(case["question"], case["id"]).lower()
for case in EVAL_SET
)
return passed / len(EVAL_SET)
# --- THE GATE: score against threshold => deploy decision ---
def eval_gate(agent, threshold):
score = run_eval(agent)
passed = score >= threshold # the fixed rule, like the factory's 2%
return score, passed
THRESHOLD = 0.80 # the feature's quality threshold
ALL_IDS = {c["id"] for c in EVAL_SET}
version_a = make_agent(ALL_IDS - {"q9"}) # candidate: 9/10
version_b = make_agent({"q1", "q2", "q3", "q4", "q6", "q8"}) # regression: 6/10
print("=== EVAL GATE — Mercado support agent ===")
print(f"quality threshold = {THRESHOLD:.2f}\n")
print(f"{'version':<26}{'score':>7} {'gate':<7} deploy")
for name, agent in (("A (production candidate)", version_a),
("B (regression introduced)", version_b)):
score, passed = eval_gate(agent, THRESHOLD)
verdict = "[PASS]" if passed else "[FAIL]"
action = "ALLOWED (green)" if passed else "BLOCKED (red)"
print(f"{name:<26}{score:>7.2f} {verdict:<7} {action}")
What to expect. When you run it:
=== EVAL GATE — Mercado support agent ===
quality threshold = 0.80
version score gate deploy
A (production candidate) 0.90 [PASS] ALLOWED (green)
B (regression introduced) 0.60 [FAIL] BLOCKED (red)
Here's the quality-control station working. Read it by version.
Version A passes, in green. Its score is 0.90 —it answers 9 of the 10 questions well—, and 0.90 is greater than or equal to the 0.80 threshold, so the gate returns PASS: deploy allowed. The feature is good enough for production according to the rule the team set in advance. Nobody had to read the ten responses and opine; the gate compared a number against a threshold and gave green.
Version B fails, in red. Its score is 0.60 —it answers only 6 of 10 well—, and 0.60 is less than the 0.80 threshold, so the gate returns FAIL: deploy blocked. And notice the force of this: version B isn't "broken" —six out of ten responses are good, a customer asking about their order is well served—. But it isn't good enough according to the threshold, and the gate rejects it without negotiating, just as the factory rejects the batch with 3% defects even though 97% of the bulbs work. "Acceptable to the eye" quality isn't enough; there's a written line, and version B is below it.
Put the two together and you have the module's central mechanism. The same feature, two versions, and a gate that lets one through and blocks the other —automatically, with a fixed rule, without human judgment case by case—. This is what turns the quality of a probabilistic component from an opinion ("it looks good") into a control ("it passes the gate or it doesn't enter"). And it's exactly the shape module 2's budget gates had: a metric (score), a threshold (0.80), a verdict (PASS/FAIL). Quality is no longer the fuzzy exception among an AI feature's constraints; it's just another gate, with the same discipline as latency and cost.
Going deeper: why this is a fitness function
The eval is a specialized fitness function. In architecture-decisions-and-tradeoffs-guide you learn the concept of a fitness function: an automated test that verifies an architectural property of the system holds over time, and that goes red if the property degrades, stopping the change. The classic examples are structural —"no domain layer imports from the infrastructure layer", "the endpoint's p95 latency is still under 200 ms", "no module exceeds 500 lines"—: properties you want the system to preserve as it evolves. This lesson's eval_gate is exactly that, applied to a new property: the quality of a probabilistic component. It's an automated test (the eval-set), it verifies a property holds (the score is still above the threshold), and it goes red if it degrades (score < threshold → deploy blocked). The only difference from a classic fitness function is that the property isn't deterministic or structural, but statistical —a score, not an exact boolean—. But the architectural role is identical: governing that a system property doesn't degrade with changes. That's why the module is called "the eval as a fitness function": it's not a metaphor, it's an identity. The general concept is from architecture-decisions; here you see its embodiment for AI.
The threshold is a design and business decision, not a technical one. Where does the 0.80 come from? Not from engineering. Just as module 2's cost budget came from the margin the business could spend, the quality threshold comes from how much imperfection the feature tolerates. For a support agent that only informs, a score of 0.80 may be acceptable —one imperfect response in five, with a human backstop, is tolerable—. For a component that touches money or health, the threshold would have to be much higher, or the component shouldn't even decide on its own (that's module 6, the deterministic shell). Setting the threshold is a conversation about risk and consequence, not a calculation. The rule: the threshold is negotiated with whoever knows the cost of a bad response, and then the gate enforces it. A threshold engineering invents without thinking about the consequences of failing is a sign with no authority —just like module 2's budget—.
A too-low threshold is a useless gate. Here there's a subtle danger that connects with the "useless fitness function." If you set the threshold at 0.50, almost any version passes it —including version B with 0.60—, and the gate stops protecting: it goes green for everything, so it never stops anything. A gate that never fails isn't a gate, it's an ornament. It's the equivalent of the quality-control station that accepts batches with up to 90% defects: it technically exists, but it rejects nothing, so it controls nothing. The threshold has to be where it truly separates acceptable from unacceptable —high enough to catch the regressions that matter—. You'll see it in Common mistakes: an eval-set (or a threshold) that never fails gives a false sense of security.
The gate doesn't fix, it only decides. An important nuance: the eval_gate tells you whether the version passes, not how to fix it if it doesn't. When version B fails with 0.60, the gate did its job —it blocked a deploy that would have degraded quality—, but fixing the component (improving the prompt, going up a model, fixing the failing cases) is another task, guided by lesson 3's per-case detail. The gate is the guard who doesn't let the heavy truck through; it's not the mechanic who lightens it. Separating the two roles is key: the gate protects production, the detail guides the fix.
Common mistakes
Treating the eval as a thermometer and not as a gate (an omission mistake). What happens: the team runs the eval, looks at the score (0.60), comments "it's a bit low but it looks acceptable" and deploys anyway. The eval measured, but didn't decide: there was no threshold with authority, so the score was a suggestion that was ignored. Why it happens: without a threshold set in advance, each release is negotiated according to the moment's hurry, and the low score always finds an excuse. How to spot it: if your deploys happen with scores that "could be better" but nobody blocks them, you have a thermometer, not a gate. How to fix it: set an explicit threshold beforehand and have the gate block without negotiating when the score falls below —the quality-control station doesn't argue with the defective batch—.
A too-loose threshold: the gate that never fails (an over-permissiveness mistake). What happens: so "nothing gets stuck," the team sets the threshold at 0.50, and then almost any version passes —including a serious regression—. The gate exists on paper but goes green for everything, so it gives a false sense of security: "we have an eval gate" while the gate protects against nothing. Why it happens: a low threshold avoids friction and false alarms, and it's tempting when the gate "gets in the way." How to spot it: if your eval gate has never blocked a deploy, suspect the threshold is too low, not that all your changes are good. How to fix it: put the threshold where it truly separates acceptable from unacceptable —high enough to catch version B (0.60), which a 0.80 threshold does block—.
Confusing the gate with the fix (a scope mistake). What happens: version B fails the gate and the team expects the gate to "solve" the problem, or gets frustrated because "the eval doesn't fix anything." The gate blocked the deploy —it did its job—, but improving the component is another task the gate doesn't do. Why it happens: it's expected that a tool that detects also corrects, like a self-repairing test. How to spot it: if you complain that the eval "only says it's wrong but doesn't fix it," you confused the role. How to fix it: use the gate to decide (block the bad deploy) and the per-case detail (lesson 3) to guide the fix (which cases to fix); they're two distinct and complementary roles.
Exercises
Exercise 1 — Apply the gate. A feature's quality threshold is 0.85. Three versions give these scores: V1 = 0.92, V2 = 0.85, V3 = 0.83. For each, give the gate's verdict (PASS/FAIL, deploy allowed/blocked) and explain the case of V2 and V3, which are on the edge.
See solution
With the rule score >= threshold and threshold 0.85:
- V1 = 0.92: 0.92 ≥ 0.85 → PASS, deploy allowed. With room to spare.
- V2 = 0.85: 0.85 ≥ 0.85 → PASS, deploy allowed. Right at the threshold. The
>=(greater than or equal) convention makes the exact threshold value pass —it's a design decision: you could use>(strict) and then V2 would fail—. What matters is that the rule be explicit and consistent. - V3 = 0.83: 0.83 < 0.85 → FAIL, deploy blocked. By two hundredths below the threshold, it doesn't pass. And so it should be: the line is the line. If you let 0.83 through "because it's close," the threshold would lose its authority —tomorrow someone would ask to pass 0.82 "because it's close to 0.83"—.
The moral: the gate is binary and doesn't negotiate with closeness. Being "almost" above the threshold is being below. That's what makes it a gate and not a suggestion.
Exercise 2 — The useless threshold. A team brags: "we have an eval gate in production, with a 0.40 threshold." You review the history and the gate has never blocked a deploy in six months. Explain what's wrong, why a 0.40 threshold makes the gate useless, and how this connects with the idea of the "useless fitness function."
See solution
What's wrong: the threshold is so low that the gate can't fail. With 0.40, a component that answers fewer than half the cases well still passes; to block a deploy, a version would have to be catastrophically bad (worse than answering 4 out of 10 well). In practice, no real version falls that low, so the gate goes green for everything. That it "has never blocked a deploy in six months" isn't a sign that all changes were good —it's a sign that the gate isn't measuring anything useful—.
The connection with the useless fitness function: a fitness function must be able to go red; if it's calibrated so it always passes (a loose threshold, a trivial condition), it verifies nothing —it gives the illusion of control without control—. It's worse than having no gate, because it generates false confidence: the team believes quality is protected when it isn't. The fix: raise the threshold to where it truly separates acceptable from unacceptable (for this agent, something like 0.80), so a serious regression like version B (0.60) does get blocked. A gate that never fails isn't a gate.
Exercise 3 — The three gates together. Remember from module 2 that an AI feature passes three gates: latency budget, cost budget, and (now) eval gate. A version of Mercado's semantic search gives: latency 250 ms (budget 800 ms), cost $2,100/month (budget $3,000/month), quality score 0.72 (threshold 0.85). Does it deploy? Explain what each gate contributes and why all three are necessary.
See solution
It doesn't deploy. All three gates must pass, and this version fails the quality one:
- Latency budget: 250 ms ≤ 800 ms → PASS. It's fast.
- Cost budget: $2,100/month ≤ $3,000/month → PASS. It fits the margin.
- Eval gate: 0.72 < 0.85 → FAIL. It isn't good enough.
The overall result is blocked: a single gate failing is enough. The feature is fast and cheap, but it returns barely relevant results (one in four searches fails the quality criterion), and deploying that would be serving a search that's speedy, cheap, and bad.
What each gate contributes and why all three are necessary: latency measures whether it responds on time, cost whether it fits the margin, and the eval whether the response is good. They're orthogonal properties: a feature can be good at two and bad at the third, like this one. If you only had the latency and cost gates (module 2), you'd have deployed a fast, cheap search without realizing it gives bad results —the mistake of exercise 3 of module 2's lesson 3—. The eval gate is the third gate, the one this module contributes, and without it the trio is incomplete. A production-ready AI feature passes all three.
Summary and next step
In this lesson you set up the heart of the module: the score became a gate by comparing it against a threshold. With the factory quality-control analogy you saw the difference between a thermometer (measures and reports) and a gate (measures and decides): the station rejects the batch that crosses 2% defects with a fixed rule, without arguing case by case, just as the eval_gate blocks the version whose score falls below the threshold. You executed the gate over two versions of the support agent: A (0.90 ≥ 0.80) passed in green —deploy allowed— and B, which regressed (0.60 < 0.80), failed in red —deploy blocked—, without a human judging by eye. And you understood why this deserves the name fitness function: it's an automated test that governs that a system property —the quality of a probabilistic component— doesn't degrade with changes, exactly the role of a fitness function from architecture-decisions, specialized to AI.
Before moving on you should be able to: explain why a score needs a threshold to become a gate; justify where the threshold comes from (risk and consequence, not technical calculation); recognize why a too-low threshold makes the gate useless; and articulate why the eval gate is a fitness function and how it completes module 2's trio of gates.
What follows is putting the gate to work where it really matters: at the moment of change. A component doesn't break on its own; it breaks when someone changes the prompt or the model. In lesson 5 you're going to use the eval to catch a regression: starting from a baseline (0.80), you'll see that a new prompt raises the score (improvement → accept) and that dropping to a cheaper model —module 2's cascade— makes it fall (regression → reject). The eval detects the drop that "by eye" you wouldn't see. It's the step from "I have a gate" to "the gate protects me from a change degrading the quality without anyone noticing."
Resources
- Anthropic — Claude docs, evaluation and success criteria (conceptual) — the guide to how to define a success criterion and a threshold to decide whether an LLM application meets it; the conceptual backing for the score-against-threshold gate, without fixing a version.
- architecture-decisions-and-tradeoffs-guide — Fitness functions (M6) — the general concept of a fitness function as an automated test that verifies an architectural property and stops the change if it degrades; this module specializes it to the quality of a probabilistic component.
- martinfowler.com — "Emerging Patterns in Building GenAI Applications", Bharani Subramaniam and Martin Fowler — the pattern of evals as a quality gate that governs the changes of an LLM app; the architecture frame that surrounds the gate idea.