Development

Scoring Outputs Before You Switch to a Cheaper Model

Antonio Evans9 min read

A split scene: on the left, a labeled folder 'golden set' with rows of test cases grouped by intent ('refund', 'cancel', 'FAQ').
Key points

Key points

  1. 1A golden set built for prompt evaluation can be reused as a model-swap gate without rebuilding anything except expected behavior labels.
  2. 2Per-intent thresholds catch failures that a single aggregate pass rate hides, because a 2 point overall drop can be a 15 point drop inside one intent.
  3. 3The candidate model must run on the exact same cases, prompts, and decoding settings as the incumbent, or the comparison measures two changes at once.
  4. 4Blocking a swap on a single intent failure, logging the per-intent table, and defining re-run triggers turn a one-off cost experiment into a repeatable release gate.

Reuse the golden set, do not rebuild it

Most teams that already evaluate prompts own the hardest part of this workflow: a curated set of cases with inputs, context, and either an expected output or a description of expected behavior. The field structure is standard. A case holds an input, the context the system sees, an expected output or an expected behavior, and tags that mark category, difficulty, and source (qaskills.sh). DeepEval calls these entries goldens and converts them to test cases at evaluation time (DeepEval).

That distinction matters when you switch from prompt work to model work. A prompt change alters instructions, so the expected output in the case still describes the right target. A model change alters the engine, so the target stays the same and only the producer changes. You are not editing the artifact, you are re-pointing a runner at a different endpoint.

Langfuse describes a golden dataset as the fixed set of test cases you run your application against before every meaningful change (Langfuse). Read the words "every meaningful change" literally. A model swap is a meaningful change, and it is the one teams most often make without the gate, because the case for it is financial and the pressure is upward.

One preparation step is worth doing before you touch the candidate model. Audit each case and add a tag for intent if you do not already have one. Intents are the slice you will compare on, and a case without an intent tag is invisible to the whole method. If your tags were free text, normalize them now: refund, cancellation, technical fault, billing dispute, out of scope, and so on. Twenty to forty intents is typical for a support bot, but fewer is fine. What you cannot afford is one bucket called "other" holding half the set.

Reusing the dataset also preserves your investment in labeling. Production failures are the highest-value source of cases, because real users misspell, ramble, and paste error logs in ways no engineer imagines (Arthur). If you have been feeding production failures into the set since the last prompt evaluation, those cases are already waiting to test the cheaper model.

Write per-intent thresholds before you run anything

A threshold is a minimum pass rate for one intent on the golden set. Deciding it before you see results is the point. If you read the candidate model's scores first and then choose a bar, you are not gating, you are rationalizing.

Several inputs belong in the threshold decision.

Start with the incumbent model's own results on the same set. That number is your floor, and comparing a candidate against a real baseline avoids the classic trap of judging against an imagined standard. Run the current model and record its pass rate per intent before the candidate ever fires.

Then adjust for traffic weight. An intent that accounts for 2 percent of traffic deserves less strictness than one that accounts for 25 percent, all else equal. You can see that weighting in production traces, which is one reason span-level tracing pays for itself (Arize).

Then adjust for the cost of a miss. A wrong refund amount costs money. A wrong greeting tone costs a review. The threshold should reflect the consequence, not just the frequency.

A worked example, with invented numbers for illustration only: suppose your incumbent passes 98 percent on refund, 94 percent on cancellation, 96 percent on technical fault, and 99 percent on out of scope. You might set refund at 98, cancellation at 94, technical fault at 94, and out of scope at 98. Those numbers encode three decisions: no regression allowed on the money intent, a small allowance on cancellation because the judge itself is noisy, and no tightening on out of scope because the incumbent has near-zero headroom.

IntentTraffic share (illustrative)Incumbent pass rate (illustrative)Threshold pass rateRationale
Refund25%98%98%Money at stake, zero tolerance for regression
Cancellation20%94%94%Judge noise on a subjective answer, match the floor
Technical fault30%96%94%Two points of slack for phrasing variance
Out of scope25%99%98%Safety behavior, keep it tight

Small intents need special handling. If an intent has fewer than twenty or thirty cases, a single flipped case moves the pass rate by several points, so the threshold crossing is noise rather than signal. Options are to pool small intents into a related parent intent for gating purposes, to treat them as advisory only, or to grow the set for those intents before you rely on them. A set in the range of 50 to 100 well-chosen cases is a reasonable starting floor for the whole evaluation, which tells you how careful to be about tiny slices (qaskills.sh).

