Now the question practitioners actually ask: should I fuse a keyword retriever with the dense one, add a cross encoder reranker, or both?
Start from why fusion helps. BM25 and dense retrieval fail in complementary ways. BM25 is strong on exact strings like product codes and error identifiers and blind to paraphrase; dense retrieval is the reverse. Combining them by rank rather than raw score, using reciprocal rank fusion, avoids the problem that BM25 scores are unbounded positive numbers while cosine similarity is bounded between -1 and 1, so naively weighted blends let BM25 dominate (a reference on hybrid search and RRF). OpenSearch's write up makes the same point: rank based fusion is stable across differing score distributions and resistant to outliers, where min max and L2 normalization are not (OpenSearch on RRF). A PostgreSQL walk through shows the same architecture with BM25 and dense vectors fused by RRF inside the database (pgEdge).
Reranking is a different lever. It does not add candidates; it reorders them. A cross encoder scoring each (query, chunk) pair cannot scan a large corpus at query time, so the correct pattern is retrieve a wide candidate set of tens to hundreds, then rerank that shortlist (Qdrant's hybrid search with reranking tutorial). That means reranking can only fix what your first stage already fetched. This is the single most useful fact for the decision: if recall@50 is 0.70 and recall@5 is 0.65, a reranker can at best get you to 0.70. If recall@50 is 0.70 and recall@5 is 0.30, a reranker is the obvious next move.
Anthropic's numbers illustrate the composition. Contextual embeddings reduced failed retrievals by 49 percent, and adding contextual BM25 plus reranking reduced failures by 67 percent (Anthropic). Each layer contributed, and the combined effect exceeded either alone. That is a general pattern, but the size of each step is your own data's business.
So use this rule. Run your frozen test set through the current retriever and each candidate, and record recall@k at your generation cutoff, MRR, and p50 and p95 latency.
- Compute the ceiling: recall at the wide candidate cutoff of the cheapest configuration. If your best first stage recalls 0.90 at the wide cutoff, no reranker can exceed 0.90 at the generation cutoff.
- Estimate the reranker's realistic gain as recall@wide minus recall@cutoff, discounted. Ordering gains usually capture part of that gap, not all of it.
- Do the same for fusion: rerun with the keyword retriever added and RRF applied, and measure the delta in recall@k and MRR over the single retriever, plus the extra latency of running two queries and merging.
- Ship if the measured gain in recall@k, or in MRR where ordering is what your task needs, is larger than the latency you add, and if the gain survives on the held out slice. A change that moves recall by less than the noise on 200 queries, roughly a point or two, is not a result.
- If neither fusion nor reranking moves recall@k, the problem is upstream: chunk boundaries, embedding quality, or missing documents. Change the index, not the ranking.
Worked example. Suppose recall@5 is 0.62, MRR is 0.55, recall@50 is 0.81, and p95 retrieval latency is 40 ms. Adding a cross encoder over the top 50 candidates costs, say, 120 ms at p95 and lifts recall@5 to 0.74 with MRR 0.71. Fusion alone costs 25 ms and lifts recall@5 to 0.69 with MRR 0.60. Under the rule, fusion is the cheaper win; the reranker is justified only if your users tolerate the latency and your product depends on ordering, as it does for single answer lookups. If recall@50 had been 0.63, no reranker would have been worth building, and the honest next step would be fixing chunking.
For the latency side, measure on your own hardware and traffic shape. Reranker cost spans orders of magnitude, from a few milliseconds for distilled cross encoders to well over a hundred for large ones on long candidate lists, per a survey of reranker patterns (a comparison of reranker types and costs). The abstraction in that piece also gives the right mental model: a first stage ANN retrieval of a few hundred candidates, then a reranker scoring each against the query, then the top passages to the model.
One more option to test before either: adding context to chunks. Prefixing each chunk with a short note about where it sits in the document, generated by a model, improved top 20 chunk retrieval failure rate by 35 percent averaged across data sources in Anthropic's testing (the Claude cookbook). It is an indexing change rather than a query time change, so it costs nothing in latency. If your chunks read as fragments, try it before adding infrastructure.