Chapter 4 · Part 2

Retrieve the right context

The R in RAG. Before the model answers, we find the handful of chunks most likely to contain the answer — so the prompt carries just the relevant facts, not all seven (or, in a real system, all seventy thousand). If you took the semantic-search course this is old friend: embed the question, score it against every chunk with cosine similarity, take the top few.

Add a retrieve function to rag.py:

rag.py (continued)
import numpy as np

def retrieve(query, k=3):
  query_embedding = embedder.encode(query, normalize_embeddings=True)
  scores = doc_embeddings @ query_embedding      # cosine similarity, all chunks
  top_k = np.argsort(-scores)[:k]                # indices of the k best
  return [documents[i] for i in top_k]

Because the embeddings are normalized, that one @ (matrix–vector product) is cosine similarity against every chunk at once; np.argsort(-scores)[:k] grabs the highest few. Same retriever you've built before — here it's a component, not the whole app.

Try it

Ask the question from Chapter 2 and see what it pulls:

rag.py (temporary test)
for chunk in retrieve("How much does the Pro plan cost?"):
  print("-", chunk)
terminal
python rag.py
- Nimbus Notes Pro costs $6 per month and adds unlimited notebooks, version history, and offline sync.
- The Nimbus Notes free plan includes 3 notebooks and 500 MB of storage.
- All paid Nimbus Notes plans come with a 30-day money-back guarantee.

The exact fact needed — "$6 per month" — comes back first, even though the question said "Pro plan" and never said "cost per month" the way the document does. Semantic retrieval matched on meaning, and the two runner-up chunks are plausibly relevant too.

That top-of-list chunk is the ground truth the model was missing in Chapter 2. Now we hand it over — and watch the hallucination disappear.