Run both models on identical cases

The single most common mistake in a model swap is measuring two changes at once. If the candidate model runs with a shorter prompt, a different temperature, or a trimmed context window, and the scores move, you cannot attribute the movement to the model. Change one variable at a time, a rule that shows up in prompt versioning guidance for exactly this reason (Microsoft Q&A).

Concretely, freeze the following across both runs: the exact system prompt text, retrieval settings (same index, same top-k), tool definitions, temperature and top-p, the judge model, and the judge prompt. Capture them in a run manifest so the comparison is reproducible later. A versioned prompt asset should snapshot the template, provider, model ID, hyperparameters, input schema, tools, and metadata, because any one of those changing alters system behavior (dev.to).

Non-determinism is the reason a single run per case is not enough. The same input can diverge across runs even with fixed settings, so use multiple trials and report pass rates rather than a single sample. The variation is exactly why you compare distributions by slice instead of eyeballing individual replies (Statsig). If your budget permits three trials per case, run three, take the pass rate per case, then aggregate to the intent.

For the judge, keep the same evaluator for both models. If the judge is an LLM, its prompt must be byte-identical between runs, including any rubric wording. A judge change between runs invalidates the comparison as surely as a prompt change does.

A minimal runner skeleton:

run(case, model, temperature=0.0, prompt_version=PINNED, top_k=5)
# for each case: 3 trials, judge each output, pass = 2 of 3
# aggregate: pass_rate = passes / cases, grouped by intent tag

Keep the raw outputs. Scores without outputs are unauditable, and the first question after a surprising drop will be "show me three failing examples in that intent."

Compare intent by intent, not in aggregate

The aggregate number is the least useful one you will compute. Aggregate pass rate hides redistribution: an intent that gains two points can mask another that loses fifteen, and the total stays flattering. Compare slices, not single replies; what matters is the distribution of outcomes by intent and segment (Statsig).

The comparison table is the deliverable. Build it as a gate artifact, with one row per intent, incumbent and candidate pass rates, the delta, and a verdict.

Consider the worked example continued: the candidate comes back at 99 percent on refund, 94 on cancellation, 95 on technical fault, and 91 on out of scope. Three of four intents clear. Out of scope drops 8 points against a threshold of 98 and an incumbent of 99. The aggregate might sit near 95, which sounds fine, and the swap is still blocked. That single row is the decision.

Now look at what that row means in practice. Out of scope is usually the refusal behavior: off-topic requests, attempts to extract the system prompt, questions about competitors. A model that handles those poorly is not a cheaper version of your assistant, it is a different and riskier product. Feature flag practice has a name for this pattern. An ops toggle exists so you can turn behavior off instantly without a redeploy, and the same instinct applies to model routing: the swap should be reversible by configuration, not by rollback of an artifact (Harness, Martin Fowler).

Two more subtle readings of the table are worth building into the review.

First, look for intents where the candidate is better. Those are candidates for partial routing: send that intent to the cheaper model and keep the incumbent for the rest. This is a real option, not a consolation prize, and it often captures most of the savings. A lower-priced model does not automatically make a cheaper system, because extra retries, extra turns, or extra context can erase the per-token gain, so partial routing should be validated the same way the full swap is (Arize, Braintrust).

Second, look at variance across trials within an intent. If the candidate swings widely on the same cases, a passing average can still be a fragile deployment. Record the spread alongside the mean.

Block the swap and log the decision

A gate that does not block is a dashboard. Write the rule down before the run and enforce it mechanically: if any intent falls below its threshold, the swap does not ship, no exceptions granted in the moment.

The gate belongs in the same pipeline as your other regression checks, and ideally runs without a human in the loop. LLM regression tests run automatically against a fixed dataset, comparing quality metrics before and after a change, and flagging degradation before release (Evidently AI). If you already run prompt evaluations in CI, this is a second invocation with a different model string and a different threshold file.

Layer the release. Even a passing eval is evidence, not proof, so ship the candidate behind a flag to a small traffic slice first, watch the same intent metrics live, and expand only if the live slices agree with the offline table. Feature flags decouple deployment from release, which lets you expose a change to a subset and widen gradually rather than all at once (LaunchDarkly, Unleash). A cheap-model rollout is a textbook release toggle: small exposure, kill switch ready, measured against the same intent slices.

