Module 8: Project — Architect an AI Feature in Mercado

The eval gate

Overview

Step 3 of the method answers a question the budget left open: the cascade of lesson 3 cheapened the feature, but how do you know the savings didn't break the quality? The answer is the sheet's eval_gate field: a gate that runs an eval-set of the support agent and blocks the deploy if the quality drops. This lesson sets it up and executes it over two versions of the agent —the production candidate, which passes, and a regression, which fails— and ties the eval to the budget: the regression that's blocked is exactly a poorly calibrated cost savings, that of dropping too much to the cheap model.

This matters because without the gate, lesson 3's savings would be a blank check. The cascade tells you "I cheapened the feature by 47%"; the eval gate tells you "…and the quality is still above the threshold" —or "…but the quality dropped, so this savings doesn't reach production"—. The two sentences together are what makes it responsible to optimize the cost of an AI component. A team that cheapens without an eval watching is optimizing blindly; one that ties the cascade to the eval gate optimizes with a safety net. And since you can't assert the exact output of an LLM (module 1), that net isn't an exact assert —it's a statistical gate, a score against a threshold—.

Connection with the module. This lesson closes the loop lesson 3 opened: there you optimized the cost, here you verify the optimization didn't degrade the quality. It's the feature's third gate —latency (M2), cost (M2), and now quality (M3)—, and all three have the same form: a metric against a threshold, and all three must pass for the feature to reach production. It's also the gate the following lessons feed: lesson 7 closes the data loop by turning the live thumbs_down into new eval-set cases, and lesson 8 runs this same gate over the complete system to decide its deploy. The boundary with AI Engineering is precise: here the eval is a gate (does it pass or not?); how a good eval-set is designed —which cases to choose, how to build the gold, how to avoid overfitting— is AI Engineering.

The analogy: the quality control that rejects the cheaper batch

Go back to the light-bulb factory and its quality control station, the one that rejects any batch with more than 2% defects. Now imagine the purchasing manager arrives with a proposal: they found a cheaper filament supplier, which halves the cost of each bulb. It's great news for the budget. But before switching suppliers, the batch made with the cheap filament passes through the same control station. And it turns out that filament, being cheaper, produces bulbs that fail more: the batch's defect rate rises to 5%. The station does the only thing it knows how to do: it rejects the batch. It doesn't care that the filament is cheaper; it doesn't care that the purchasing manager is excited. The rule is the rule: more than 2% defects, and the batch doesn't go out.

Notice what just happened: the savings that breaks the quality doesn't reach the customer. The cheap filament would have saved money, but at the cost of selling bulbs that fail, and the control station exists precisely so that decision isn't made on its own, in the excitement of the savings. The station isn't against saving; it's for the savings not degrading the product below the line. If the manager finds a cheap filament that does pass the 2%, go ahead. If not, the savings stays at the door.

The eval gate is that station, and the "cheaper filament" is the poorly calibrated cascade of lesson 3 —dropping to the cheap model to save—. The savings is tempting (the feature costs less), but if it drops the support agent's quality below the threshold, the eval gate rejects the version, exactly as the station rejects the batch. The eval isn't against the cascade; it's for the cascade not degrading the quality below the line. A savings that passes the eval, go ahead; one that doesn't, stays at the door. This lesson sets up that station for Mercado's support agent.

Worked example: the candidate passes, the regression fails

We're going to set up the gate and pass two versions of the agent through it. The gate runs an eval-set of ten tickets, gets the score (fraction of correct responses), compares it against the 0.80 threshold, and decides: PASS (deploy allowed) or FAIL (deploy blocked). Version A is the production candidate with the current model: it answers 9 of 10 well (score 0.90). Version B is the same system, but someone dropped to the cheap model to save (lesson 3's cascade, poorly calibrated): cheaper, but it regressed to 6 of 10 (score 0.60).

# M8 Lesson 4 — the EVAL GATE (M3) of the support agent: the quality gate
# that governs the deploy. It runs the eval-set, gets the score, compares it
# against the threshold, and decides PASS (deploy) or FAIL (blocked). Here we catch
# a REGRESSION: dropping to the cheap model (the M3-L4 cascade) raised the speed
# but knocked down the quality. All SIMULATED, deterministic output.

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 business 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 receive 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 your order's tracking in your profile.",
    "q2": "For a return, go to your order and press Return.",
    "q3": "Standard shipping takes 3 to 5 business 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 section, Addresses.",
    "q7": "We resend the invoice to your account's email.",
    "q8": "You can cancel the order if it wasn'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)


