Skip to content

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.

RAG pipeline, traced end-to-end. Type a question or pick an example, then watch it flow through the four stages. Adjust top-k to change how many chunks are retrieved.

The knowledge base in embedding space - 8 doc chunks (grey), your question (teal arrow), the retrieved hits (green).

1Embed the question
question embedded to q = (0.24, 0.97) - a unit direction in the same space as the chunks.
planfreeratelimit
2Retrieve - cosine nearest chunks
[1]rate limits0.97
[2]pricing0.97
support0.61
SDKs0.56
API keys0.14
KQL query-0.07
regions-0.81
backups-0.98
3Augment - stitch chunks into the prompt
SYSTEM: Answer using ONLY the context. Cite sources as [n]. CONTEXT: [1] Rate limits - 100 req/s on Free, 1,000 req/s on Pro. Exceeding the limit returns HTTP 429. [2] Plans & pricing - Free tier: 5 GB storage, 1M reads/mo. Pro: $29/mo with higher limits. QUESTION: What are the rate limits on the free plan?
4Generate - the answer
Grounded answerAccording to the retrieved docs, Free allows 100 requests/second and Pro allows 1,000 requests/second, returning HTTP 429 when exceeded [1]; the Free tier includes 5 GB of storage and 1M reads per month, and Pro is $29/month [2].

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 answer
The whole loop in ~10 lines

Check 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.