From a store of vectors to an answer
In b3 you loaded Recall's notes into a vector store. A store you never query is just an expensive filing cabinet. Retrieval is the query: it takes a natural-language question and returns the handful of chunks most likely to contain the answer, ordered best-first. Everything downstream - the prompt you build, the citations you show, the answer the model writes - is only as good as this ranked list. So we learn to build it, and to read it with a suspicious eye.
retrieve(query, k) function over Recall's Corpus A, the same call expressed through a real framework's retriever interface, and a repeatable habit for judging whether a result set is trustworthy - including a hand-computed hit rate you will lean on again in b9.
Top-k similarity search, end to end 7 min live
Retrieval is four steps that never change: embed the query with the same model you used for the documents, score the query vector against every stored chunk with cosine similarity, sort those scores high to low, and take the top k. The vector database does the middle two fast; you supply the query and choose k.
Self-studyThe retriever in a dozen lines of Python3 min▶
Written by hand, retrieval is exactly the four steps. Here it is over Corpus A - embed the query, cosine against every chunk, sort, slice:
In production you never loop in Python - the vector store scores all chunks with an ANN index (b3). But the contract is identical: query in, ranked chunks out. If you can read this function, you can read any retriever.
Self-studyThe same call against a real vector store2 min▶
Every vector database ships a one-line version of the loop above. With Chroma you hand it the query text and how many results you want; it embeds and ranks for you:
Note n_results is k, and Chroma returns a distance (smaller = more similar) rather than a cosine score (larger = more similar) - same ranking, flipped sign. Read your store's docs so you know which way "good" points.
LiveChoosing k, and rank vs threshold3 min▶
k is the one knob you own on day one, and it trades off two failures:
- k too small - you miss relevant chunks that sat just below the cut. If the answer lived at #4 and k=3, the model never sees it. This is a silent failure: the answer looks confident and is simply incomplete.
- k too big - you drag in loosely related noise. That costs tokens (every chunk goes into the prompt), costs latency, and can distract the model into citing the wrong passage. More context is not more accuracy.
A common starting point is k=4 and you tune from there against real questions. The second habit:
The retriever as a component, and how to read its output 6 min live
Frameworks wrap that four-step move in a reusable object called a retriever: a component with one job - take a query string, hand back a ranked list of documents. Because the interface is fixed, you can swap what is behind it (a different store, hybrid search in b5, a reranker in b6) without touching the code that consumes the results. The generator just asks the retriever for chunks.
Self-studyThe LangChain retriever interface2 min▶
In LangChain the vector store gives you two front doors. The direct one is similarity_search; the composable one is .as_retriever(), which returns an object any chain can call:
Both do the identical four steps. similarity_search(query, k=4) is what you reach for while exploring; the retriever is what you wire into a chain so the rest of the pipeline never needs to know how retrieval happens.
LiveReading a ranked result set critically4 min▶
The habit that saves you weeks: never trust a retriever you have not eyeballed. For any query, ask three things of the ranked list:
- Is #1 actually relevant? Read the top chunk. Does it genuinely address the question, or did it just share some vocabulary? A high score on the wrong chunk is worse than a low score on nothing.
- Is the right chunk anywhere in the top-k? Even if it is not #1, being at #2 or #3 is fine - the generator sees all k. Being absent is the failure that matters.
- How far did relevance fall off? If #1 is strong and #2-#4 are noise, a smaller k would be cleaner. If several are all plausible, you may be under-retrieving.
The demo that lied. A team (anonymized) shipped a support bot that looked great in the demo - every question returned a confident answer. When they finally read the top-k for 20 real tickets, the correct article was missing from the top-3 on nearly half of them; the model was fluently paraphrasing the wrong chunk. Nobody had looked at the ranked list. The fix was not a better model - it was measuring hit rate first.
That "is the right chunk in the top-k?" question, counted across many queries, is hit rate - the fraction of queries where the correct document lands in the top-k. It is the cheapest retrieval metric you have, and you can compute it by hand today.
Live★ Build-along: vary the query, watch the ranking move6 min▶
Here is Recall answering over Corpus A with k=4. Type a question, hit Search, and read the ranked list the way Part 2 taught: is #1 right, is the correct note in the top-4, where does relevance drop off? Change a word and watch the cosine scores and the order shift.
Try "what did the MD promise about cloud spend?" (should surface the Q2 board note), then "aisle or window?" (travel prefs), then something Corpus A cannot answer like "what is our AWS bill?" - and notice it still returns its best guess. Judging when a best guess is too weak to use is session b7.
Compute a hit rate by hand ★ 10 min · the playground above
You do not need a framework to measure retrieval quality - you need five questions and honest eyes. Do this over the Corpus A playground and you will have your first evaluation number.
Write five questions you know Corpus A can answer, and for each note down which note (A1-A8) is the correct source. Example: "when does the daughter graduate?" → A8.
Run each query with k set to 4 (or mentally take the top-3). For each, mark a ✓ if the correct note appears in the top-3, an ✗ if it does not.
Compute hit rate = (number of ✓) / 5. Four out of five is 0.80. That single number is your baseline; every change you make later (chunking, hybrid, reranking) should move it up.
Inspect the misses. For any ✗, read what did rank first. Was it a synonym collision? A too-short query? Write one sentence on why it missed - those sentences become your test cases in b9.
Before session b5 ◐ 40 min total
- Take the from-scratch
retrieve(query, k)function and run it against your own vector store from b3. Confirm that raising k from 2 to 6 changes which chunks come back and eyeball where the noise starts. - Rewrite the same query using
similarity_search(query, k=4)and again through.as_retriever().invoke(query). Confirm you get the same ranked documents - proving the interface is just a wrapper. - Compute a hit rate over ten of your own questions with k=3, then again with k=5. Note how hit rate rises with k while precision falls - the core k trade-off, in numbers.
- Read: the LangChain "Retrieval" / retriever concept docs, and skim the LlamaIndex querying guide for how it names the same interface.
Official sources covered
Taught from official framework docs. This page covers ~80% of their working content on basic retrieval and the retriever interface - the hosted-infra and advanced-config parts stay with the source.
Three questions before you go 🎯 ◐ 90 seconds
1 · What does "top-k" retrieval return?
Top-k means score every chunk by cosine, sort high to low, and return the k highest - a ranking, not a fixed threshold.
2 · You set k too small. What is the likely failure?
Small k misses relevant chunks that sat below the cut; large k drags in noise and cost. k=4 is a common starting point you tune.
3 · Hit rate for retrieval is...
Hit rate = fraction of queries whose correct document lands in the top-k. It is the cheapest retrieval metric and you can count it by hand.