From GPT to Production
Embeddings, vector DBs, and RAG
Retrieval-augmented generation end to end
A language model's weights are a closed book frozen at training time - retrieval hands it an open book, fetched by meaning, right when the question arrives.
An LLM only knows what it saw during training. Ask it about your company's internal wiki, a document written yesterday, or the exact number in a contract, and it will do the most dangerous thing it can: answer fluently and wrong, inventing a plausible detail because nothing in its weights says otherwise. Retrieval-Augmented Generation (RAG) fixes this by giving the model an external memory. Before answering, we embed the question into a vector, use it to retrieve the few most relevant document chunks by similarity, paste them into the prompt as context, and only then generate. The model stops guessing from memory and starts reading from sources it can cite.
The one-line idea
A closed-book exam rewards memorising; an open-book exam rewards finding the right page fast. RAG turns every question into an open-book exam: embed the query, look up the nearest passages in a vector database, and let the model answer only from what it just read.
From documents to vectors
You cannot search millions of words by re-reading them. Instead you turn text into geometry. Back in Lesson 12 we saw that an embedding model maps text to a point in a high-dimensional space where meaning becomes position - "invoice" and "billing" land close together even though they share no letters. The same model embeds both your documents and your question into that one shared space, so a question can find the passages that mean the same thing, not just the ones that spell it the same way.
Whole documents are too coarse to embed as one vector - a 40-page manual would collapse into a single blurry point. So we chunk: split each document into passages of a few hundred tokens (often with a little overlap so a sentence straddling a boundary isn't lost), and embed each chunk on its own. Each chunk becomes one point in the space, paired with its original text. That collection of (vector, text) pairs is your knowledge base.
The vector database: nearest neighbors, fast
Given the question's vector , retrieval means finding the document vectors pointing in the most similar direction. That is exactly the cosine similarity from Lesson 9 - the dot product of the two vectors, normalised so only their angle matters, not their length:
Computing this against a handful of chunks is trivial. Against millions, comparing to every single vector (an exact linear scan) is too slow for a live request. A vector database - FAISS, Pinecone, pgvector - builds an index for approximate nearest-neighbor (ANN) search: it returns almost the true top- in sublinear time by cleverly avoiding most comparisons. You trade a sliver of exactness for orders-of-magnitude speed. That trade-off is the whole game at scale.
Why cosine, not raw distance
Embedding vectors vary in length for reasons that have nothing to do with meaning - a longer chunk can push its vector further from the origin. Cosine similarity divides that magnitude out and keeps only the angle, so two passages about the same topic score high even if one is a sentence and the other a paragraph. In the demo below, the origin is the centre of the space and every chunk is a direction; the question's arrow retrieves whichever chunks sit at the smallest angle from it.
Retrieve - Augment - Generate
With the nearest chunks in hand, the rest is prompt assembly. Augment: stitch the retrieved passages into the prompt as a CONTEXT block, above the question, with an instruction to answer only from that context and cite it. Generate: the LLM does what it always does - predict the next tokens - but now the most relevant facts sit right there in its context window, so the highest-probability continuation is the grounded one. The citations let a human trace every claim back to a source. Trace one question through all four stages below.
The knowledge base in embedding space - 8 doc chunks (grey), your question (teal arrow), the retrieved hits (green).
Sources: [1] rate limits · [2] pricing
Stage 1 - the question becomes a vector in the same space as the document chunks.
The aha: grounding beats memorising
Flip the toggle to Without retrieval and the same model answers from its weights alone - smooth, confident, and often subtly wrong, because it is pattern-matching a plausible-sounding number instead of reading the real one. Turn retrieval back on and the wrong detail snaps to the cited fact. Nothing about the model changed; we changed what was in its context window. RAG also means you can update knowledge by editing the database - no expensive retraining, and private data never has to enter the weights at all.
# 1. Offline: chunk every document, embed it, store (vector, text) in the DB
index = VectorDB()
for chunk in split(docs, size=512, overlap=64): # chunking, with overlap
index.add(embed(chunk), chunk) # same embedding model for docs + queries
# 2. Online: answer a question by retrieving, augmenting, generating
def rag(question, k=4):
q = embed(question) # question -> vector, same space as docs
hits = index.search(q, k) # ANN: cosine-nearest k chunks, sublinear
ctx = "\n".join(f"[{i+1}] {h.text}" for i, h in enumerate(hits))
prompt = f"Use ONLY this context and cite [n].\n{ctx}\n\nQ: {question}"
return llm(prompt) # grounded, citable answerCheck yourself
In RAG, the retriever ranks document chunks against the question by:
Pasting the retrieved chunks into the prompt mainly helps the model:
Recall the three stages of RAG and what each one contributes.
Nail down the retrieval-augment-generate pipeline. Try to state it, then check.
Primary source
RAG was introduced by Lewis et al., "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" (2020) - the paper that coined the term and paired a neural retriever with a generator. For a gentle, code-first walkthrough of embeddings, similarity search, and where retrieval fits in the LLM stack, work through the Hugging Face LLM Course.
Ask your teacher
The retrieval step is the hash-table idea from Lesson 23 generalised. A hash table answers "give me the value for this exact key" in by jumping straight to a bucket. A vector index answers the fuzzier "give me the values whose keys mean the same thing" - locality-sensitive hashing and other ANN methods bucket nearby embeddings together so lookup stays sublinear over millions of vectors, trading exactness for speed (connection C4). Ask me how LSH hashes similar vectors into the same bucket, how chunk size and overlap change what gets retrieved, or how "re-ranking" a shortlist of candidates sharpens the final top-.
Lock it in
- An LLM's weights are frozen at training time - ask about fresh, private, or exact data and it will hallucinate fluently.
- RAG fixes this: embed the question, retrieve the nearest document chunks by cosine similarity from a vector DB, paste them into the prompt, generate from that grounded context.
- Chunking splits documents into embeddable passages; ANN indexes (FAISS, Pinecone, pgvector) keep retrieval sublinear at scale.
- Cosine similarity measures direction only (not magnitude), so a short sentence and a long paragraph about the same topic still match.
- Toggle retrieval off and the model guesses confidently from its frozen weights - toggle it on and the answer snaps to cited facts, updatable without retraining.