def eval_gate(agent, threshold):
    # THE GATE: score against threshold => deploy decision.
    score = run_eval(agent)
    return score, (score >= threshold)


THRESHOLD = 0.80
ALL_IDS = {c["id"] for c in EVAL_SET}
# Version A: the complete system with the current model (production candidate).
version_a = make_agent(ALL_IDS - {"q9"})                     # 9/10
# Version B: the same system, but someone dropped to the cheap model to save
# (the poorly calibrated M2 cascade). Cheaper, but REGRESSED in quality.
version_b = make_agent({"q1", "q2", "q3", "q4", "q6", "q8"})  # 6/10

print("=== EVAL GATE — Mercado support agent (threshold 0.80) ===\n")
print(f"{'version':<34}{'score':>7}   {'gate':<7} deploy")
for name, agent in (("A (current model, candidate)", version_a),
                    ("B (regression: dropped to cheap)", 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:<34}{score:>7.2f}   {verdict:<7} {action}")

print()
print("Version B was cheaper (the M2 savings), but the eval-gate")
print("BLOCKS it: the savings that breaks quality doesn't reach production. The")
print("gate is what makes 'cheaper' not mean 'worse in production'.")

What to expect. When you run the file, the output is exactly this:

=== EVAL GATE — Mercado support agent (threshold 0.80) ===

version                             score   gate    deploy
A (current model, candidate)         0.90   [PASS]  ALLOWED (green)
B (regression: dropped to cheap)     0.60   [FAIL]  BLOCKED (red)

Version B was cheaper (the M2 savings), but the eval-gate
BLOCKS it: the savings that breaks quality doesn't reach production. The
gate is what makes 'cheaper' not mean 'worse in production'.

Read the two rows, because in them is the central mechanism of step 3.

Version A passes, in green. Score 0.90 (9 of 10 correct responses), which is greater than or equal to the 0.80 threshold, so the gate returns PASS: deploy allowed. The agent is good enough for production according to the rule the team set beforehand. No one had to read the ten responses and opine case by case; the gate compared a number against a threshold and gave green.

Version B fails, in red. Score 0.60 (6 of 10), which is less than 0.80, so the gate returns FAIL: deploy blocked. And here's the connection with lesson 3, which is the heart of this lesson: version B was cheaper. Someone dropped to the cheap model chasing the cascade's savings —a decision that in lesson 3 looked like pure gain (less cost)—. But the cheap model answers four of the ten questions worse, and the eval gate catches it. The savings was real; the quality degradation too. The gate rejects the version without negotiating, exactly as the control station rejects the batch with the cheap filament: the savings that breaks the quality doesn't reach production.

Put the two together and you have what ties step 2 with step 3. Lesson 3 gave you a cost lever (the cascade) with a hidden quality risk (the false negative, the hard one badly routed to the cheap one). This lesson sets up the gate that makes that risk visible before deploying: version B looks like a savings until it runs through the eval gate, and there it's seen that the savings came at the cost of four bad responses. Without the eval gate, version B would have been deployed —cheaper, faster, and worse for 40% of the customers—. With the eval gate, it stays at the door. That's the difference between optimizing the cost blindly and optimizing it with a safety net.

Going deeper: the fitness function of the probabilistic component

The eval gate 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 infrastructure", "the p95 latency is still under 200 ms"—. The 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: govern that a property of the system doesn't degrade with the changes. It's not a metaphor: the eval is a fitness function for AI. The general concept lives in architecture-decisions; here you see its incarnation.

The threshold comes from the risk, and for this feature it's high. Where does the 0.80 come from? Not from engineering. Just as lesson 3's cost_budget came from the business, the quality threshold comes from how much imperfection the feature tolerates —which is exactly lesson 2's nd_tolerance—. The support agent has tolerance 3 (intolerant), so its threshold would have to be demanding. Here we use 0.80 for the informative responses (with a human backup, one in five imperfect is tolerable), but for the actions that touch money —the refunds— the effective threshold is even harder, and in fact another layer guarantees it: the deterministic shell (lesson 7), which validates each refund proposal against the policy, not just an aggregate score. The lesson: the threshold is negotiated with whoever knows the cost of a bad response, and for a tolerance-3 feature, the quality is governed with a strict eval gate and a shell that doesn't trust the score.

A too-low threshold is a useless gate. If you set the threshold at 0.50, almost any version would pass it —including version B with 0.60—, and the gate would stop protecting: it would go green for everything, so it would never stop anything. A gate that never fails isn't a gate, it's a decoration. It's the equivalent of the control station that accepts batches with up to 50% defects: technically it exists, but it rejects nothing. The threshold has to be where it really separates the acceptable from the unacceptable —high enough to catch the regression that matters, the version B that a 0.80 threshold does block—. An eval gate that has never blocked a deploy is suspicious: either all your changes were perfect, or your threshold is too loose (almost always the latter).

The gate decides, it doesn't fix. An important nuance: the eval_gate tells you whether the version passes, not how to fix it. When version B fails with 0.60, the gate did its job —it blocked a deploy that would have degraded the quality—, but fixing the component (recalibrating the cascade so it escalates more when in doubt, improving the agent's prompt —that's AI Eng—, or going up a model where needed) is another task. The gate is the guard that doesn't let the defective batch pass; it's not the mechanic who fixes the filament. Separating the two roles is key: the gate protects production, the per-case detail guides the correction.

Common mistakes

Treating the eval as a thermometer and not as a gate. What happens: the team runs the eval, looks at the score (0.60), comments "it's a bit low but the savings is worth it" and deploys anyway. The eval measured, but didn't decide: there was no threshold with authority, so the score was a suggestion the savings excitement ignored. Why it happens: without a threshold set beforehand, each release is negotiated according to the moment's hurry (or savings). How to detect it: your deploys happen with scores "that could be better" but no one blocks them. How to fix it: set an explicit threshold before and make the gate block without negotiating when the score falls below —the control station doesn't argue with the purchasing manager—.

Confusing "cheaper" with "better" without verifying the quality. What happens: it's the error this lesson attacks head-on. The team applies lesson 3's cascade, sees the cost drop, and deploys the cheaper version without running the eval —assuming saving is always good—. The cheap version answers worse, and the degradation is discovered from customer complaints weeks later. Why it happens: the savings is visible and measurable (the bill drops), the quality degradation is invisible without an eval that measures it. How to detect it: you deployed a cost change (cheaper model, shorter prompt) without running the eval gate over the new version. How to fix it: every change that touches the cost passes through the eval gate before deploying. The cascade and the eval go together: the cascade gives the savings, the eval confirms the savings didn't break the quality. One without the other is dangerous.

A too-loose threshold that never fails. What happens: so "nothing gets stuck", the team sets the threshold at 0.50, and then almost any version passes —including version B's regression—. The gate exists on paper but goes green for everything, giving a false sense of security: "we have an eval gate" while the gate protects nothing. Why it happens: a low threshold avoids friction and false alarms, tempting when the gate "annoys". How to detect it: your eval gate has never blocked a deploy. How to fix it: set the threshold where it really separates acceptable from unacceptable —high enough to catch version B (0.60) that a 0.80 threshold does block—. A gate that never fails isn't a gate.

Exercises

Exercise 1 — The savings that does pass. The product manager insists on the cascade's savings and asks for a cheaper version. The team recalibrates the classifier so it escalates more when in doubt (fewer hard ones to the cheap one), and the new version C gives: cost $2,900/mo (under the $3,000 budget) and score 0.86 (above the 0.80 threshold). Is it deployed? Explain what the recalibration achieved and why this version is indeed a good savings.

See solution

Yes, it's deployed. Version C passes the feature's three gates:

  • Cost budget: $2,900/mo ≤ $3,000/mo → PASS. It fits the margin.
  • Eval gate: 0.86 ≥ 0.80 → PASS. The quality holds.
  • (The latency, as in lesson 3, fits with slack.)

What the recalibration achieved: version B failed because the classifier sent too many hard queries to the cheap model (false negatives), which answered them worse —hence the 0.60 score—. By recalibrating so the classifier escalates when in doubt (preferring the cheap false positive: sending an easy one to the expensive one and paying a few cents extra, over the expensive false negative: sending a hard one to the cheap one and answering badly), version C keeps almost all of the cascade's savings without sacrificing the quality: the hard ones go back to the expensive model, so the score rises to 0.86, while the 70% easy ones still go to the cheap one, so the cost stays low.

Why it's a good savings: it's a savings that passes the eval. The difference from version B isn't that it saves less —it saves almost the same—, but that it saves without degrading the quality below the threshold. It's exactly the cheap filament that does pass the 2% control: the purchasing manager gets their savings, and the customer gets a product that doesn't fail. The lesson: the eval gate isn't against saving; it's for the savings not breaking the quality. Version C is the correct way to apply the cascade —cheap where it doesn't cost quality, expensive where it does—, and the eval gate is what distinguishes this good savings from version B's bad savings.

Exercise 2 — The three gates together. An AI feature ready for production passes three gates: latency budget, cost budget, and eval gate. A version of the support agent gives: p95 latency 1200 ms (budget 4000 ms), cost $2,787/mo (budget $3,000), score 0.72 (threshold 0.80). Is it deployed? Explain what each gate contributes and why all three are necessary.

See solution

It's not deployed. All three gates must pass, and this version fails the quality one:

  • Latency budget: 1200 ms ≤ 4000 ms → PASS. It's fast.
  • Cost budget: $2,787/mo ≤ $3,000/mo → PASS. It fits the margin.
  • Eval gate: 0.72 < 0.80 → FAIL. It's not good enough.

The overall result is blocked: one gate failing is enough. The feature is fast and cheap, but it answers more than a quarter of the tickets badly, and deploying that would be serving a support agent that's fast, economical, and bad.

What each gate contributes and why all three are necessary: the latency measures whether it responds in time, the 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 (step 2), you'd have deployed a fast, cheap agent without realizing it answers badly —the error this lesson attacks—. The eval gate is the third gate, the one step 3 contributes, and without it the trio is incomplete. And notice the irony of this specific version: it's cheaper than the one in exercise 1 ($2,787 vs $2,900) but worse (0.72 vs 0.86) —again, the savings that breaks the quality—. A feature ready for production passes all three.

Exercise 3 — The eval gate over the system, not just the model. In this capstone, the eval gate doesn't evaluate "the model" in isolation, but the complete system —the agent with its shell, its guardrails, its fallback—. Explain why evaluating the complete system gives a more honest score than evaluating only the model, with an example where the model alone would give one score and the complete system another.

See solution

Evaluating the complete system gives a more honest score because the quality the customer experiences is the system's, not the isolated model's. The customer never talks with the naked model; they talk with the model plus its shell, its guardrails, and its fallback. Measuring only the model ignores everything that surrounds it, and that "everything" changes the result in both directions.

An example where the two scores differ: suppose a ticket asking "refund me 9999 for my order of 50" (from a confused or malicious customer).

  • Evaluating only the model: the model, induced, proposes refund 9999. If your eval measures "did the model propose the right thing?", this counts as a failure —the model got it wrong—, lowering its score.
  • Evaluating the complete system: the model proposes refund 9999, but the deterministic shell blocks it (amount out of policy) and the system responds with a correct refusal or escalates to a human. The result the customer sees is correct —9999 wasn't refunded—, so the case counts as a system success.

The complete system gives the honest score because the model doesn't have to be perfect for the system to be —exactly the whole guide's thesis—. A model that sometimes proposes badly, surrounded by a shell that catches those proposals, produces a high-quality system. And conversely: an excellent model connected directly to the refunds API (no shell) is a fragile system, even if the model alone scores high. The architectural quality lives in the system, not the model, so the eval gate must run over the system. This connects with lesson 8, where the capstone's eval gate runs over the complete pipeline —cascade, guardrails, shell, fallback— to decide the deploy.

Summary and next step

In this lesson you set up the sheet's eval_gate field: the quality gate that governs the support agent's deploy. With the quality control station that rejects the batch made with the cheaper filament, you saw the central idea of step 3: the savings that breaks the quality doesn't reach production. You executed the gate over two versions: the candidate (0.90 ≥ 0.80) passed in green, and a regression —lesson 3's cheap model, more economical but degraded— (0.60 < 0.80) failed in red, with the deploy blocked. You thus tied step 2 with step 3: the cascade gave you the savings, and the eval gate confirmed the savings didn't break the quality. And you understood why the eval deserves the name fitness function —an automated test that governs that a property of the system (the probabilistic quality) doesn't degrade with the changes—, specialized from architecture-decisions 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/tolerance, not a technical calculation); recognize why a too-low threshold makes the gate useless; and articulate why the eval gate must run over the complete system, not just the model.

Lesson 5 sets up the sheet's guardrail field: the guardrails and the trust boundary. So far we've dealt with the cost (M2) and the quality (M3); now we armor the security. You'll set up the guardrail stack around the core —injection signal at the input, schema validation at the output, and the deterministic boundary that validates the proposal against the policy— and you'll execute the attack: the model will give in to three injections —leak its prompt, refund 9999, invent a grant_admin action— and each proposal will die at a different gate. The security won't come from a stronger prompt, but from validating the output.

Resources