A/B and shadow-deploy a model swap behind a Foundry agent¶
A new model is tempting, but "it felt better in a notebook" isn't evidence. Swap models the way you ship risky code — shadow first, then a small slice of live traffic, then promote on numbers.
Stage: Foundry · For: Developer, IT/admin · Level: Advanced · Time: 90 min
Treat a model swap like a production change
A model change can shift quality, latency, cost, and behaviour all at once — sometimes invisibly. The routing and deployment mechanics below are representative; confirm the current deployment and traffic options against the Foundry docs and your hosting setup. Never flip a production agent to a new model in one step on a hunch.
When to use this¶
A new base model shipped, or you fine-tuned one, and you want to adopt it without gambling your production quality. Shadow and A/B let you measure the new model on your real traffic and your real metrics before it ever influences a user — so the promotion decision is evidence, not vibes.
What you'll need¶
- A working pro-code agent in production (or production-like) traffic.
- The candidate model deployed and reachable (a new base model or a tuned deployment).
- Your evaluation suite and live metrics: quality, latency, cost, error rate.
- A routing layer you control — to mirror traffic (shadow) or split it (A/B) by config, not redeploy.
- Agreed promotion criteria written down before you look at the results.
Try it now — shadow then split¶
Keep the current model serving users; run the candidate in parallel and compare. Representative shape:
import random
CURRENT = "gpt-4o" # serving users today
CANDIDATE = "gpt-4o-mini" # or your fine-tuned deployment
MODE = "shadow" # "shadow" -> "canary" -> "ab" -> "promote"
CANARY_PCT = 0.05 # for canary/ab modes
def answer(thread_id: str):
# The user ALWAYS gets the current model's answer during shadow.
primary = run_with_model(thread_id, CURRENT)
if MODE == "shadow":
# Run the candidate on the same input, log its result, return NOTHING to the user.
candidate = run_with_model(thread_id, CANDIDATE)
log_comparison(CURRENT, primary, CANDIDATE, candidate) # quality/latency/cost/tokens
return primary
if MODE in ("canary", "ab") and random.random() < CANARY_PCT:
out = run_with_model(thread_id, CANDIDATE) # a small slice sees the candidate
log_serving(CANDIDATE, out)
return out
return primary
Why this works: shadow mode gives you a head-to-head on identical inputs with zero user risk — the candidate's answer is logged, never served. Only once shadow looks good do you let a small, controlled slice of real users see it.
Step by step¶
- Write the promotion criteria first. Decide the thresholds — quality must be ≥ current, latency and error rate within bounds, cost acceptable — before you see any numbers, so the decision isn't motivated.
- Shadow the candidate. Mirror live inputs to the candidate, log quality/latency/cost/tokens against the current model, and serve users only the current model. Let it run long enough to cover real variety.
- Read the comparison honestly. Compare distributions, not just averages — and look hard at the worst cases and any behaviour changes (tone, format, refusals), which averages hide.
- Canary a small slice. If shadow passes, route a few percent of real traffic to the candidate. Watch live metrics and user signals closely; keep the rollback one config change away.
- Widen to a true A/B if you need user-outcome evidence. Split traffic and compare downstream outcomes (resolution, deflection, satisfaction), not just model-graded quality.
- Promote or roll back on the numbers. Meets the pre-agreed criteria → promote the candidate to the default and keep watching. Doesn't → roll back; the shadow logs already tell you why.
Screenshots¶
We deliberately don't ship screenshots that go stale — the Microsoft Copilot UI changes often. Follow the numbered steps above, which we keep current. Maintainers can regenerate fresh captures with the Playwright tool in tooling/screenshots/.
Make it better¶
- Automate the gate. Wire the shadow comparison into CI/CD so a candidate can't be promoted unless it clears the thresholds automatically.
- Pin versions explicitly. Don't let "latest" silently change the model under you — promote specific versions so every change is deliberate and traceable.
- Keep a fast rollback. Make reverting to the previous model a single config flip, tested, so a bad promotion is a non-event.
Watch out for¶
- Averages lie. A new model can win on the mean and lose catastrophically on a slice you care about. Always inspect the tail and segment by use case.
- Behaviour drift is silent. Quality scores can hold while tone, verbosity, or refusal behaviour shifts. Eyeball real outputs, not just metrics.
- Shadow doubles cost while it runs. You're paying for two models on shadowed traffic — scope the duration and sampling, and remember to turn it off.
- Don't A/B without enough traffic. Tiny samples give noisy verdicts. If volume is low, lean on offline evaluation and shadow rather than a underpowered A/B.
Where this leads (the frontier)¶
Safe model swaps are what make fine-tuning and model upgrades routine instead of scary. Combine this with continuous evaluation for the quality signal and cost optimization to weigh the economics of every swap.
Related¶
- Fine-tune a domain model and serve it — a common source of the candidate model
- Evaluate and continuously monitor a Foundry agent — the metrics your gate reads
- Token-budgeting and cost optimization — the cost side of a model decision
Thanks — glad it helped. Spotted something to improve anyway? Suggest it →
Sorry this didn't land. Tell us what broke → so we can fix that rung of the ramp.
Found an issue or want to suggest an improvement? Open a GitHub issue · Star on GitHub