Why the top result is not always the best result
By b5 Recall retrieves by meaning, filters by metadata, and fuses dense with sparse. But every one of those still ranks by comparing precomputed vectors, and a vector is a lossy summary. Two passages can look equally close to a query in embedding space when a careful reader would rank one far above the other. Reranking is that careful reader - a model that looks at the query and a candidate together and scores their real relevance. The catch is it is slow, so you only ever run it on a shortlist. That constraint shapes the whole design.
Bi-encoder vs cross-encoder, and the two-stage funnel 9 min live
The retriever you have used all track is a bi-encoder: it encodes the query and each document separately into vectors, then compares them with cosine. That is why it is fast - document vectors are precomputed once and reused forever. A cross-encoder is different: it feeds the query and one document together through a single transformer and outputs a query-specific relevance score. Far more accurate, and far too slow to run over a whole corpus. So you stage them.
LiveBi-encoder vs cross-encoder, side by side3 min▶
The difference is when the query and document meet:
- Bi-encoder (your retriever). Query and document are encoded into vectors independently. Document vectors are computed once at index time, so a query is just "embed the query, cosine against the precomputed vectors". Fast and scalable - but the two texts never interact, so subtle relevance is lost.
- Cross-encoder (the reranker). The query and one document are concatenated and passed through the transformer together, so attention can compare them token by token. The output is a single relevance score for that exact pair. Much more accurate - but nothing is precomputable, so you must run a fresh forward pass for every candidate.
LiveWhy you rerank the shortlist, not the corpus3 min▶
The whole two-stage design exists because of latency. A bi-encoder vector search over millions of chunks returns in well under 100ms because the heavy work happened at index time. A cross-encoder has no precomputed anything - it runs a full model pass per query+document pair.
- The numbers force it. Reranking on the order of 40 million records with a cross-encoder on a V100 GPU would take more than 50 hours for a single query. You cannot rerank a corpus. You can rerank 150 candidates in a blink.
- So you funnel. Stage 1 (bi-encoder) casts a deliberately wide net - Anthropic's contextual-retrieval work retrieves the top ~150 chunks - precisely so the right answer is somewhere in the candidate set even if it is not #1. Stage 2 (cross-encoder) rescores those ~150 and keeps the top ~20 for the generator.
- Recall then precision. Stage 1 optimizes recall (get the right chunk into the net); stage 2 optimizes precision (put it at the top). Neither model could do both jobs affordably alone.
Wider net, then sharper sort. A team (anonymized) was retrieving k=5 straight from the bi-encoder and complained the right doc was "usually there but rarely first". Rather than tune the embedder, they retrieved 100 candidates and added a cross-encoder rerank down to 5. Same embeddings, same corpus - the correct chunk moved from a scattered rank into the top two on most queries. The fix was staging, not a better model.
Self-studyAlso: stuffing too many chunks hurts the LLM2 min▶
There is a second reason reranking matters: you cannot just crank k up to be safe. Handing the generator 50 loosely relevant chunks does not raise accuracy - it lowers it. The model spreads attention across noise, is more likely to cite the wrong passage, and every extra chunk costs tokens and latency. Reranking lets you retrieve wide (high recall) yet feed the LLM a small, high-precision set - the best of both instead of a bad trade.
MMR diversity and query expansion / HyDE 7 min live
Two more levers shape what reaches the generator. Query expansion / HyDE works at the input side: widen or rewrite the query so stage 1 retrieves chunks the raw question would have missed. MMR works at the output side: trim near-duplicate results so your final set is diverse rather than five phrasings of the same fact. One improves recall, the other improves the usefulness of the top-k.
LiveQuery expansion and HyDE3 min▶
A short or oddly worded query retrieves poorly - the b1 lesson that vague queries are where retrieval is weakest. Query expansion fixes it before retrieval runs, and the DeepLearning.AI Chroma course teaches two flavours:
- Expansion with related terms. Use an LLM to add synonyms and related phrasing to the query, so the embedding lands nearer more of the relevant chunks. "refund?" becomes "refund, return policy, money back, days to process" - a richer vector, a wider net.
- HyDE (hypothetical document embeddings). Ask an LLM to write a plausible answer to the question, then embed that hypothetical answer instead of (or alongside) the question. Answers look like documents, so an answer-shaped vector matches real answer passages better than a question-shaped one. It directly attacks the b1 problem that questions rarely share words with their answers.
LiveMMR: maximal marginal relevance2 min▶
A wide retrieve often returns near-duplicates: three chunks that all state the same fact in slightly different words. Feeding all three to the generator wastes context on redundancy and can crowd out a different, also-relevant chunk. MMR (maximal marginal relevance) picks results that are relevant to the query and different from what it has already picked - each new chunk must earn its slot by adding something new.
- What it does. Instead of taking the top-k by pure similarity, MMR balances relevance to the query against dissimilarity to the already-selected results, trimming redundant near-duplicates.
- When to reach for it. Broad questions ("summarize what went wrong across incidents") where you want coverage of distinct points, not the single best point repeated. Most frameworks expose it as a search type - LangChain has a
search_type="mmr"retriever option.
Live★ See it: top-k now, imagine the rerank4 min▶
This is the bi-encoder stage over Corpus B - the raw, un-reranked top-k you have used since b4. Run a broad query and read the list as a candidate set, not a final answer: is the truly most relevant incident at #1, or merely in the net? Are there near-duplicates a cross-encoder would reorder and MMR would thin?
Pick a query where #1 looks arguable, then describe in one line how a cross-encoder reading each query+incident pair together would likely reorder it, and which near-duplicate MMR would drop.
Reorder a result set on paper ★ 10 min · the Corpus B playground
You do not need a cross-encoder loaded to reason like one. Use the Corpus B playground above and practise the two-stage instinct by hand.
Find a near-duplicate pair. Run a broad query (e.g. "payment problems") at k=5 and look for two results that say almost the same thing - two payment-outage incidents, say. Note their ranks.
Rerank on paper. Reading each query+incident pair carefully (as a cross-encoder would), decide which of the two is genuinely more relevant to the exact question, and write the order you would put the top-5 in. Note where it differs from the cosine order.
Apply MMR. From your reranked list, drop the near-duplicate that adds the least new information and pull up the next distinct incident. That is MMR by hand - relevance plus diversity.
Expand the query. Take a short query that retrieved poorly and rewrite it with related terms, or sketch a one-sentence hypothetical answer (HyDE). Say which chunks that richer query would now reach that the terse one missed.
Before session b7 ◐ 40 min total
- Add a cross-encoder rerank to your b4 retriever: retrieve the top ~50 with the bi-encoder, rescore with a
sentence-transformerscross-encoder (or a hosted reranker), keep the top 5. Compare hit rate before and after. - Switch your LangChain retriever to
search_type="mmr"on a broad query and confirm the returned chunks are more varied than pure similarity gave you. - Implement a simple query expansion: ask an LLM to rewrite a terse query with related terms, retrieve with both, and note which chunks the expanded query reached that the original missed.
- Read: the Pinecone reranking guide, and the DeepLearning.AI "Advanced Retrieval for AI with Chroma" lessons on query expansion and cross-encoder reranking.
Official sources covered
Taught from official docs and a vendor course. This page covers ~80% of their working content on reranking and query-side recall tricks - the model-training internals and hosted-reranker billing stay with the source.
Three questions before you go 🎯 ◐ 90 seconds
1 · How does a cross-encoder differ from a bi-encoder?
A bi-encoder encodes query and doc separately (fast, precomputable). A cross-encoder reads the pair together for an accurate, query-specific score - too slow to run over a whole corpus.
2 · Why do you rerank only a candidate set (e.g. ~150) rather than the whole corpus?
Cross-encoders have nothing precomputed - reranking ~40M records on a V100 would take 50+ hours. Retrieve wide fast, then rerank the shortlist.
3 · What problem does MMR (maximal marginal relevance) address?
MMR trims redundant near-duplicates by favouring results that are both relevant to the query and different from those already selected.