RAG Pipeline Explained — Retrieval-Augmented Generation
RAG grounds LLM answers in your own knowledge base by retrieving relevant document chunks at query time — making it the go-to architecture for enterprise AI.
What is it?
Retrieval-Augmented Generation (RAG) is an architecture that grounds LLM responses in a specific knowledge base by retrieving relevant documents at query time and injecting them into the prompt context. Instead of relying on parametric knowledge (memorized during training), the LLM reads retrieved text to formulate its answer — like an open-book exam instead of a closed-book one.
How it works
RAG has two phases:
-
INDEXING (offline): Documents → chunk into 256–512 token pieces with 10–15% overlap → embed each chunk with an embedding model (e.g. text-embedding-3-small, 1536-dim vectors) → store vectors + metadata in a vector database (Pinecone, Chroma, pgvector, Weaviate).
-
RETRIEVAL + GENERATION (online, per query): User query → embed with same model → cosine similarity search against stored vectors → retrieve top-k chunks (k=3–10) → assemble context → send to LLM with retrieved context in the prompt.
Critical: the embedding model used for indexing and querying MUST be identical — switching models invalidates the entire index.
Reranking: a cross-encoder reranker (e.g. Cohere Rerank) can rescore retrieved chunks for relevance before generation. Adds ~100ms latency but improves answer quality significantly.
Hybrid search combines dense (embedding) and sparse (BM25 keyword) retrieval. Dense catches semantic matches ('car' ↔ 'automobile'); sparse catches exact matches and rare terms. Reciprocal Rank Fusion (RRF) merges the two result lists.
Code example
from anthropic import Anthropic import chromadb client = Anthropic() vdb = chromadb.Client() collection = vdb.get_collection("docs") def rag_query(question: str) -> str: results = collection.query(query_texts=[question], n_results=3) context = "\n\n".join(results["documents"][0]) response = client.messages.create( model="claude-opus-4-5", max_tokens=512, messages=[{"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}\nAnswer based only on the context above."}] ) return response.content[0].text
Key concepts
Common mistakes
Using different embedding models for indexing vs querying. If you index with text-embedding-ada-002 and later switch to text-embedding-3-small, the vector spaces are incompatible — all retrieved results will be wrong.
Chunk size too large (>1024 tokens). A 2000-token chunk has an embedding that averages over all its content — the signal for any specific sentence gets diluted.
No overlap between chunks. If a key answer spans the boundary between chunk 3 and chunk 4, neither chunk alone contains the full answer. Use 10–15% overlap.
Trusting retrieval without verifying faithfulness. The LLM can still hallucinate content that is not in the retrieved chunks — instruct the model explicitly: 'Answer ONLY from the provided context.'
Not filtering low-similarity results. Always set a minimum similarity threshold (0.70–0.75 cosine) and tell the LLM 'no relevant documents found' when nothing passes the threshold.
Interview questions on this topic
Explain the difference between HNSW and IVF vector indexes. What are the speed, memory, and recall tradeoffs?
Practice answering these with structured learning → Start on CrackLab
Continue learning — explore the full AI Engineering curriculum
30+ structured topics from Python fundamentals to deploying production LLM systems. Free to start.
Related free guides
LLMs are neural networks that predict the next token using Transformer attention — understanding them unlocks every AI engineering discipline.
Prompt engineering is the highest-leverage LLM skill — the right technique can improve accuracy by 30–40% with zero additional compute during training.
AI agents extend LLMs with a feedback loop — perceive → reason → act → observe → repeat — enabling them to solve multi-step tasks that a single prompt cannot.