Why does switching embedding models silently break my agent's retrieval?

You upgraded to a better embedding model and recall fell off a cliff, with no error in the logs. Here is why a model swap quietly poisons a vector index, and how to migrate without breaking search.

B

Balagei G Nagarajan

4 MIN READ


The same documents mapped into two different vector spaces by two embedding models, so a query fails to connect to its true match
Hard error the moment a query hits the index: dimension X does not match dimension Y.
— from “Why does switching embedding models silently break my agent's retrieval?”
 <p><b>Key facts.</b></p>
 <ul>
   <li>Each embedding model learns a distinct, non-interoperable vector space; a cosine similarity between a query from one model and a vector from another is geometrically meaningless (Qdrant and Weaviate migration docs; OpenAI developer community).</li>
   <li>OpenAI's text-embedding-ada-002 vectors are not compatible with text-embedding-3 vectors; there is no upgrade transform, re-embedding is required (OpenAI developer community, 2024).</li>
   <li>Vector databases enforce a fixed dimension per collection: a dimension change throws a hard error, but a same-dimension model swap fails silently.</li>
   <li>Re-embedding a large corpus is real work that teams underestimate: one worked example puts a 50-million-document corpus at roughly 25 billion tokens to re-embed (reported, Tensoria 2026).</li>
 </ul>
 <h2>What does this failure look like in production?</h2>
 <p>One of two ways. Different vector dimension? Hard error the moment a query hits the index: dimension X does not match dimension Y. Annoying, but safe, because it stops you. Same dimension? Nothing errors. The index accepts the queries, hands back nearest neighbors, and those neighbors are wrong. Recall collapses without a trace. The agent starts grounding on irrelevant chunks, answers look fluent, everything is off. Teams spend days blaming the model or the prompt before someone checks whether the embedding swap was the real cause.</p>
 <h2>Why are two embedding models incompatible at all?</h2>
 <p>Every model encodes meaning as its own geometry. During training, it maps words and phrases onto directions, clusters, and distances that are specific to that model's architecture and data. Swap to a different model and those directions shift. Two models can both output 1536-dimensional vectors and still put the same sentence at opposite ends of the space. Comparing a query from model B against documents from model A is like plotting GPS coordinates from one map system onto a completely different one. The arithmetic runs fine. The answer is wrong. No reliable linear transform gets you from one production embedding space to another, which is why re-embedding the corpus is the only real fix.</p>
 <div class="fig"><img src="/blog/article2-diagram.png" alt="The partial migration trap: an index built with model A, a query embedder swapped to model B landing in a different space, a meaningless comparison, and the fix of re-embedding the corpus"/></div>
 <h2>What is the partial-migration trap?</h2>
 <p>The most common version of this failure is subtle. A developer updates the query embedder to a newer model but leaves the existing index, built on the old model, untouched. Now every search embeds the query in space B and compares it against documents in space A. Dimensions differ? Error. Dimensions match? Silent recall collapse. There is also a nastier provider-side variant: an embedding API quietly routes to an updated model, invalidating your whole index with no code change and no error on your side. Both produce plausible-looking but semantically wrong results. Both erode trust without ever crashing.</p>
 <h2>How do you migrate embedding models safely?</h2>
 <table style="width:100%;border-collapse:collapse;margin:1.5rem 0;font-size:0.97rem;"><tr><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">Risk</th><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">Signal</th><th style="text-align:left;padding:10px 12px;border-bottom:2px solid #e5e7eb;color:#5E6AD2;font-weight:600;">Fix</th></tr>
   <tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Dimension change</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Hard "dimension mismatch" error</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Re-embed corpus; rebuild index with new dimension</td></tr>
   <tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Same-dimension swap</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Silent recall collapse</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Full re-embed; never mix vectors from two models</td></tr>
   <tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Partial migration</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Query embedder newer than index</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Version the model with the index; startup assertion</td></tr>
   <tr><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Provider silent update</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Quality drops with no deploy</td><td style="padding:10px 12px;border-bottom:1px solid #eee;vertical-align:top;">Pin model version; monitor retrieval metrics</td></tr>
 </table>
 <p><!-- conviction-start -->New embedding model, new geometry; the upgrade misranks silently.<!-- conviction-end --></p><p>Treat the embedding model as a versioned dependency of the index, not a swappable black box. Persist raw chunks separately so re-embedding is just an embed-and-index step. Tag every collection with the exact model and version. Run a blue-green migration: keep the old index serving live traffic while you build and validate the new one, dual-write updates, then cut over only after a golden set of queries clears your Recall@k and nDCG bar. Modern vector databases ship primitives for this, Qdrant named vectors and Weaviate collection aliases, because the problem is common enough to warrant first-class support. Knowing before you cut over where the new embeddings hold up and where they degrade is the kind of pattern-level reliability VibeModel builds as the Pattern Intelligence Layer.</p>

 <aside class="jaside v-warn"><span class="col"><svg class="hook" width="28.5" height="34.5" viewBox="0 0 57 69" fill="none" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"><path fill="var(--pageBg)" d="M54 0V0.716804C54 25.9434 35.0653 47.1517 10 50L0 57V0H54Z"/><path fill="var(--acc)" d="M56.9961 4.15364C57.0809 2.49896 55.8083 1.08879 54.1536 1.00394C52.499 0.919082 51.0888 2.19168 51.0039 3.84636L56.9961 4.15364ZM9.09704 51.7557L8.49716 48.8163L9.09704 51.7557ZM6 69V59.2227H0V69H6ZM9.69692 54.6951L14.3373 53.7481L13.1375 47.8693L8.49716 48.8163L9.69692 54.6951ZM14.3373 53.7481C38.202 48.8777 55.7486 28.4783 56.9961 4.15364L51.0039 3.84636C49.8967 25.4384 34.3213 43.5461 13.1375 47.8693L14.3373 53.7481ZM6 59.2227C6 57.0268 7.54537 55.1342 9.69692 54.6951L8.49716 48.8163C3.55195 49.8255 0 54.1756 0 59.2227H6Z"/></svg><span class="rail"></span></span><span class="glyph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><h4>Watch out</h4><p>The index accepts the queries, hands back nearest neighbors, and those neighbors are wrong.</p></aside>

Frequently asked questions

Can I just convert my old vectors to the new model?
No. There is no reliable transform between two production embedding spaces. Research prototypes for unsupervised space translation exist but are not production-ready. Re-embedding the corpus with the new model is the dependable path.

Why did my retrieval break with no error after a model upgrade?
The new model shared the old vector dimension, so the database accepted the queries instead of erroring. It compared new-space queries against old-space vectors, which is meaningless, so it returned wrong neighbors and recall quietly collapsed.

Is it safe to mix old and new vectors in one index?
No. Vectors from two different models are not comparable, so mixing them corrupts similarity search. Keep one model per index, or run separate indexes during a blue-green migration.

How do I catch this before users do?
Version the embedding model alongside the index and add a startup or query-time assertion that fails fast on a mismatch. Track Recall@k and nDCG on a fixed golden query set so a silent drop trips an alert.


Share this post

Join the discussion

Have a take, a war story, or a question? Sign in with GitHub to comment and react. Comments are powered by GitHub Discussions, ad-free and yours to moderate.

Continue Reading

Find where your agent breaks, before you build it

Faultmap maps where your agent will fail from the goal and your data, then hands you the first test suite it has to pass.