Retrieval-Augmented Generation (RAG) is an AI architecture that gives a Large Language Model (LLM) access to external knowledge at inference time. Instead of relying solely on what the model learned during training, RAG retrieves relevant documents from a knowledge base and augments the prompt before asking the LLM to generate an answer.
RAG solves two of the biggest problems with standalone LLMs: knowledge cutoff (the model does not know recent events) and hallucination (the model invents facts it does not know). It is now the most widely used pattern for building production AI applications.
RAG in 30 seconds
| Problem with raw LLMs | RAG solution |
|---|---|
| Training data has a cutoff date | Retrieve up-to-date documents at query time |
| Model hallucinates missing facts | Provide verified source documents in the prompt |
| Cannot access private/proprietary data | Index your own documents in a vector store |
| Context window is limited | Retrieve only the most relevant chunks |
| Fine-tuning is expensive | RAG requires no model retraining |
How RAG works — step by step
User query
│
▼
[1. Embed query] → query vector
│
▼
[2. Vector search] → top-K relevant chunks
│
▼
[3. Build prompt] → "Context: <chunks>\n\nQuestion: <query>"
│
▼
[4. LLM generates] → grounded answer
│
▼
Answer (with source citations)
Offline indexing (done once or periodically)
- Load documents — PDFs, web pages, database rows, Markdown files
- Chunk — split documents into passages (e.g., 512 tokens with 50-token overlap)
- Embed — convert each chunk to a dense vector using an embedding model
- Store — write vectors + metadata to a vector database (Chroma, Pinecone, pgvector, etc.)
Online retrieval (every query)
- Embed the user question using the same embedding model
- Similarity search — find the top-K chunks whose vectors are closest to the query vector
- Stuff into prompt — insert retrieved text as context for the LLM
- Generate — the LLM reads the context and answers the question
RAG architecture diagram
Documents ──► Chunker ──► Embedder ──► Vector DB
│
User Query ──────► Embedder ──► Retriever ─┘
│
▼
[Context + Query]
│
▼
LLM
│
▼
Answer
Simple RAG pipeline in Python
# pip install openai chromadb
import openai
import chromadb
from chromadb.utils import embedding_functions
# 1. Setup
client = openai.OpenAI() # uses OPENAI_API_KEY env var
embed_fn = embedding_functions.OpenAIEmbeddingFunction(
model_name="text-embedding-3-small"
)
chroma = chromadb.Client()
collection = chroma.create_collection("docs", embedding_function=embed_fn)
# 2. Index documents
documents = [
"Python was created by Guido van Rossum and released in 1991.",
"FastAPI is a modern Python web framework based on type hints.",
"RAG stands for Retrieval-Augmented Generation.",
"Vector databases store high-dimensional embeddings for similarity search.",
]
collection.add(
documents=documents,
ids=[f"doc_{i}" for i in range(len(documents))],
)
# 3. Query function
def rag_query(question: str, top_k: int = 2) -> str:
# Retrieve relevant chunks
results = collection.query(query_texts=[question], n_results=top_k)
context = "\n".join(results["documents"][0])
# Build augmented prompt
prompt = f"""Answer the question using ONLY the context below.
If the context does not contain the answer, say "I don't know."
Context:
{context}
Question: {question}
Answer:"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return response.choices[0].message.content
# 4. Use it
print(rag_query("When was Python released?"))
# "Python was released in 1991."
print(rag_query("What is FastAPI?"))
# "FastAPI is a modern Python web framework based on type hints."
print(rag_query("Who invented JavaScript?"))
# "I don't know." <- grounded, no hallucination
RAG with LangChain
# pip install langchain langchain-openai langchain-chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
# Load and split
loader = TextLoader("my_docs.txt")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
# Embed and store
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# RAG chain
prompt = ChatPromptTemplate.from_template("""
Answer the question based only on the following context:
{context}
Question: {question}
""")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
)
answer = chain.invoke("What does this document say about X?")
print(answer.content)
Chunking strategies
How you split documents dramatically affects retrieval quality.
| Strategy | How | Best for |
|---|---|---|
| Fixed-size | Split every N tokens | Simple, fast, good default |
| Recursive | Try paragraphs then sentences then words | General text (LangChain default) |
| Semantic | Group sentences by embedding similarity | Dense topic documents |
| Document-aware | Split by headings, code blocks | Markdown, HTML, code |
| Sliding window | Overlapping chunks (50-100 token overlap) | Prevents context loss at boundaries |
Rule of thumb: 256–512 tokens per chunk with 10–15% overlap. Experiment with your data.
Embedding models
| Model | Provider | Dimensions | Notes |
|---|---|---|---|
text-embedding-3-small |
OpenAI | 1,536 | Fast, cheap, good quality |
text-embedding-3-large |
OpenAI | 3,072 | Higher quality, more expensive |
text-embedding-ada-002 |
OpenAI | 1,536 | Legacy, still widely used |
all-MiniLM-L6-v2 |
HuggingFace | 384 | Free, runs locally, fast |
bge-large-en-v1.5 |
HuggingFace | 1,024 | Strong open-source model |
embed-english-v3.0 |
Cohere | 1,024 | Good multilingual support |
Always use the same embedding model for indexing and querying.
Vector databases
| Database | Type | Notes |
|---|---|---|
| Chroma | Open-source, local | Easy start, great for prototyping |
| pgvector | PostgreSQL extension | Keep data in Postgres, production-ready |
| Pinecone | Managed cloud | Serverless, scales automatically |
| Weaviate | Open-source / cloud | Hybrid search (dense + sparse) |
| Qdrant | Open-source / cloud | Fast, Rust-based, payload filtering |
| Milvus | Open-source / cloud | High scale, billions of vectors |
| FAISS | Meta library | In-memory, no persistence, fast |
For most projects: Chroma (dev) → pgvector or Pinecone (production).
RAG vs Fine-tuning vs Prompt engineering
| Prompt engineering | RAG | Fine-tuning | |
|---|---|---|---|
| When | Built-in knowledge sufficient | Need external / private data | Need style / format changes |
| Cost | Low (just prompt tokens) | Medium (embedding + retrieval) | High (training compute) |
| Freshness | Static (training cutoff) | Real-time (update index) | Static (re-train to update) |
| Privacy | Data in prompt only | Data in your vector store | Data used in training |
| Accuracy | Depends on model knowledge | High (grounded in docs) | High (baked into weights) |
| Complexity | Low | Medium | High |
| Hallucination risk | High for unknown facts | Low (sources in context) | Medium |
Decision rule:
- Need factual accuracy over private docs → RAG
- Need the model to learn a new tone or format → Fine-tuning
- Want the model to use existing knowledge better → Prompt engineering
- Best results → RAG + fine-tuned model
Common RAG failure modes
| Problem | Symptom | Fix |
|---|---|---|
| Retrieval misses | Relevant doc not returned | Better chunking, reranking, hybrid search |
| Context stuffing | Too many chunks, LLM confused | Reduce top-K, add reranker |
| Chunk boundary cut-off | Answer split across chunks | Add overlap, use semantic chunking |
| Embedding mismatch | Query and doc use different models | Always use same model for both |
| Hallucination despite context | LLM ignores retrieved docs | Stronger system prompt, lower temperature |
| Stale index | Old data returned | Implement incremental indexing or TTL |
| Slow retrieval | High latency | Use ANN index (HNSW), add caching |
| No answer (false negative) | "I don't know" when doc exists | Expand top-K, tune similarity threshold |
Advanced RAG patterns
Hybrid search (dense + sparse)
Combine vector similarity with keyword search (BM25) for better recall:
# Using Weaviate hybrid search
results = collection.query.hybrid(
query="Python web framework",
alpha=0.5, # 0 = BM25 only, 1 = vector only
limit=5
)
Reranking
Use a cross-encoder to re-score retrieved chunks before passing to LLM:
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
scores = reranker.predict([(query, chunk) for chunk in top_chunks])
sorted_chunks = [c for _, c in sorted(zip(scores, top_chunks), reverse=True)]
top_reranked = sorted_chunks[:3]
HyDE (Hypothetical Document Embeddings)
Ask the LLM to generate a hypothetical answer first, then embed that for retrieval:
# 1. Generate hypothetical answer
hypo_answer = llm.generate(f"Write a passage that answers: {question}")
# 2. Embed the hypothetical answer (not the question)
query_vector = embed(hypo_answer)
# 3. Search with this vector — often retrieves better results
Multi-query retrieval
Generate multiple query variants to improve recall:
queries = llm.generate(f"Generate 3 different phrasings of: {question}")
all_chunks = [retrieve(q) for q in queries]
unique_chunks = deduplicate(all_chunks)
RAG evaluation metrics
| Metric | What it measures | Tool |
|---|---|---|
| Context Precision | Are retrieved chunks relevant? | RAGAS |
| Context Recall | Did retrieval find all needed info? | RAGAS |
| Faithfulness | Is the answer grounded in context? | RAGAS |
| Answer Relevancy | Does the answer address the question? | RAGAS |
| Latency | End-to-end response time | Custom timing |
# pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
results = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(results)
RAG stack options
| Component | Lightweight | Production |
|---|---|---|
| Document loader | Manual / pypdf | Unstructured.io |
| Chunker | RecursiveCharacterTextSplitter | Semantic chunker |
| Embeddings | text-embedding-3-small | text-embedding-3-large or BGE |
| Vector DB | Chroma (local) | pgvector / Pinecone / Qdrant |
| Retriever | Simple similarity | Hybrid + reranker |
| LLM | GPT-4o-mini | GPT-4o / Claude Opus 4.6 |
| Framework | None (raw API) | LangChain / LlamaIndex |
| Evaluation | Manual | RAGAS |
| Observability | Print logs | LangSmith / Langfuse |
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Embedding entire documents | Retrieves too much noise | Chunk to 256–512 tokens |
| Skipping overlap between chunks | Cuts context at boundaries | Add 10–15% overlap |
| Using different embedding models for index vs query | Vectors are incomparable | Always use the same model |
| No reranking for top-K > 5 | LLM gets confused by noise | Add cross-encoder reranker |
| Trusting retrieval blindly | Missing chunks cause hallucination | Log retrieved context, monitor faithfulness |
| Not updating the index | Stale, wrong answers | Set up periodic re-indexing or upsert |
| Huge chunks to include more context | LLM "lost in the middle" problem | Keep chunks small, retrieve more of them |
| No evaluation dataset | No way to measure improvements | Build a Q&A ground truth set early |
RAG vs related terms
| Term | What it is |
|---|---|
| RAG | Retrieval + generation: fetch docs at query time to ground the LLM |
| Fine-tuning | Train the LLM on new examples to update its weights |
| Semantic search | Find similar documents by vector distance — the retrieval step of RAG |
| Vector database | Database optimised for storing and querying embedding vectors |
| Embedding | A dense numerical representation of text capturing semantic meaning |
| Context window | The token limit of the LLM — RAG helps by selecting only relevant text |
| Prompt engineering | Crafting instructions for the LLM — RAG provides the content alongside |
| Agent | An LLM that decides when and what to retrieve (vs static RAG pipeline) |
| GraphRAG | Microsoft technique: build a knowledge graph over docs for better reasoning |
Frequently asked questions
Why use RAG instead of just making the context window bigger? Even 1M-token context windows have limits: cost scales linearly with tokens, latency increases, and LLMs suffer "lost in the middle" — they attend worse to content in the middle of a long context. RAG retrieves only the handful of relevant passages, keeping prompts focused and cheap.
Does RAG eliminate hallucination? No — it reduces hallucination when the answer exists in the knowledge base. If the retrieved chunks are irrelevant or the LLM ignores them, it can still hallucinate. Combine RAG with faithfulness evaluation and a strong system prompt such as "answer ONLY from the context".
When should I fine-tune instead of RAG? Fine-tune when you need the model to change its behavior or format — writing in your brand tone, following a specific output schema, or learning domain-specific abbreviations. Use RAG when you need the model to access specific facts or documents.
How do I choose chunk size? Start with 512 tokens and 50-token overlap. Run retrieval experiments: if recall is low (the right chunk is never retrieved), try smaller chunks. If the LLM complains about missing context, try larger chunks or increase top-K.
Can RAG work without a vector database? Yes — for small knowledge bases (fewer than 100 documents), you can embed everything in memory with FAISS or even a simple cosine similarity loop. Vector databases add scalability, persistence, filtering, and hybrid search at the cost of infrastructure.
What is the difference between RAG and an AI agent? A basic RAG pipeline retrieves documents in a fixed sequence every time. An AI agent decides dynamically which tools to call, when to retrieve, how many times to retry, and can chain multiple retrieval steps. Think of RAG as one tool an agent might use, not a mutually exclusive pattern.