Google retired the gemini-2.0-flash snapshot three days after our live demo shipped. Queries started returning 404 with zero changes on our side: no deploy, no config drift, nothing in the git history to bisect. The model had stopped existing, on a schedule we did not set and were not consulted about.

That is the fact to internalize about hosted models: the model is a dependency with someone else's deprecation schedule. You will migrate whether you planned to or not. And whether that migration is a config change or a rewrite gets decided months before it happens, by three structural choices: a single routing seam, per-task fallback chains, and an eval gate.

Diagram of the routing seam: RAG, guardrails, agents, and evals call one adapter with per-task primaries and fallbacks; a retired snapshot is repointed to a -latest alias, and an eval gate checks every routing change

Migrations come in three flavors

Voluntary, forced, and economic. We have run all three in the past year.

The voluntary kind looks like the financial-analytics platform we work on, which moved from Gemini 2.5 to 3.5 across multiple services under live traffic: RAG indexing, AI guardrails with PII anonymization, and the eval-scoring pipelines. The platform holds 99.9% API availability, so taking ingestion offline for a week to swap models was never an option.

The forced kind is the snapshot retirement above. Our notice period was the 404s.

The economic kind is quieter: a cheaper model appears that clears your quality bar, and staying put means paying a margin tax out of habit.

The useful part is that all three are the same operation. Repoint task types at a different model, prove quality held, adjust the operational envelope. Build for the forced case, which arrives with the least warning, and the voluntary and economic cases come nearly free.

One seam, not thirty call sites

The decision that made the 2.5 to 3.5 migration survivable is written down as an ADR: every LLM call on that platform goes through LiteLLM as a single adapter, deliberately instead of per-provider SDKs. The RAG indexer does not know it talks to Gemini. The guardrail service imports no Google client. Each service asks for the model assigned to its task type, and the router resolves that to a primary model plus a fallback chain.

The seam converts "rewrite every call site" into "change one config." A migration becomes an edit to a routing table, shipped and rolled back like any other deploy. The fallback chain covers the minutes nobody is watching: one of our systems degrades to a smaller model on rate limit instead of queueing, because a slightly worse answer now beats a perfect answer after the queue backs up.

The seam has a price. Provider-specific features have to be threaded through the adapter or skipped, and every latency investigation crosses one extra hop. We pay it, because the alternative is a provider SDK import in every service, which is exactly the shape of codebase that turns a deprecation email into a quarter of rework.

Pager insurance and quality insurance

An alias or a fallback is pager insurance. An eval gate is quality insurance. You need both, because a response coming back tells you nothing about whether the response is still good.

The pager side first. Our durable fix for the snapshot retirement was repointing the demo to the gemini-flash-latest alias, so future snapshot retirements resolve to the current model instead of a 404. The hotfix commit and the alias commit are both cited in the week-one incident log, which is worth reading if you want the timeline rather than the moral.

The quality side is a discipline, and it is cheap to state: routing changes and quality checks travel in the same commit. When the demo's routing policy moved to Gemini, the eval suite's assertions moved with it, in one diff. We treat evals as CI, so proving the new model still works is one green CI run instead of an afternoon of manual spot checks. If proving quality takes an afternoon of human attention, you will skip it exactly once, on the migration where it mattered.

Migrate the budgets and the schemas too

Quality parity covers half the checklist. Two things bit us that had nothing to do with answer quality.

Latency first. A reasoning model spends around 9 seconds thinking before it emits a token. Our time-to-first-token budget was tuned for a chattier model, and it declared healthy requests dead. We raised the budget to 20 seconds. The general rule: timeouts tuned for model A are latent bugs waiting for model B. Latency characteristics are part of model drift, and they belong in the migration checklist next to the prompt templates.

Schemas second. When we migrated embeddings to gemini-embedding-001 at 768 dimensions, no alias could save us, because vectors from different embedding models do not share a space. That migration shipped only after an eval spike showed retrieval held up at 768 dimensions. The mechanics of why the index is the expensive part are in how RAG works in production.

The quarter before the migration

None of this machinery can be built during the incident. The seam, the fallback chains, the alias policy, and the eval gate all went in during calm weeks, and each looked slightly like overengineering at the time. Then a snapshot retired three days after a deploy, and the whole stack of decisions cashed out as one config line and a green CI run.

If you run models in production, assume the deprecation email is already scheduled. Route every call through one seam, give each task type a fallback chain, pin to aliases rather than snapshots where the provider offers them, and make quality proof a CI job.

The systems behind this article are summarized in the operator brief, and the demo that took the 404s is still answering queries at the live demo, alias and all.