Retrieval-Augmented Generation (RAG)
Retrieval-Augmented Generation (RAG) is an AI architecture that combines a language model with an external knowledge retrieval system, allowing the model to look up relevant information at inference time rather than relying solely on knowledge baked into its weights during training. It matters because it solves one of the most practical limitations of large language models — stale, hallucinated, or missing knowledge — by grounding responses in real, retrievable documents. The counterintuitive fact: RAG often outperforms a model ten times its size on knowledge-intensive tasks, simply because finding the right context is more powerful than memorizing more parameters.
A language model without RAG is like a brilliant analyst locked in a room with no reference books — it can only answer from memory, and memory fades, distorts, and has gaps. RAG gives that analyst a filing cabinet: before answering, the system first retrieves the most relevant documents, then generates an answer using both the question and those documents as input. The "aha moment" is realizing that knowledge and reasoning are separable — you can keep a relatively small, cheap model for reasoning while outsourcing knowledge to a searchable, updatable database. This means you can update what your AI "knows" without retraining it at all — just update the database.
How It Works
RAG has three core stages: indexing, retrieval, and generation. Here's the pipeline in plain terms, with the key math that drives it.
Stage 1 — Indexing (Build the Knowledge Base)
Your documents are split into chunks, each chunk is converted into a dense vector (an embedding), and those vectors are stored in a vector database. ```python from sentence_transformers import SentenceTransformer import numpy as np
# Load a small but capable embedding model embedder = SentenceTransformer("all-MiniLM-L6-v2") documents = [ "The Eiffel Tower was completed in 1889.", "Paris is the capital of France.", "Python was created by Guido van Rossum.", ]
# Each document becomes a fixed-length vector doc_embeddings = embedder.encode(documents) print(doc_embeddings.shape) # (3, 384) — 3 docs, 384 dimensions each ```
Stage 2 — Retrieval (Find Relevant Chunks)
When a user asks a question, that question is also embedded into the same vector space. The system then finds the stored document vectors that are closest to the query vector. The standard similarity measure is cosine similarity:
Explanation: This measures the angle between two vectors — a score of 1.0 means they point in the same direction (highly similar), while 0.0 means they are completely unrelated.
Symbols:
- — the query embedding vector (the user's question, encoded)
- — a document embedding vector (one stored chunk)
- — the dot product: multiply corresponding elements and sum them
- — the magnitude (length) of the query vector
```python from sklearn.metrics.pairwise import cosine_similarity query = "When was the Eiffel Tower built?" query_embedding = embedder.encode([query])
# Compare query against all stored doc embeddings scores = cosine_similarity(query_embedding, doc_embeddings)[0]
# Retrieve top-1 most relevant document best_idx = np.argmax(scores) print(f"Best match: '{documents[best_idx]}' (score: {scores[best_idx]:.3f})")
# Best match: 'The Eiffel Tower was completed in 1889.' (score: 0.812) ```
Stage 3 — Generation (Augment the Prompt)
The retrieved document(s) are injected into the prompt sent to the language model. The model sees both the question and the supporting context, and generates a grounded answer.
Explanation: The probability of a good answer is now conditioned on both the retrieved context and the query together, rather than the query alone — the model "reads" the retrieved chunks before responding.
Symbols:
- — the ideal conditional probability we want
- — the language model's learned probability distribution
- — concatenation: context and query are joined into one combined prompt
```python import openai retrieved_context = documents[best_idx] query = "When was the Eiffel Tower built?" prompt = f"""Use the following context to answer the question. Context: {retrieved_context} Question: {query} Answer:""" response = openai.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}] ) print(response.choices[0].message.content)
# "The Eiffel Tower was completed in 1889." ```
A Minimal End-to-End RAG Pipeline
```python import numpy as np from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity import openai class SimpleRAG: def __init__(self, documents: list[str]): self.documents = documents self.embedder = SentenceTransformer("all-MiniLM-L6-v2")
# Index: encode all documents once at startup self.doc_embeddings = self.embedder.encode(documents) def retrieve(self, query: str, top_k: int = 2) -> list[str]: """Find the top_k most relevant document chunks.""" q_emb = self.embedder.encode([query]) scores = cosine_similarity(q_emb, self.doc_embeddings)[0] top_indices = np.argsort(scores)[::-1][:top_k] return [self.documents[i] for i in top_indices] def generate(self, query: str, context_chunks: list[str]) -> str: """Augment the prompt with context and call the LLM.""" context = "\n".join(f"- {chunk}" for chunk in context_chunks) prompt = ( f"Answer using only the context below.\n\n" f"Context:\n{context}\n\n" f"Question: {query}\nAnswer:" ) response = openai.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}] ) return response.choices[0].message.content def ask(self, query: str) -> str: """Full RAG pipeline: retrieve then generate.""" chunks = self.retrieve(query, top_k=2) return self.generate(query, chunks)
# Usage docs = [ "Python was created by Guido van Rossum and released in 1991.", "PyTorch is an open-source machine learning framework by Meta.", "The transformer architecture was introduced in the paper " "'Attention Is All You Need' (2017).", ] rag = SimpleRAG(docs) print(rag.ask("Who created Python?"))
# "Python was created by Guido van Rossum." ```
Patrick Lewis is a research scientist at Meta AI (then Facebook AI Research) and University College London, who led the team that published the landmark 2020 paper "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." His work formalized RAG as a trainable end-to-end architecture where both the retriever and generator could be jointly fine-tuned, distinguishing it from earlier ad-hoc combinations of search and generation. A surprising detail: the original RAG paper's retriever used Dense Passage Retrieval (DPR) trained on Wikipedia, and the entire knowledge base was just the English Wikipedia — yet it beat task-specific models that had seen far more specialized training data.
Intuition
Imagine you're a doctor asked to diagnose a rare condition you've never personally encountered. You could try to recall everything from medical school — or you could quickly pull three relevant journal articles from a shelf, read the key sections, and then form your diagnosis using both your trained judgment and those fresh references. RAG makes language models work like the second doctor. Your knowledge base is the medical library. Embeddings are like tagging each article with a precise subject fingerprint. The retriever is like a librarian who, given your question, instantly fetches the three most relevant articles. The LLM is the doctor — it never goes to the library untrained, but it's no longer limited to what it memorized years ago. The key intuition about vector similarity: two chunks of text are "close" in embedding space if they talk about the same concept, not just if they share the same words. "The author of Python" and "Guido van Rossum created a programming language" will have similar embeddings even though they share almost no words — because the embedding model learned meaning, not just vocabulary.
Historical Development
Origins · 1 minhow this was discovered
Why It Matters
Prerequisites
What This Leads To
See Also
Dense Passage Retrieval · Vector Databases · LangChain Framework · Prompt Engineering · Semantic Search