Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Chapter 2 told one side of a story: word-overlap retrieval lost to embeddings on a question about “holidays” that shared zero words with the document that answered it. Meaning beat spelling. This chapter tells the other side: there are questions where embeddings lose outright to plain keyword matching, and the reason is just as mechanical as Chapter 2’s was.

Chapter 7 of 10 · Better retrieval

Where we left off: Chapter 2 told one side of a story: word matching lost to embeddings on a question about “holidays” that shared zero words with the right document.

What we fix now: See the other side: a question where embeddings lose outright to plain keyword matching, then combine both methods with Reciprocal Rank Fusion.

By the end, you can:

  • explain why a tokenizer that drops digits makes embeddings blind to codes and numbers

  • build BM25 from scratch and combine it with embeddings using Reciprocal Rank Fusion

  • identify a real failure mode of RRF itself: a meaningless tied score still gets a real rank

Source

Embeddings confidently return the wrong section. The reason is mechanical, not mysterious: the tokenizer used for embeddings keeps letters only, so “4477” disappears from the question entirely before embedding even starts, and “extension” was never one of the roughly 200 words the toy embedding model was trained on in the first place. What’s left, “what,” “department,” “is,” “for,” carries almost no real signal, so whatever the embedding model returns is closer to a guess than a retrieval.

Before · embeddings alone

Same question: “what department is 4477”

Top result: Remote Work Policy at 0.560, the wrong section, and IT Support doesn’t even appear in the top 3. “4477” vanished before embedding even started, letters-only tokenization drops it entirely.

After · BM25 keyword search

Same question: “what department is 4477”

Top result: IT Support at 1.860, a wide margin over everything else. BM25 keeps digits, so “4477” survives as an exact, distinctive token that only appears in one section.

Real keyword search: BM25

Chapter 1 used a naive word-overlap count. Real keyword search systems use something more careful: BM25, which scores a document higher when it contains a query term more often (with diminishing returns, the fourth mention doesn’t matter as much as the first), adjusted for how long the document is, and weighted by how rare that term is across the whole collection (a common word like “the” barely counts; a rare word like “helpdesk” counts for a lot). It’s simple enough to build from scratch and still genuinely used in production search today, often as one half of a hybrid system.

BM25 finds it immediately, and by a wide margin: “4477” and “extension” are exact, distinctive tokens that only appear in one section, exactly the kind of signal keyword search is built for and embeddings, in this toy model, can’t represent at all.

Combining both: Reciprocal Rank Fusion

Neither method should simply replace the other, Chapter 2’s “holidays” question still needs embeddings, this chapter’s “4477” question needs BM25. The two scores can’t just be added together directly: cosine similarity lives between -1 and 1, BM25 scores are unbounded and on a completely different scale. Reciprocal Rank Fusion (RRF) sidesteps that entirely by ignoring raw scores and combining rank positions instead: a document gets 1 / (k + rank) points from each method it was retrieved by, added together across methods.

Hybrid search gets it right. Now the other direction, to make sure nothing regressed: Chapter 2’s original synonym question, the one embeddings needed to solve in the first place.

Still correct. Hybrid search didn’t have to choose between Chapter 2’s win and this chapter’s win, it keeps both, because each method still contributes its own ranking to the fusion regardless of what the other one thinks.

Quick check

Before moving on, answer for yourself (no peeking ahead):

  1. Why does the embedding tokenizer losing digits matter so much more for the “4477” question than it would for a typical question about vacation or sick leave?

Check your reasoning ▸

For a typical question, plenty of other words (“vacation,” “sick,” “carry over”) still carry real signal even if a stray number gets dropped. For the “4477” question, the number is essentially the entire meaningful content: once it’s stripped out, almost nothing distinctive is left for the embedding model to work with.

  1. Why can’t cosine similarity scores and BM25 scores just be added together directly? What does RRF compare instead, and why does that sidestep the problem?

Check your reasoning ▸

Cosine similarity is bounded between -1 and 1, while BM25 scores are unbounded and on a completely different scale, so adding them directly would let whichever score happens to have bigger numbers dominate for no principled reason. RRF sidesteps this by ignoring raw scores entirely and combining rank positions instead, which sit on the same scale (1st, 2nd, 3rd) no matter which method produced them.

  1. The bare “4477” query broke RRF even though BM25 alone got it right. What specifically went wrong, and is that BM25’s fault, the embedding model’s fault, or RRF’s fault?

Check your reasoning ▸

A strong answer traces the mechanism precisely: the embedding side produced a meaningless, tied 0.0 score across every section, but RRF still assigns that meaningless ranking a real rank position, and that arbitrary rank ends up outweighing BM25’s genuinely confident, correctly ranked result. It’s really a gap in RRF’s assumption that every method’s ranking carries some signal, not a bug in either individual method.

  1. If you were building a real system and knew your users mostly asked natural-language questions with the occasional order number or product code mixed in, would you lean more on tuning k, improving the embedding model’s vocabulary, or something else entirely?

Check your reasoning ▸

There’s no single right answer here, but a strong response reasons about trade-offs: tuning k reweights an existing symptom without fixing the cause, improving vocabulary helps but numbers and codes are an open-ended set no fixed vocabulary fully covers, and “something else” (like detecting when a query is mostly non-word tokens and leaning harder on BM25 for it) targets the actual mechanism instead of working around it.

  1. Chapter 2 showed embeddings winning where word-matching failed (“holidays” vs “vacation”). This chapter shows the opposite: embeddings losing where keyword matching wins. Does this contradict Chapter 2, or is something else going on?

Check your reasoning ▸

It doesn’t contradict Chapter 2, it completes the picture. Chapter 2’s example needed meaning, two different spellings, one idea, while this chapter’s example needs an exact token match, a number that only ever means one specific thing. Both are real, and a strong answer recognizes there’s no single method that’s simply “better,” which is exactly why hybrid search combining both exists.


You can now: combine embeddings and keyword search with Reciprocal Rank Fusion, and you’ve seen a real case where combining them still breaks: a query with no usable signal from one side poisoning the fused ranking.

But one problem remains: every retrieval method in this series so far has worked the same way: embed the question, embed each candidate independently, compare the two fixed vectors after the fact. That misses signal a closer look could catch.

Next → Chapter 8: Re-ranking