Click any words you think contain an error. Click again to unmark.
The query path forgets the 'query:' prefix that BGE-M3 was trained to expect, projecting queries into the wrong region of vector space and silently dropping recall.
Imagine two postal services that both deliver mail, but each requires a sticker on the front of the envelope. One sticker says 'Outgoing', the other says 'Incoming'. The post office uses those stickers to sort. If you forget the sticker, the envelope still leaves the office, still gets dropped at addresses, but it ends up at the wrong houses because the sorter could not tell which pile it belonged to. That is what is happening here. The docs went out with the right sticker, the queries went out bare, and the matches landed in a slightly different neighborhood.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
This snippet is the canonical embedding-pipeline bug in 2026 production code. Two functions, one missing prefix, no error, and a quiet 5-15% recall drop that survives QA. The bug is interesting because it teaches several things at once: how asymmetric encoders are trained, why the input contract matters, what detection looks like when the failure is silent, and how to structure code so the bug cannot recur.
The walkthrough below isolates the defect, explains why it costs recall, lays out the three layers of detection a mature stack uses, and ends with the structural fix that closes the class.
Isolating the defect
Read the two paths side by side
The index path:
texts = [f'passage: {d.text}' for d in docs]
vecs = embed_model.encode(texts, normalize_embeddings=True)
The query path:
q_vec = embed_model.encode(query, normalize_embeddings=True)
The asymmetry is the bug. The index side wraps each document with 'passage: '. The query side encodes the raw query string. The vector DB call after each is fine. The encoder call after each runs to completion.
What the diff should look like
The correct query path mirrors the index path:
q_vec = embed_model.encode(f'query: {query}', normalize_embeddings=True)
One line. One token. Tens of millions of incorrectly-routed query vectors over a six-month deploy.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- BGE-M3 (BAAI) trained with explicit query: and passage: prefixes; the model card warns about prefix mismatch.
- E5-large-v2 and E5-Mistral-Instruct (intfloat) ship with the same two-prefix contract and document the recall hit on mismatch.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you detect this bug if you cannot afford a labeled relevance set?
Sample 200 queries and their top-1 results before and after adding the prefix, score whether the top-1 changed, and inspect a stratified sample. Large flip rate with no obvious win is the signal.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Asserting the code is fine because it runs and returns results. Silent recall drops are the signature of a prefix mismatch, not a thrown error.
60 second bullets to scan on the way to the call.
Which embedding model families require query: and passage: prefixes
Why a missing prefix produces a silent recall drop, not an error
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.