The log entry should be durable and boring. Record the date, both model identifiers, the prompt version hash, the dataset version, the judge version, the per-intent table, the verdict, and a link to the CI run. Two practical reasons: the next person asked "why are we still on the expensive model" needs the answer, and the same record becomes the baseline for the next candidate. Immutable versioning exists so a log can be tied back to an exact artifact rather than a fuzzy memory (dev.to, Braintrust).

Keep the gate artifact small and disposable. When the swap completes, retire the temporary flag rather than leaving it to rot, because unfixed flags accumulate debt and can silently evaluate to the wrong state later (LaunchDarkly).

Set re-run triggers so the gate stays honest

A blocked swap is not a permanent answer. Models change under you, prompts drift, and the case set ages, so define the events that force a re-run.

Useful triggers:

  • The incumbent's price, rate limits, or availability changes enough to alter the economics.
  • A new candidate appears that you have not tested on this set.
  • The golden set gains new cases from production failures, since a changed dataset invalidates the old comparison.
  • The judge or rubric changes.
  • The route, prompt, or retrieval configuration for an affected intent changes.
  • A calendar checkpoint, quarterly or semiannual, to catch quiet drift.

That last one deserves emphasis. Prompts and model APIs can shift behavior over time without any code change, which is why regression suites are scheduled rather than event-only (Statsig, Evidently AI). If your last golden-set run is older than the oldest case you have added since, the comparison you are about to trust is stale.

Two disciplines make the triggers work. Keep thresholds in a file under version control, so a threshold change is a reviewable diff, and log every threshold change with its reason. And when a re-run flips a verdict, treat that the same as a code regression: open a ticket, fix or re-block, and confirm the fix with a fresh run.

For teams running several models across text, images, and embeddings, the practical burden of re-runs is mostly plumbing. Pointing every candidate at the same endpoint and the same judge reduces the runner to a configuration change; Quanta Inference exposes text, image, video, audio, and embedding models through one OpenAI-compatible API key (https://quantaintelligence.ai/inference), which keeps the manifest identical across candidates while only the model string varies.

FAQ

FAQ

Can I use my existing prompt evaluation dataset for a model swap?

Yes, and you should. The cases, context, and expected behavior do not change when you swap the engine, so the same set works. The only preparation needed is intent tagging, because the comparison runs per intent rather than in aggregate (Langfuse).

How many cases do I need per intent before a threshold is meaningful?

Enough that one flipped case does not swing the rate by several points, which usually means at least twenty to thirty. Intents with fewer cases should be pooled, marked advisory, or expanded before you trust a gate on them. A curated set of 50 to 100 cases is a reasonable starting scope for the whole evaluation (qaskills.sh).

What counts as a failed comparison that should block the swap?

Any intent whose candidate pass rate falls below its written threshold blocks the swap, regardless of how good the aggregate looks. Partial routing is available as a middle path: send passing intents to the cheaper model, keep the incumbent for failing ones. The block-and-route decision should be logged with the per-intent table attached (Arize). You may also want to see the full picture at https://quantaintelligence.ai/ if the swap is part of a wider build.

Should I change the prompt to suit the cheaper model?

Not in the same run. Change one variable at a time; otherwise a score movement cannot be attributed to the model or the prompt (Microsoft Q&A). Establish the model-only comparison first, then tune the prompt for that model as a separate change with its own evaluation.

How often should the comparison be re-run?

At minimum on every candidate change, every dataset change, every judge change, and every route or retrieval change, plus a calendar checkpoint for drift. Models and APIs can shift behavior without your code changing, which is why a scheduled regression run complements event-driven ones (Statsig, Evidently AI).

Sources

Sources

  1. How to Build a Golden Dataset for LLM Evaluation
  2. Datasets | DeepEval - The LLM Evaluation Framework
  3. Golden dataset evaluation: build and maintain LLM test sets
  4. AI Agent Regression Testing From Production Failures | Arthur
  5. How to reduce LLM costs without sacrificing quality
  6. How Do You Organize Prompt Versioning in Azure AI Foundry?
  7. Mastering Prompt Versioning: Best Practices for Scalable LLM ...
  8. Prompt regression testing: Preventing quality decay
  9. Feature Flags Best Practices (Feature Toggles)
  10. Feature Toggles (aka Feature Flags)
  11. How to reduce costs for LLMs using Braintrust - Articles
  12. A tutorial on regression testing for LLMs
  13. Feature Flags 101: Use Cases, Benefits, and Best Practices
  14. What is a feature flag and why are feature flags used?
  15. Best Prompt Versioning Tools for Production Teams (2026) - Braintrust
  16. Reducing technical debt from feature flags
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles