RAG · GenAI Bootcamp 1.0 · 26 concepts

The Retriever Analogy Handbook

Every retrieval concept in the RAG pipeline, taught through one analogy at a time — with the mapping, the LangChain code, and where the metaphor breaks.

26 concepts Find · Order · Trim

How to use this handbook

Why analogies

Retrieval is unusually hard to teach because nothing in it is visible. Chunks, vectors, ranks and scores are all abstractions with no physical form, so learners nod along and then cannot tell you why a reranker is not a retriever. An analogy gives the abstraction a body. Once you can picture a sous-chef walking to a shelf, you can reason about what happens when the shelf is badly organised.

How this handbook works

Every concept gets one analogy chosen specifically for it, rather than one metaphor stretched across all twenty-six. A stretched metaphor starts lying quickly. Each section then gives you the mapping table (analogy element to technical element), the flow, the LangChain code, and — the part most guides skip — an explicit note on where the analogy breaks down. An analogy you cannot see the edges of is a future misconception.

How to read it

Diagnose first. If you already have a broken RAG answer, start at the use-case chooser: match the symptom, jump to that concept, then open the visual. If you are learning cold: read the story, then the mapping, then the code. If you are teaching: the story and mapping are your slide, the code is your notebook cell, and the 'where it breaks' box is the question a sharp student will ask you. If you are revising: use the cheat sheet, then re-run the matching practical in retriever_advance_colab.ipynb.

The two anchor analogies

Start with these two pictures of the same idea — then open each concept's visual only when you want the full infographic.

Use-case chooser

Name the failure mode first. Then pick the technique — do not start from a favourite algorithm.

Read left to right: symptom → technique → why → notebook. Start with 00_basics_concepts.ipynb once, then open the method notebook for the row you chose. Full originals remain available as retriever.ipynb and retriever_advance_colab.ipynb. All 26 concepts now have a notebook (lab or theory-only) — see method-notebooks/README_INDEX.ipynb.

Problem you are seeingBest technique to try firstWhyNotebook
Exact names, IDs, codes, dates are missedBM25 / SparseExact lexical matching01
User uses different words than documentsDense RetrievalSemantic matching02
Both exact terms + semantic intent matterHybrid RetrievalBM25 + dense complement each other06
BM25/dense result lists need mergingRRFRank-based fusion avoids score-scale problems07
You know one retriever should matter moreWeighted FusionExplicitly control contribution08
Retrieved chunks are repetitiveMMRRelevance + diversity03
Conversational query is incompleteQuery RewritingMakes query standalone09
Different wording causes poor recallMulti-QuerySearches multiple formulations11
Very short/abstract query does not match document languageHyDEConverts query into document-like representation12
One question contains multiple independent questionsQuery DecompositionRetrieve each sub-question separately13
Next search depends on information found in first searchMulti-Hop RetrievalSequential dependent retrieval16
Small chunks retrieve well but lack contextParent Document RetrievalSearch small → return larger parent14
Exact sentence retrieves well but needs nearby contextSentence WindowSearch sentence → expand locally15
Correct docs retrieved but wrong one ranks firstRerankingMore accurate second-stage ranking17
Correct docs contain lots of irrelevant textContextual CompressionRemove irrelevant context18
Tenant/role/year/version restrictions existMetadata Pre-filteringRestrict search before retrieval05
Data exists in SQL + vector DB + web + APIsQuery RoutingSelect correct source26 theory
Answer exists in image/table/diagramMultimodal retrievalText retrieval alone is insufficient25 theory
Practice loop — Choose a row → read the analogy → open the visual → run the linked method notebook (start with 00 basics if the index is not built yet). Ask after each run: Did this fix the failure mode, or only reshuffle noise?
Foundations

The RAG Pipeline, End to End

ANALOGYThe Restaurant Kitchen

Nobody hands the head chef the entire pantry.

The Restaurant Kitchen
Restaurant kitchen analogy: customer order, pantry, sous-chef retriever, head-chef LLM
The Restaurant Kitchen — Concept 01

A customer orders pasta with tomato basil sauce. Behind the door, that order does not go straight to the stove. Raw deliveries arrive at the back and get unpacked and inspected. Vegetables are chopped into usable portions. Everything is put into labelled jars and arranged on organised shelves — because a pantry where nothing is labelled is the same as no pantry at all.

Only when the order ticket lands does the sous-chef walk the shelves and bring back exactly the jars and the one recipe card this dish needs. The head chef then cooks. The chef never sees the other four hundred jars, and does not need to.

The mapping

In the kitchenIn the pipelineWhy it matches
Raw deliveries at the back doorDocumentsUnstructured source material, exactly as it arrives
Unpacking and inspectingParsing / loadingGetting usable text out of PDFs, HTML, Word, scans
Chopping into portionsChunkingWhole documents are too big to use as a unit
Labelled jarsEmbeddingsEach chunk gets a numeric label describing its meaning
Organised shelvesVector storeStorage arranged for fast lookup by that label
The sous-chefRetrieverFetches only what this specific order needs
The head chefLLMTurns retrieved ingredients into the finished dish
The plated dishFinal answerWhat the customer actually receives

The flow

1
Order placed
The user asks a question
2
Sous-chef fetches
The retriever pulls the relevant chunks and nothing else
3
Head chef cooks
The LLM generates using only what it was handed
4
Dish served
The answer is returned, grounded in those chunks
The whole kitchen, in six lines
docs      = loader.load()                                  # deliveries
chunks    = splitter.split_documents(docs)                # prep work
store     = Chroma.from_documents(chunks, embeddings)     # labelled jars on shelves
retriever = store.as_retriever(search_kwargs={"k": 4})    # the sous-chef
context   = retriever.invoke(user_question)               # fetch for this order
answer    = llm.invoke(prompt.format(context=context, question=user_question))
Where the analogy breaks — A sous-chef who finds the shelf empty can walk back, ask, and try again. A basic retriever gets exactly one trip per query and returns whatever was closest, empty-handed or not. The version that goes back for a second trip is multi-hop retrieval (Concept 17) — and it is a deliberate design choice, not the default.

What a Retriever Actually Is

ANALOGYThe Librarian

The librarian finds the books. She does not write your essay.

The Librarian
Library analogy: student question, librarian retriever, teacher LLM
The Librarian — Concept 02

A student walks up to the desk and asks a question. The librarian does not answer it. She walks the stacks, finds the three books that actually address it, marks the relevant pages, and hands them over. The teacher reads those pages and explains the answer.

That division of labour is the whole idea. Suppose your vector database holds one thousand chunks from a set of PDFs and the question is 'What is semantic chunking?'. The retriever does not forward one thousand chunks. It returns chunk 12 (the definition), chunk 48 (an example) and chunk 91 (the advantages). Three chunks go to the LLM. Nine hundred and ninety-seven do not.

The mapping

In the libraryIn RAGNote
The studentUserAsks the question
The books on the shelvesDocuments / chunksThe knowledge that exists
The librarianRetrieverFinds relevant material; generates nothing
The teacherLLMReads what was handed over and produces the answer
How many books she can carrykThe cap on what gets passed forward
The catalogue systemIndex / vector storeWhat makes finding possible at all

The flow

1
Student asks
User query enters the system
2
Librarian searches the stacks
Retriever searches the stored chunks
3
Relevant pages handed over
Top-k chunks returned
4
Teacher explains
LLM generates the final answer from that context
The minimal retriever
retriever = vector_store.as_retriever(search_kwargs={"k": 4})

documents = retriever.invoke("What is semantic chunking?")

for document in documents:
    print(document.page_content)
    print(document.metadata)
    print("-" * 50)
Where the analogy breaks — A real librarian will tell you 'we don't stock anything on that'. A retriever will not. It always returns its top-k, even when the best available match is terrible, because 'closest' is not the same as 'good'. Nothing in the output signals that the match was poor. That silence is what score_threshold exists to break.

search_type and search_kwargs

ANALOGYThe Camera in Manual Mode

Same scene, same camera, different dials, completely different photo.

Camera in Manual Mode
Camera analogy for search_type and search_kwargs
Camera in Manual Mode — Concept 03

Point a camera at a scene and the picture you get depends entirely on the settings. Switch the mode dial and you change which algorithm the camera runs. Inside each mode, a different set of controls becomes available — and some dials are simply greyed out because they mean nothing in that mode.

search_type is the mode dial. search_kwargs are the controls that mode exposes. This is why fetch_k and lambda_mult do nothing under plain similarity search: they are MMR dials, and MMR is not the mode you selected.

The mapping

On the cameraIn search_kwargsWhat it changes
Mode dialsearch_typeWhich retrieval algorithm actually runs
Frames you keepkFinal number of documents returned
Burst frames before you choosefetch_kCandidates considered before MMR selects (MMR only)
Depth-of-field trade-offlambda_multBalance of relevance against diversity (MMR only)
Minimum acceptable exposurescore_thresholdReject anything below this relevance
Lens filter on the frontfilterBlocks whole categories before the shot is taken

The flow

1
similarity
Return the most similar chunks, full stop
2
similarity_score_threshold
Return only chunks that clear the relevance bar
3
mmr
Return chunks that are relevant and different from each other
MMR mode with its dials
retriever = vector_store.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 4,             # final number of documents to return
        "fetch_k": 20,      # candidate documents considered by MMR
        "lambda_mult": 0.5, # relevance vs diversity
    },
)

documents = retriever.invoke("What is a vector database?")
Where the analogy breaks — A badly exposed photo looks wrong immediately. A badly tuned k produces a fluent, confident, wrong answer that looks exactly like a right one. Retrieval failures are silent in a way camera failures are not — which is why logging the retrieved chunks alongside the answer is not optional in production.

Similarity Metrics

ANALOGYTwo Hikers with Compasses

Same two people, three completely different questions about them.

Two Hikers with Compasses
Two hikers analogy for cosine, Euclidean, and dot-product similarity
Two Hikers with Compasses — Concept 04

Two hikers are standing on a hillside. There are three sensible ways to ask whether they are 'the same'. Are they facing the same direction? How far apart are they standing? Or: are they facing the same way and did they both walk a long way to get there?

Those three questions are cosine similarity, Euclidean distance and dot product. They are not competing answers to one question — they are answers to three different questions, and which one is right depends on whether magnitude carries meaning in your embedding space.

The mapping

The questionThe metricBest result
Are they facing the same way?Cosine similarityHighest score — the default for text embeddings
How far apart are they standing?Euclidean distance (L2)Lowest distance
Same direction, and how far did each walk?Dot / inner productHighest score — rewards magnitude too

The flow

1
Cosine
Angle only. Length of the vector is ignored entirely
2
Euclidean
Straight-line distance. Both angle and length affect it
3
Dot product
Direction times magnitude
4
Normalised vectors
If every hiker walked exactly one kilometre, dot product and cosine become identical
Chosen at index creation, not query time
store = Chroma.from_documents(
    chunks,
    embeddings,
    collection_metadata={"hnsw:space": "cosine"},   # or "l2", "ip"
)

# Pinecone, Weaviate and Qdrant expose the same choice when the index is created.
# Changing it later means rebuilding the index.
Where the analogy breaks — Two hikers exist in two dimensions and your intuition is reliable there. Embeddings live in 768 to 3,072 dimensions, where almost every pair of random vectors is nearly orthogonal and 'distance' stops behaving the way the hillside suggests. Use the metric your embedding model was trained with; do not reason it out from the picture.
Narrowing the search

Metadata Filtering

ANALOGYThe Office Security Badge

Semantic similarity gets you into the building. Metadata decides which floors open.

Office Security Badge
Office security badge analogy for metadata filtering
Office Security Badge — Concept 05

Someone asks for 'the leave policy'. By content alone, HR, Finance and Legal all hold documents that talk about leave, and all three are semantically excellent matches. The badge reader does not care what is written inside any of them. It cares about what is stamped on the outside: department, year, document type, clearance level, which company you work for.

That is metadata filtering. It is a structured constraint applied alongside meaning, and it is the only mechanism in the retrieval stack that can enforce a rule rather than express a preference.

The mapping

The badge systemThe retrieverExample
Attributes on the badgeDocument metadata{"department": "HR", "year": 2026}
Reader at each floorMetadata filterApplied during or after the search
Contractors: floors 1–3 onlyRole-based access filter{"access_role": "manager"}
Separate companies in one towerTenant filterHard isolation between customers
Badge expired in 2024Range / date filter{"year": {"$gte": 2024, "$lte": 2026}}
Two conditions on one doorBoolean filter{"$and": [...]}

The flow

1
Exact match
One metadata value must match precisely
2
Range
Numeric or date values inside a window
3
Boolean
AND, OR and NOT across several conditions
4
Source / type / tenant / role
Restrict to a repository, document class, customer or permission level
Filter passed into search_kwargs
retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={
        "k": 4,
        "filter": {"department": "HR", "year": 2026},
    },
)

documents = retriever.invoke("Show the HR leave policy for 2026.")

# Syntax varies: Chroma, Pinecone, Qdrant, Weaviate and Elasticsearch
# all express this differently.
Where the analogy breaks — A human guard applies judgement — an expired badge and an obviously legitimate visitor gets a phone call. A metadata filter is absolutely literal. {"year": 2026} will silently exclude a 2025 policy that is still in force, and the user will never know a better document existed. The real work here is not the filter syntax; it is the quality and consistency of the metadata you wrote at ingestion time.

Pre-filtering vs Post-filtering

ANALOGYVisa Checked at Check-in vs at Immigration

Same rule, applied at a different moment — and the second version is expensive and leaky.

Visa at Check-in vs Immigration
Airport visa analogy comparing pre-filtering and post-filtering
Visa at Check-in vs Immigration — Concept 06

Pre-filtering is the airline checking your visa at the check-in desk. No visa, you never board, and the flight carries only people who are allowed to arrive. Post-filtering lets everyone fly, then immigration at the destination turns four out of five passengers straight around.

The numbers make the difference obvious. With ten thousand documents and a filter for HR and 2026, pre-filtering leaves one hundred and fifty documents and runs the similarity search only on those. Post-filtering runs the search across all ten thousand, returns the top five, and then discards four of them — leaving one result where you asked for five, while more valid HR documents that never made the top five sit unretrieved in the database.

The mapping

At the airportIn retrievalConsequence
Visa checked at check-inPre-filteringSmaller, safer search space
Visa checked at arrivalPost-filteringWasted retrieval, weaker guarantees
Passengers who never boardedDocuments excluded before searchNever enter the candidate set at all
Turned around at the borderResults discarded after searchAlready loaded, already ranked, then dropped
A near-empty arrivals hallToo few final resultsThe classic post-filter failure

The flow

1
Pre-filter
All documents → apply metadata filter → allowed set → similarity search → results
2
Post-filter
All documents → similarity search → top candidates → apply filter → whatever survives
Both, side by side
# Pre-filter: the vector DB applies the constraint during the search
retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4, "filter": {"department": "HR", "year": 2026}},
)
documents = retriever.invoke("Show the HR leave policy for 2026.")

# Post-filter: search first, discard afterwards
documents = vector_store.similarity_search("Show the HR leave policy for 2026.", k=5)
allowed = [
    d for d in documents
    if d.metadata.get("department") == "HR" and d.metadata.get("year") == 2026
]
Where the analogy breaks — At an airport, the passenger without a visa did genuinely travel — and that is exactly the security point people miss. Under post-filtering, restricted documents were really loaded into your candidate set and really scored. If any layer logs, caches or traces that set, the restricted content has left the boundary even though the user never saw it. Pre-filtering is not merely the faster option for access control; it is the only correct one.
How the search runs

Sparse Retrieval

ANALOGYThe Index at the Back of the Book

It matched the letters, not the meaning — and sometimes that is exactly what you want.

Index at the Back of the Book
Book index analogy for sparse / BM25 retrieval
Index at the Back of the Book — Concept 07

Flip to the index, look up 'leave', and you get pages 42, 88 and 130. Fast, deterministic, and completely explainable: those pages contain that word. Now look up 'time off'. Nothing — even though page 42 is entirely about time off.

That is sparse retrieval. BM25 and TF-IDF add sophistication on top (rare words count for more than common ones, repetition has diminishing returns) but the underlying contract is unchanged: it matches terms.

The mapping

In the bookIn sparse retrievalNote
Index entriesTerms in the vocabularyOne entry per distinct token
Page numbers under an entryDocument IDsThe posting list
Words too common to indexStop words'the' is in every document, so it discriminates nothing
Rare technical termsHigh IDF weightA rare match is far more informative
The ranking rulesBM25 / TF-IDFHow matches are scored and ordered

The flow

1
Query is tokenised
'employee leave policy' becomes three terms
2
Each term is looked up
Documents containing those terms are collected
3
Scores combined
Rarer terms and better term coverage rank higher
4
Ranked list returned
Every result provably contains the words
BM25 in LangChain
from langchain_community.retrievers import BM25Retriever

bm25 = BM25Retriever.from_documents(chunks)
bm25.k = 5

documents = bm25.invoke("employee leave policy")
Where the analogy breaks — An index cannot do synonyms, and that is a real limitation. But it is also unbeatable at the thing embeddings are worst at: exact tokens. Error codes, SKUs, AZ-400, surnames, version numbers, lambda_mult. Do not file sparse retrieval under 'the old way' — it catches precisely what dense retrieval blurs.

Dense Retrieval

ANALOGYThe Colleague Who Knows What You Meant

Not one word matched. He understood anyway.

The Colleague Who Knows What You Meant
Dense retrieval analogy: colleague understands meaning without matching keywords
The Colleague Who Knows What You Meant — Concept 08

You ask a colleague 'where's that doc about how many days off we get?' and he hands you a file titled 'Annual Leave Entitlement — 20 Days'. There is no shared vocabulary between what you said and what he gave you. He mapped your phrasing onto a meaning, and matched on meaning.

That mapping is exactly what an embedding model does: it converts text into a position in a high-dimensional space where 'how many days off' and 'annual leave entitlement' land near each other. The retriever then simply returns whatever is nearby.

The mapping

The colleagueDense retrievalNote
Understanding what you meantEmbedding the queryText becomes a vector representing meaning
His mental map of the shared driveVector store of document embeddingsBuilt once, at ingestion
'That sounds like this file'Nearest-neighbour searchClosest vectors, not matching words
How many files he bringskSame cap as always

The flow

1
Query embedded
The question becomes a vector
2
Nearest neighbours found
The index returns the closest stored vectors
3
Chunks returned
Semantically similar text, regardless of wording
Dense retrieval
dense = vector_store.as_retriever(search_kwargs={"k": 5})

documents = dense.invoke("How many days off can employees take?")
# can return: "Employees are entitled to 20 days of annual leave."
Where the analogy breaks — The same helpful colleague confidently hands you the wrong file when he only half-understood. Dense retrieval never says 'no match' — it returns the nearest thing in the space even when nothing in the space is close. And it fails exactly where the index succeeds: ask it for invoice INV-2026-0041 and it will cheerfully return invoice INV-2026-0141, because those two strings are neighbours in meaning-space.

Hybrid Retrieval

ANALOGYThe Detective: Fingerprints and an Eyewitness

One is exact but narrow. One is fuzzy but broad. Good detectives use both.

Fingerprints and an Eyewitness
Detective analogy for hybrid sparse + dense retrieval
Fingerprints and an Eyewitness — Concept 09

A fingerprint is unarguable — but only if that print is already on file. An eyewitness saying 'tall, dark coat, walked with a limp' is imprecise, but it covers cases where no print exists at all. Neither alone closes every case.

Hybrid retrieval runs a keyword search and a vector search over the same query and combines the results. Ask for 'HR leave policy 2026' and sparse retrieval nails the literal tokens HR, leave policy and 2026, while dense retrieval surfaces 'employee annual vacation guidelines', which shares almost no words with the query and is exactly what you wanted.

The mapping

The investigationHybrid retrievalStrength
Fingerprint matchSparse / BM25Exact identifiers, codes, rare terms
Eyewitness descriptionDense / vectorMeaning, paraphrase, synonyms
Weighing the two kinds of evidenceFusion (Concepts 18–19)How the two lists become one
Senior detective ranking suspectsReranker (Concept 21)A second, more careful pass

The flow

1
Query goes to both retrievers
BM25 and vector search run in parallel
2
Two ranked lists come back
They will overlap, but not agree
3
Lists are merged
By weights, by ranks, or by handing everything to a reranker
4
One final ranking
Passed downstream
EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_classic.retrievers import EnsembleRetriever

bm25 = BM25Retriever.from_documents(chunks)
bm25.k = 5
dense = vector_store.as_retriever(search_kwargs={"k": 5})

hybrid = EnsembleRetriever(retrievers=[bm25, dense], weights=[0.5, 0.5])

documents = hybrid.invoke("HR leave policy 2026")
Where the analogy breaks — 'Hybrid' only tells you that two sources were consulted. It says nothing about how the evidence was combined — and a naive merge (just concatenating both lists) produces a ranking where BM25 dominates by position and good dense results sit uselessly at the bottom. Hybrid retrieval merged badly is worse than either retriever used alone.
Improving the question

Query Rewriting

ANALOGYJoining a Group Chat Halfway Through

'What did he say about it?' is unsearchable until someone catches you up.

Joining a Group Chat Halfway
Group-chat analogy for query rewriting
Joining a Group Chat Halfway — Concept 10

You scroll into a group chat and the newest message reads 'What did he say about it?'. Alone it is meaningless. Someone who read the previous twenty messages can restate it: 'What did the CEO say about the 2026 acquisition?'. Same intent, now self-contained.

That is query rewriting, and it is mandatory in conversational RAG. The retriever has no memory of turn three. It sees only the string you hand it, so the string has to stand on its own.

The mapping

In the chatIn the pipelineNote
The previous twenty messagesConversation historyState the retriever cannot see
The friend who catches you upRewriting LLMResolves pronouns and implied subjects
The restated messageStandalone queryWhat actually gets embedded
'he', 'it', 'that one'Unresolved referencesThe specific thing being fixed

The flow

1
Original query
Ambiguous, context-dependent
2
Rewrite against history
Pronouns resolved, subject made explicit
3
Standalone query
Sent to the retriever
Rewrite before retrieve
rewrite_prompt = ChatPromptTemplate.from_messages([
    ("system", "Rewrite the user's question as a standalone search query, "
               "resolving all pronouns using the chat history. Return only the query."),
    MessagesPlaceholder("history"),
    ("human", "{question}"),
])

standalone = (rewrite_prompt | llm | StrOutputParser()).invoke(
    {"history": history, "question": question}
)
documents = retriever.invoke(standalone)
Where the analogy breaks — The friend catching you up can also get it wrong — and confidently insert an entity that was never mentioned. A rewriter that hallucinates 'the 2026 acquisition' when the chat was about a 2025 merger converts a vague query into a precisely wrong one, which is harder to spot than the original vagueness. Log the original and the rewrite; never only the rewrite.

Query Expansion

ANALOGYCoriander or Cilantro

Same plant, different shop, no match.

Coriander or Cilantro
Coriander vs cilantro analogy for query expansion
Coriander or Cilantro — Concept 11

You walk into a supermarket in Texas and ask for coriander. They hand you a jar of seeds. You wanted the fresh green leaves — which that shop calls cilantro. Nothing was wrong with your request except the vocabulary.

Query expansion is walking in with the list instead: coriander, cilantro, Chinese parsley. It adds synonyms, acronyms, regional variants and spelling alternatives so that documents using different words for the same concept still surface.

The mapping

At the supermarketIn retrievalExample
The word you happened to useOriginal query'employee leave policy'
Every name for the same thingExpanded terms'vacation policy', 'annual leave guidelines', 'PTO'
Asking three aisles instead of oneBroader retrievalHigher recall
Also being handed the seedsPrecision lossThe unavoidable trade-off

The flow

1
Original query
One phrasing, one vocabulary
2
Add synonyms, acronyms, variants
Automatically or from a curated glossary
3
Broader search
More documents match
4
Downstream reranking
Cleans up what the widening dragged in
Expansion, then retrieval
expanded = (
    "employee leave policy OR vacation policy "
    "OR annual leave guidelines OR PTO OR time off entitlement"
)
documents = bm25.invoke(expanded)

# Domain glossaries beat LLM-generated synonyms for regulated corpora:
# {"PTO": ["paid time off", "annual leave"], "AL": ["annual leave"]}
Where the analogy breaks — Expand too far and everything matches. 'Car' expanded to 'vehicle' pulls in the forklift maintenance manual, which is genuinely a vehicle document and genuinely useless. Expansion buys recall and spends precision — which is exactly why it belongs upstream of a reranker, not instead of one.

Query Decomposition

ANALOGYThe Work Breakdown Structure

No single document answers a comparison question. Four documents do.

Work Breakdown Structure
Work breakdown structure analogy for query decomposition
Work Breakdown Structure — Concept 12

A stakeholder says: 'compare Company A and Company B's 2025 revenue and explain why their growth rates differed'. No chunk anywhere in the corpus contains that answer, because the answer does not exist until someone assembles it.

A project manager does not attempt that as one task. It becomes four tickets: A's 2025 revenue, B's 2025 revenue, factors affecting A's growth, factors affecting B's growth. Each is retrievable. The final report is the synthesis.

The mapping

In the projectIn retrievalNote
The epicComplex multi-part queryNot answerable as stated
Individual ticketsSub-queriesEach one is a normal retrieval
Assigning each ticketOne retrieval call per sub-queryRun in parallel
The status reportSynthesis by the LLMWhere the comparison actually happens

The flow

1
Complex query arrives
Comparison, multi-hop or multi-entity
2
Split into sub-questions
Each independently answerable
3
Retrieve evidence for each
Separate retrieval per sub-query
4
Combine and answer
The LLM assembles the comparison
Decompose, retrieve, synthesise
subqueries = decompose_chain.invoke(question)      # -> list[str]

evidence = {sq: retriever.invoke(sq) for sq in subqueries}

answer = llm.invoke(
    synthesis_prompt.format(question=question, evidence=evidence)
)
Where the analogy breaks — A project manager notices a missing ticket. A decomposer does not. If it fails to generate the sub-question that actually mattered, the pipeline retrieves cleanly, synthesises fluently, and produces a confidently incomplete answer — with nothing in the output indicating the gap. Inspect the generated sub-queries during evaluation, not just the final text.

HyDE — Hypothetical Document Embeddings

ANALOGYThe Police Sketch Artist

Draw a face that may not exist, then search the database for photos that look like it.

Police Sketch Artist
Police sketch artist analogy for HyDE
Police Sketch Artist — Concept 13

A witness gives a description: middle-aged, thin face, scar above the left eyebrow. You cannot search a mugshot database with a sentence. So a sketch artist draws a plausible face from the description — a face that quite possibly belongs to no one — and you search for photographs that resemble the sketch.

That is HyDE exactly. A short question and a long document are structurally dissimilar, which hurts embedding similarity. So the LLM first writes a hypothetical answer — 'Llama 2 improves safety using supervised safety fine-tuning, RLHF, red teaming and safety evaluation' — and that gets embedded and used for the search. The sketch is discarded. The retrieved document is real.

The mapping

The investigationHyDENote
The witness descriptionThe short user queryToo sparse to match well
The sketchThe hypothetical documentFabricated on purpose, never returned
'Looks like the sketch'Embedding similarityThe actual search operation
The mugshot databaseThe vector storeOnly real records live here
The matched photographThe retrieved chunkReal evidence, found via a fiction

The flow

1
User query
'How does Llama 2 improve safety?'
2
LLM writes a hypothetical answer
A document-shaped passage, not a question
3
Embed the hypothetical text
It now resembles real documents in shape and vocabulary
4
Vector search
Retrieve real chunks near that embedding
Both the class and the explicit chain
from langchain_classic.chains.hyde.base import HypotheticalDocumentEmbedder

hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
    llm=llm,
    base_embeddings=embeddings,
    prompt_key="web_search",
)

# The explicit version — clearer to teach, easier to debug:
hypothetical = (hyde_prompt | llm | StrOutputParser()).invoke({"question": question})
vector       = embeddings.embed_query(hypothetical)
documents    = vector_store.similarity_search_by_vector(vector, k=4)
Where the analogy breaks — A sketch can be wrong in the details and still lead you to the right person. But if the model invents the whole direction — a technique that does not exist in your corpus — you will retrieve confidently irrelevant documents and the pipeline will look like it worked. HyDE helps most where the model has genuine domain knowledge, and hurts most on proprietary corpora it has never seen, which is exactly the enterprise case.

Multi-Query Retriever

ANALOGYAsking Three Colleagues the Same Question

Three phrasings, three memories, one pooled answer.

Asking Three Colleagues
Asking three colleagues analogy for multi-query retrieval
Asking Three Colleagues — Concept 14

You ask three people the same thing in three different ways. One remembers the training deck, one remembers the wiki page, one remembers an email. Each phrasing unlocked a different memory. You pool what comes back and drop the duplicates.

Multi-query retrieval does the same mechanically: the LLM generates several variations of the question, each is retrieved for separately, and the unique union is returned. 'How does Llama 2 improve safety?' becomes 'What safety techniques are used in Llama 2?', 'How was Llama 2 safety fine-tuned?' and 'How does Llama 2 reduce unsafe responses?'.

The mapping

Asking aroundMulti-queryNote
Rephrasing for each personGenerated query variationsUsually three to five
Each person's memoryOne retrieval run per variationIndependent searches
Pooling the answersUnion of resultsDuplicates removed
Keeping your original question tooinclude_original=TrueCheap insurance

The flow

1
One user query
As typed
2
Generate N variations
By LLM
3
Retrieve for each
N searches
4
Merge and deduplicate
Unique union returned
MultiQueryRetriever
from langchain_classic.retrievers.multi_query import MultiQueryRetriever

multi_query = MultiQueryRetriever.from_llm(
    retriever=vector_store.as_retriever(),
    llm=llm,
    include_original=True,
)

documents = multi_query.invoke("How does Llama 2 improve safety?")
Where the analogy breaks — Multi-query generates several questions; HyDE generates one hypothetical answer. Multi-query is aimed at recall, HyDE at semantic matching, and they compose rather than compete. But three colleagues cost three times the latency and an extra LLM call before any of it starts. Turn it on when you have measured that recall was the failure — not by default.
Choosing what to return

Parent Document Retriever

ANALOGYFind the Line via the Index, Read the Whole Chapter

Search small, return big.

Index to the Line, Read the Chapter
Index-to-the-line, read-the-chapter analogy for parent document retriever
Index to the Line, Read the Chapter — Concept 15

The index takes you to one exact sentence, which is perfect for finding. But a sentence lifted out of a chapter is often useless for understanding — it references 'this policy' and 'the above conditions' and assumes everything around it.

So you use the sentence to locate the chapter, and hand over the chapter. Concretely: a two-thousand-token section is split into three-hundred-token children. The children are embedded and searched. When child 3 matches, the retriever returns the full parent section instead.

The mapping

In the bookIn the retrieverNote
The indexed sentenceChild chunkSmall, precise, what gets embedded
The chapter around itParent documentLarge, contextual, what gets returned
The index itselfChild embeddings in the vector storeSearch happens here
The shelf holding full booksDocstoreParents live here, not in the vector index

The flow

1
Split into parents, then children
Two levels of chunking at ingestion
2
Embed only the children
Precision comes from small units
3
Query matches a child
Best small chunk found
4
Return its parent
Context comes from the large unit
ParentDocumentRetriever
from langchain_classic.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_text_splitters import RecursiveCharacterTextSplitter

retriever = ParentDocumentRetriever(
    vectorstore=vector_store,
    docstore=InMemoryStore(),
    child_splitter=RecursiveCharacterTextSplitter(chunk_size=300),
    parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000),
)

retriever.add_documents(documents)
results = retriever.invoke("How was RLHF performed?")
Where the analogy breaks — Reading the whole chapter is fine for one hit. Return four parents at two thousand tokens each and you have spent eight thousand tokens of context to deliver perhaps four hundred useful ones. Parent-document retrieval trades token budget for context quality — which is precisely the mess contextual compression (Concept 24) exists to clean up afterwards.

Sentence Window Retriever

ANALOGYThe Notification vs Opening the Thread

Match one line, return the five around it.

Notification vs Opening the Thread
Notification vs opening the thread analogy for sentence window retriever
Notification vs Opening the Thread — Concept 16

A notification shows a single line: 'RLHF is used to further align the model'. Precise enough to match your search, useless on its own. You tap it, and the app shows the five messages around it — the setup before and the consequence after.

Sentence window retrieval indexes individual sentences for matching precision, then replaces the matched sentence with a window of its neighbours before handing anything to the LLM. Match sentence 10, return sentences 8 through 12.

The mapping

On your phoneIn the retrieverNote
The one-line notificationThe indexed sentenceWhat is embedded and searched
Tapping to open the threadMetadata lookupThe window replaces the content
The five messages shownThe returned windowFixed number of neighbours
Scroll depthWindow sizeYour tunable parameter

The flow

1
Split the document into sentences
At ingestion
2
Store each sentence plus its window in metadata
The window is precomputed
3
Search sentence embeddings
Maximum matching precision
4
Swap in the window
Return neighbours, not the bare sentence
LangChain has no built-in class — you compose it
# At ingestion
metadata = {
    "sentence_id": 10,
    "window": " ".join(sentences[8:13]),   # sentences 8..12
}

# At query time
documents = vector_store.similarity_search(query, k=4)
for document in documents:
    document.page_content = document.metadata["window"]

# LlamaIndex ships this directly (SentenceWindowNodeParser +
# MetadataReplacementPostProcessor); in LangChain it is custom code.
Where the analogy breaks — Parent-document and sentence-window look similar and differ in one important way. A parent is a structural unit — the section a human author deliberately wrote. A window is a positional unit — N sentences either side, which will happily straddle a heading and drag half of an unrelated topic in with it. Structure respects the author's intent; position does not.

Multi-Hop Retrieval

ANALOGYThe Treasure Hunt Clue Chain

You cannot find clue two by staring harder at clue one.

Treasure Hunt Clue Chain
Treasure hunt clue chain analogy for multi-hop retrieval
Treasure Hunt Clue Chain — Concept 17

The first clue sends you to the oak tree. Taped under the branch is the second clue, which sends you to the boathouse. No amount of re-reading clue one would have revealed the boathouse — you had to physically go and collect the next piece.

'Who founded the company that developed Llama 2?' is that structure. Hop one asks which company developed Llama 2 and returns Meta. Only now can hop two be formed: who founded Meta. Both answers combine into the final response.

The mapping

On the huntIn retrievalNote
Clue oneFirst retrievalAnswers only part of the question
What clue one revealsIntermediate evidenceThe bridge entity
Clue two, written from clue oneGenerated follow-up queryCannot be written in advance
The final chestCombined answerSynthesised from all hops
Giving up after ten treesHop limitThe safety rail

The flow

1
Retrieve for the original query
Partial evidence
2
LLM forms the next query from what it found
The dependency step
3
Retrieve again
New evidence
4
Combine and answer
Repeat until the question is covered or the cap is hit
No built-in class — LangGraph and state
documents_1 = retriever.invoke(question)

next_query = llm.invoke(
    followup_prompt.format(question=question, evidence=documents_1)
)
documents_2 = retriever.invoke(next_query)

answer = llm.invoke(
    synthesis_prompt.format(evidence=documents_1 + documents_2)
)

# In production: LangGraph, with explicit state, a hop counter and a stop condition.
Where the analogy breaks — A treasure hunt has a designed ending. A multi-hop loop does not. Without an explicit hop cap and a stop condition, the agent will hop into an irrelevant branch and keep going, or loop between two documents indefinitely — burning tokens while looking busy. Cap the hops, and log every query the model generates, because that chain is where the reasoning actually went wrong.
Merging result lists

Weighted Fusion

ANALOGYWeighted Exam Marks

Theory is 40%, practical is 60% — but only if both were marked on the same scale.

Weighted Exam Marks
Weighted exam marks analogy for weighted fusion
Weighted Exam Marks — Concept 18

Two papers, two marks, one final grade. Theory counts for forty per cent and practical for sixty. A BM25 score of 0.8 and a vector score of 0.9 with those weights gives 0.4 × 0.8 + 0.6 × 0.9 = 0.86, and documents are sorted by that combined figure.

The arithmetic is trivial. The assumption underneath it is not: both papers must be marked out of the same total. One marked out of 100 and one out of 10 makes the weighting meaningless, no matter how carefully you chose the percentages.

The mapping

In the examIn fusionNote
Theory paper markBM25 scoreUnbounded, corpus-dependent
Practical paper markVector scoreCosine sits in [-1, 1]
The 40/60 splitWeightsYour explicit judgement of which retriever to trust
Marking both out of 100Score normalisationMandatory, and usually skipped
The final gradeCombined scoreWhat the ranking is sorted on

The flow

1
Both retrievers return scores
On different, incomparable scales
2
Normalise each score list
Min-max or z-score, per query
3
Apply weights and add
The weighted sum
4
Sort by combined score
Final ranking
Usually custom code
def normalise(scores):
    lo, hi = min(scores), max(scores)
    return [(s - lo) / (hi - lo + 1e-9) for s in scores]

final = 0.4 * normalise(bm25_scores)[i] + 0.6 * normalise(vector_scores)[i]

# Note: LangChain's EnsembleRetriever(weights=[...]) is *weighted RRF*,
# not raw-score weighted addition. If you need true score fusion, write it
# yourself or use your vector DB's native hybrid implementation.
Where the analogy breaks — Exam marks live on a fixed, known scale. Retrieval scores do not. BM25 is unbounded and shifts with corpus statistics, so on one query it might peak at 4 and on the next at 40. Without per-query normalisation, weighted fusion silently hands the entire ranking to whichever retriever happens to produce bigger numbers — and your carefully chosen 40/60 does nothing at all.

Reciprocal Rank Fusion

ANALOGYThe Championship Points Table

Nobody adds up lap times across circuits. They award points for position.

Championship Points Table
RRF championship points table analogy: ranks become points across retrievers
The Championship Points Table — Concept 19
RRF worked example
RRF worked example with formula, rank scores, and multi-retriever fusion table
RRF worked example — Concept 19

Formula One does not compare a Monaco lap time with a Monza lap time — the numbers are not comparable and never will be. It converts finishing position into points and sums those. A driver who finishes second, second and third beats one who wins a race and retires from two.

RRF does exactly that. Each retriever's list is a race. A document's position in each list earns it points on a 1/(k + rank) curve, points are summed across lists, and the document that placed respectably everywhere beats the one that placed first in a single list and nowhere else.

The mapping

In the championshipIn RRFNote
Each raceEach retriever's ranked listBM25 list, vector list, and so on
Finishing positionRankPosition is all that is used
The points curve1 / (k + rank)Steep at the top, flat further down
Championship standingsFused rankingSum of points across lists
Consistent podium finishesAppearing high in several listsWhat RRF rewards

The flow

1
Each retriever returns a ranked list
Scores are ignored entirely
2
Each document earns points by position
In every list it appears in
3
Points are summed
Across all lists
4
Sort by total
Final fused ranking
EnsembleRetriever uses weighted RRF internally
from langchain_classic.retrievers import EnsembleRetriever

ensemble = EnsembleRetriever(
    retrievers=[bm25, dense],
    weights=[0.5, 0.5],           # weighted reciprocal rank fusion
)

documents = ensemble.invoke("Llama 2 grouped query attention")

# Because it uses ranks, the two retrievers' score scales never need to match.
Where the analogy breaks — A points table deliberately throws away margin of victory — a one-second win and a lap-ahead win both score twenty-five. RRF throws away exactly the same thing: a runaway perfect match and a barely-adequate one both simply 'finished first'. When the margin is the signal you care about, do not fuse — rerank. And note that RRF is not reranking: it reorders using positions alone and never reads a single document.

Maximal Marginal Relevance

ANALOGYThe Buffet Plate

The rice is excellent. That is not a reason to take rice four times.

The Buffet Plate
MMR buffet plate analogy for relevance versus diversity
The Buffet Plate — Concept 20

You survey the buffet and take the best thing on it. Then, by the same logic, the second-best thing — which is more rice. Then more rice. You now have a technically optimal plate and a genuinely boring meal, because you optimised each choice in isolation.

MMR asks a different question from the second pick onwards: is this item good and different from what is already on my plate? Work through the numbers from the notes with fetch_k = 5 and k = 2 and λ = 0.5. Doc A is chosen first at 0.95. Then Doc B scores −0.025 (highly relevant at 0.90, but 0.95 similar to Doc A — nearly a duplicate), Doc C 0.24, Doc D 0.32 and Doc E 0.35. MMR picks Doc E, which is the least relevant of the five and the most additive.

The mapping

At the buffetIn MMRSymbol
The best dish on the counterHighest query similaritySim(Dᵢ, Q)
What is already on your plateThe selected setS
'Do I already have this?'Similarity to what is selectedSim(Dᵢ, D⫺)
How adventurous you feelRelevance vs diversity balanceλ — lambda_mult
Plate capacityFinal results returnedk
The stretch of counter you surveyCandidate poolfetch_k

The flow

1
Fetch fetch_k candidates
A wide pool, ranked by relevance
2
Take the single most relevant
The first pick is pure relevance
3
Score every remaining candidate
Relevance minus similarity to what is already picked
4
Repeat until k are selected
λ = 1 is all rice; λ = 0 is one of everything
MMR mode
retriever = vector_store.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 4,
        "fetch_k": 20,       # survey 20, plate 4
        "lambda_mult": 0.5,  # 1.0 = pure relevance, 0.0 = pure diversity
    },
)
Where the analogy breaks — Diversity is not always a virtue. If the correct answer genuinely is stated in five near-identical documents, MMR will discard four of them — and it might discard the clearest phrasing while keeping a tangential one, because it is optimising for difference, not for quality. MMR fixes redundancy and damages corroboration. Know which of those you have.
Sharpening what you send

Reranking

ANALOGYThe ATS Screen, Then the Interview Panel

Nobody interviews a thousand people. Nobody hires off a keyword score either.

ATS then Interview Panel
Reranking analogy: ATS screen then interview panel
ATS Screen, Then Interview Panel — Concept 21

A thousand CVs arrive. A keyword screen cuts that to fifty in seconds — fast, shallow, and its internal ordering means almost nothing. Then a panel interviews those fifty properly and ranks the top five. Two stages, two entirely different cost profiles, deliberately.

Retrieval is that hiring funnel. A million stored chunks, a first-stage retriever that surfaces fifty candidates, a reranker that scores each one carefully against the query, and five that reach the LLM. Ask 'how many days of annual leave do employees receive?' and the first stage might return the remote work policy first and the annual leave policy third. The reranker scores them 0.08 and 0.95 respectively and fixes the order.

The mapping

In hiringIn retrievalNote
A thousand applicantsThe full corpusFar too many to assess properly
Keyword ATS screenFirst-stage retrievalBM25, vector or hybrid — fast and broad
Fifty shortlistedCandidate setfetch_k / rank_window_size
The interview panelCross-encoder rerankerSlow, accurate, applied to the shortlist only
The minimum hiring barScore thresholdBelow it, nobody gets an offer
Five offers madetop_n sent to the LLMThe final context

The flow

1
Apply metadata and security filters
Before anything else
2
Retrieve a broad candidate set
Optimised for recall, not precision
3
Score every candidate against the query
The reranker's actual job
4
Sort, threshold, truncate
Best evidence only
5
Send to the LLM
Typically 3–5 chunks
CrossEncoderReranker
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

model = HuggingFaceCrossEncoder(model_name="cross-encoder/ms-marco-MiniLM-L6-v2")
reranker = CrossEncoderReranker(model=model, top_n=5)

retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=dense_retriever,   # returns ~50 candidates
)
Where the analogy breaks — Reranking was not invented for RAG, and treating it as a RAG feature is a small but real misconception. Cascade ranking was formalised in 2011 and BERT-based passage re-ranking in 2019; RAG inherited a solved architecture. The practical constraint is cost: interviews are expensive. Reranking adds latency, compute and per-call spend, so it belongs on twenty to a hundred candidates and never on the corpus.

Bi-encoder vs Cross-encoder

ANALOGYDating Profiles vs an Actual Date

You can pre-write a profile. You cannot pre-run a date.

Dating Profiles vs a Date
Bi-encoder vs cross-encoder analogy: dating profiles versus an actual date
Dating Profiles vs an Actual Date — Concept 22

A bi-encoder is two profiles, each written independently, compared by a compatibility score. It is fast precisely because each profile is written once and reused against everyone — a million comparisons cost a million cheap distance calculations.

A cross-encoder is putting the two people in a room and watching the conversation. Far more accurate, and impossible to prepare in advance, because the output depends on the specific pairing. That is the entire architectural reason retrieval and reranking are separate stages.

The mapping

In datingIn retrievalConsequence
Profile written once, in advanceDocument embeddingPrecomputed and stored at ingestion
Compatibility score from two profilesCosine similarityCheap, scales to millions
An actual dateCross-encoder forward pass on [query + document]One model run per pair
Cannot pre-run every possible dateCross-encoders cannot be precomputedWhy they cannot search a corpus
Screen by profile, date the shortlistRetrieve broadly, rerank narrowlyThe whole two-stage design

The flow

1
Bi-encoder
Query → vector; document → vector (already stored); compare
2
Cross-encoder
[query + document] → one model → relevance score
3
Twenty candidates
Twenty separate cross-encoder passes, then sort
The cost difference, made explicit
# Bi-encoder: one embedding call, then vector maths across millions
query_vector = embeddings.embed_query(question)   # document vectors already stored

# Cross-encoder: one model forward pass per candidate document
scores = model.score([(question, d.page_content) for d in candidates])
ranked = [d for _, d in sorted(zip(scores, candidates), reverse=True)]
Where the analogy breaks — A date reveals what a profile never could — but a date with a million people is not a plan, it is a fantasy. The entire cascade exists because accuracy does not scale and breadth does not discriminate. You buy accuracy only at the point where it changes the outcome, which is the last fifty documents, not the first million.

Pointwise, Pairwise and Listwise Reranking

ANALOGYJudging a Cooking Competition

Three ways to judge five dishes, three very different bills.

Judging a Cooking Competition
Pointwise, pairwise, and listwise reranking as judging a cooking competition
Judging a Cooking Competition — Concept 23

A judge can score each dish independently on a card out of ten and sort the cards afterwards. Or take two dishes at a time and simply say which is better, repeatedly. Or taste all five together and write the final running order directly.

Those are pointwise, pairwise and listwise reranking. The 2019 multi-stage BERT ranking work implements the first two directly as monoBERT and duoBERT; LangChain exposes the third as LLMListwiseRerank.

The mapping

The judging methodThe reranking familyCost
Score each dish on its own cardPointwise (monoBERT)N model calls — linear
'Which of these two is better?'Pairwise (duoBERT)Grows roughly quadratically
Taste all five, write the orderListwise (LLMListwiseRerank)One call, but order-sensitive

The flow

1
Pointwise
Query + Doc A → 0.91 · Query + Doc B → 0.67 · sort by score
2
Pairwise
A vs B, A vs C, B vs C … aggregate the wins
3
Listwise
Input A, B, C, D → output C, A, D, B
Listwise reranking with an LLM
from langchain_classic.retrievers.document_compressors import LLMListwiseRerank

listwise = LLMListwiseRerank.from_llm(llm=llm, top_n=5)

retriever = ContextualCompressionRetriever(
    base_compressor=listwise,
    base_retriever=base_retriever,
)
Where the analogy breaks — Pairwise is the most reliable and the least affordable — the comparison count grows roughly with the square of the candidates. Listwise is cheap per call but carries a subtle defect: its output depends on the order you fed the documents in. Shuffle the input and you can get a different 'final' ranking from the same model on the same query. Test that before trusting it in production.

Contextual Compression

ANALOGYThe Newspaper Clipping Service

They do not send you the newspaper reordered. They send you the three paragraphs.

Newspaper Clipping Service
Contextual compression analogy: newspaper clipping service
The Newspaper Clipping Service — Concept 24

A clipping service does not mail you the whole paper with the relevant articles moved to the front. It cuts out the three paragraphs that mention your company and sends only those. The number of newspapers it processed did not change; the size of what lands on your desk changed enormously.

That is the distinction people most often miss. A retrieved HR chunk of a thousand tokens might contain leave policy, dress code, work-from-home rules, travel reimbursement and the holiday calendar. Asked how many annual leave days employees get, compression keeps two sentences and drops the rest.

The mapping

StageWhat it changesWhat it leaves alone
RetrieverWhich documents you haveTheir order and their content
RerankerThe order of those documentsTheir content — 1,000 words stay 1,000 words
Contextual compressionThe content inside themOften the document count stays the same

The flow

1
LLMChainExtractor — the intern who reads it for you
Reads each document with your question in mind and types out only the relevant lines. Accurate, and costs one LLM call per document.
2
EmbeddingsFilter — the metal detector
Sweeps and keeps only what beeps above a threshold. No LLM call, so fast and cheap — but it keeps or drops whole chunks rather than trimming inside them.
3
CrossEncoderReranker as compressor — the bouncer with a list
Anything below the relevance bar simply does not get in.
ContextualCompressionRetriever
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import (
    LLMChainExtractor,
    EmbeddingsFilter,
)

compressor = LLMChainExtractor.from_llm(llm)                     # the intern
# compressor = EmbeddingsFilter(embeddings=embeddings,
#                               similarity_threshold=0.75)       # the metal detector

retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=dense_retriever,
)
documents = retriever.invoke(question)
Where the analogy breaks — Why not simply lower k? Because if one thousand-token document contains twenty useful tokens, retrieving fewer documents does not help — you still send a thousand tokens. But compression is lossy by design, and a clipping service can cut away the sentence that gave the quote its meaning. An aggressive extractor will happily remove the effective date, the 'except for contractors' clause, or the caveat, and hand the LLM a cleaner context that is now wrong.
Beyond plain text

Multimodal Retrieval

ANALOGYHumming a Tune to Shazam

A hum and a studio master share nothing — until both become the same kind of fingerprint.

Humming a Tune to Shazam
Multimodal retrieval analogy: humming to Shazam in fingerprint space
Humming a Tune to Shazam — Concept 25

You hum badly into a phone and get back a studio recording. There is no format in common between those two things. They match because both were converted into the same kind of audio fingerprint, and the comparison happens in fingerprint space, not in sound space.

Multimodal retrieval does that for text and images. A multimodal embedding model projects both into one shared vector space, which is what makes text-to-image, image-to-text and image-to-image search possible at all.

The mapping

With ShazamIn multimodal retrievalNote
Your humQuery in one modalityText, typically
The studio recordingStored item in another modalityImages, typically
The audio fingerprintShared embedding spaceWhere the comparison actually happens
The returned trackCross-modal nearest neighbourRetrieved by proximity, not format

The flow

1
Text → image
Text encoder → shared space → search image vectors → relevant images
2
Image → image
Image encoder → shared space → search image vectors → similar images
3
Image → text
Same space, opposite direction
A CLIP-style shared space
# Both modalities embed into the same dimensionality
text_vector = multimodal_embeddings.embed_query("a red sports car at night")

results = image_store.similarity_search_by_vector(text_vector, k=5)

# The image vectors were created by the image encoder of the *same* model.
# Mixing encoders from different models produces meaningless distances.
Where the analogy breaks — Shazam works because both sides really are audio. Cross-modal spaces are trained approximations, and their failure modes are specific and predictable: reliably good at objects, scenes and style; reliably bad at text inside images, exact quantities, and spatial relations. 'The cat to the left of the dog' and 'the dog to the left of the cat' land in almost the same place.

Query Understanding and Routing

ANALOGYThe Hotel Concierge

The concierge does not answer. He works out which desk you need.

The Hotel Concierge
Query understanding and routing analogy: hotel concierge directs to the right desk
The Hotel Concierge — Concept 26

You approach with 'I need this sorted by Friday'. The concierge does not attempt it. He works out what kind of request it is, checks you are actually a guest, and points you at housekeeping, the booking desk or the airport shuttle. Retrieval is one desk among several.

That is the front of a production pipeline: understand the intent, extract any metadata implied by the phrasing, apply security and governance, then route. A question about last quarter's revenue belongs in SQL. A question about today's news belongs in web search. Sending either to your vector store guarantees a fluent, ungrounded answer.

The mapping

At the hotelIn the pipelineNote
Working out what you actually needQuery understanding / intent classificationBefore any search runs
Checking your room key and statusSecurity and governanceWho is allowed to ask this
Noticing 'by Friday'Metadata extractionTurns phrasing into filters
Choosing the right deskQuery routingVector DB, SQL, graph, web, API
Sending you back to the lobbyFallback routeThe 'no suitable source' path

The flow

1
Query understanding
Intent, metadata extraction, security
2
Query transformation
Rewriting, expansion, decomposition, HyDE, multi-query
3
Routing
Vector store, SQL, knowledge graph, web search, API, or several
4
Retrieval → fusion → reranking → compression
Then, and only then, the LLM
Routing
route = router_chain.invoke(question)      # -> "vector" | "sql" | "web" | "graph"

ROUTES = {
    "vector": vector_retriever,
    "sql":    sql_chain,
    "web":    web_search_tool,
    "graph":  graph_retriever,
}

documents = ROUTES.get(route, vector_retriever).invoke(question)
Where the analogy breaks — A concierge who guesses wrong sends you back to the lobby, mildly annoyed but informed. A router that misclassifies sends the question to a source that contains no answer — and the LLM will still write a confident paragraph. Every router needs an explicit fallback and an honest 'I have no source for this' path, or misrouting becomes indistinguishable from hallucination.
Revision

The analogy cheat sheet

Use the analogy as the retrieval cue. If you can recall the picture, the mechanism follows; if you cannot, that is the section to re-read.

#ConceptAnalogyMemory hook
01RAG pipelineRestaurant kitchenNobody hands the chef the whole pantry
02RetrieverLibrarianFinds the books; does not write the essay
03search_type / kwargsCamera in manual modeMode dial first, then the dials that mode exposes
04Similarity metricsTwo hikers with compassesSame direction, how far apart, or both
05Metadata filteringOffice security badgeMeaning gets you in; metadata picks the floor
06Pre- vs post-filterVisa at check-in vs immigrationCheck before boarding, not after landing
07Sparse retrievalIndex at the back of the bookMatches letters, not meaning
08Dense retrievalThe colleague who knew what you meantNo words matched; he understood
09Hybrid retrievalFingerprints and an eyewitnessExact and fuzzy evidence together
10Query rewritingJoining a group chat halfwayMake the message stand on its own
11Query expansionCoriander or cilantroSame thing, different vocabulary
12Query decompositionWork breakdown structureOne epic becomes four tickets
13HyDEPolice sketch artistDraw a fake face, find the real photo
14Multi-queryAsking three colleaguesThree phrasings unlock three memories
15Parent documentIndex to the line, read the chapterSearch small, return big
16Sentence windowNotification vs opening the threadMatch one line, return the five around it
17Multi-hopTreasure hunt clue chainClue two only exists once you fetch clue one
18Weighted fusionWeighted exam marks40/60 only works if both are out of 100
19RRFChampionship points tablePosition earns points; lap times are never compared
20MMRThe buffet plateDo not take rice four times
21RerankingATS screen, then interview panelScreen a thousand, interview fifty, hire five
22Bi- vs cross-encoderDating profiles vs an actual dateYou cannot pre-run a date
23Rank familiesJudging a cooking competitionScore each, compare pairs, or order the lot
24Contextual compressionNewspaper clipping serviceCuts the paragraph out, not the paper up
25Multimodal retrievalHumming to ShazamDifferent formats, one fingerprint space
26Query routingHotel conciergeWorks out which desk, does not answer himself

The three verbs worth memorising

If a student remembers nothing else, this is the sentence that prevents the most common confusion in the whole subject. The retriever finds the documents. The reranker orders them. Contextual compression trims them. Three different verbs, three different stages, three different failure modes.

A note on import paths

The code here follows the langchain_classic paths used in the bootcamp notes. LangChain moves these between releases more often than almost any other library in the stack, so treat every import as correct for the version you pin and verify against the docs when you upgrade. The concepts do not move; the module paths do.

Every analogy is a lie that helps

Each 'where the analogy breaks' box exists because a good analogy is dangerous in exactly proportion to how good it is. The clearer the picture, the more confidently a learner extends it past the point where it holds. Teach the edge at the same time as the picture and the analogy becomes a tool rather than a future misconception to unpick.

Top