You wouldn't expect a brilliant colleague to know the contents
of a document they've never read.
But you would expect them to read it quickly if you handed it to them,
then answer your questions accurately based on what it says.
RAG is how you hand documents to a language model.
01 — The Problem RAG Solves
Every LLM has a knowledge cutoff — a date after which it knows nothing. GPT-4's cutoff is early 2024. LLaMA-3's is early 2024. Whatever happened after that date, whatever lives in your private documents, whatever is in your company's internal wiki — the model has never seen any of it. Ask about it and it will either say "I don't know" or, worse, invent something plausible-sounding that is completely wrong.
The obvious solution is to put the relevant information in the prompt. If you want the model to answer questions about a specific document, just paste the document in. For a short document, this works fine. But what if you have 500 documents? Or 10,000? You can't paste them all in — most models have a context window of 8k–128k tokens, and even a moderate knowledge base blows past that immediately. And even if you could fit everything in, it would be incredibly slow and expensive to process the entire library on every single question.
Imagine you hire a research assistant who has read everything in the world but doesn't have access to your company's private files. You give them a question. Instead of asking them to memorise your entire file room, you have a filing system: you quickly locate the 3 most relevant files, hand them to the assistant, and they answer based on what those files say.
RAG is that filing system. When a question comes in, you find the most relevant pieces of your knowledge base and hand them to the model. The model answers based on what you handed it — not based on what it vaguely remembers from training. It is grounded, accurate, and up to date.
RAG solves three problems at once:
02 — The RAG Pipeline at a Glance
RAG has two distinct phases. The first happens once, offline, before any questions arrive. The second happens on every single query in real time.
Load documents → Split into chunks → Convert to vectors → Store in vector database
Embed question → Find similar chunks → Build prompt → Generate answer
That's the whole pipeline. Every RAG system — from a quick weekend project to a production enterprise search engine — follows this exact shape. The details differ; the structure doesn't.
03 — Stage by Stage: What Each Component Does
Stage 1 — Load: Getting Text Into the Pipeline
The first step is simply reading your documents. PDFs, Word files, web pages, Markdown files, database exports — everything needs to be converted to plain text before anything else can happen. Libraries like LangChain provide loaders for almost every format. PyMuPDF handles PDFs reliably. Unstructured extracts clean text from mixed-format documents including tables and headers.
The goal of this stage is simple: get the text out. Clean it up — remove page headers, footers, duplicate whitespace — and you're ready for the next step.
Stage 2 — Chunk: Splitting Into Manageable Pieces
You cannot embed an entire document as a single unit. If a user asks about one specific policy in a 50-page handbook, you don't want to retrieve all 50 pages — you want to retrieve the 3 paragraphs that actually answer the question. Chunking splits your documents into smaller, self-contained pieces that can be individually retrieved.
Before digital search, researchers used index card systems. Instead of storing entire books on cards, they wrote one key idea per card — a sentence, a definition, a finding. When they needed to answer a question, they flipped through the cards to find the relevant ones, pulled those out, and worked from them.
Chunks are index cards. Each one captures a coherent piece of information. The smaller the card, the more precisely you can match it to a specific question. The larger the card, the more context it carries but the less precise the match.
The most common approach is recursive character chunking: split at paragraph breaks first, then at sentence breaks, then at word breaks — only splitting smaller if the chunk still exceeds the target size. A typical chunk size for RAG is 300–500 tokens with a 50–100 token overlap between adjacent chunks, so that a sentence split across a boundary doesn't lose its context.
Stage 3 — Embed: Turning Text into Numbers
This is where Day 23 (Word Embeddings) comes back in full force. Once you have your chunks, you convert each one into a vector — a list of numbers that represents its meaning. The embedding model is trained so that similar text produces similar vectors. Two chunks that talk about "refund policy" will have vectors that point in nearly the same direction in the high-dimensional space, even if they use slightly different words.
vector: [0.82, -0.14, 0.33, 0.61, ...] (384 dimensions)
chunk: "We issue refunds in under a week."
vector: [0.79, -0.11, 0.35, 0.58, ...] ← very close → high similarity
chunk: "The quarterly revenue report is attached."
vector: [-0.21, 0.67, -0.43, 0.12, ...] ← very different → low similarity
Popular embedding models: BGE-M3 and E5-large (open-source, excellent quality), Sentence-BERT (lightweight, fast), OpenAI text-embedding-3-small (cloud API, easy to use). All of these are BERT-style encoders — exactly the architecture from Day 26 — producing a fixed-size vector from the mean-pooled token outputs.
Stage 4 — Store: The Vector Database
The embeddings are stored in a vector database — a specialised database that is optimised for one specific operation: given a query vector, find the stored vectors that are most similar to it. This is called Approximate Nearest Neighbour (ANN) search, and it's what makes retrieval fast even across millions of chunks.
| Tool | Best for | Where it runs |
|---|---|---|
| FAISS | Fast local search, research, no infra needed | In-memory / local file |
| Chroma | Prototyping, small projects, easy setup | Local or Docker |
| Pinecone | Production at scale, managed service | Cloud (managed) |
| Weaviate | Hybrid search (vectors + keyword), open-source | Self-hosted or cloud |
| Qdrant | Rust-backed, high performance, open-source | Self-hosted or cloud |
For a first project, FAISS or Chroma will take you further than you think. Move to a managed service like Pinecone only when you have millions of documents and need guaranteed uptime.
Stage 5 — Retrieve: Finding What's Relevant
When a question arrives, you embed it using the same embedding model used to embed the chunks. The resulting query vector is then compared to every stored chunk vector using cosine similarity — the same metric from Day 23. The chunks with the highest similarity scores are the most relevant ones and get retrieved.
Result: a number between -1 and 1
1.0 = identical meaning 0.0 = unrelated -1.0 = opposite meaning
Top-k chunks (typically k=3 to 5) are selected and passed to the LLM
The beauty of this connection: it's the exact same vector similarity concept from Day 23, just applied at scale. "king − man + woman ≈ queen" used the same cosine distance to find the closest vector. Here, "What is the refund policy?" finds the chunk whose vector is closest to the query vector. Same math, same intuition, production use case.
Stage 6 — Generate: The Grounded Answer
The retrieved chunks are assembled into a prompt — along with the original question and a clear instruction to answer only based on the provided context. This prompt is sent to an LLM (GPT-4, LLaMA-3, Claude — whatever you're using) which generates the final answer.
You are a helpful assistant. Answer the question using ONLY
the provided context. If the answer is not in the context,
say "I don't have that information."
Context:
[Chunk 1: "Refunds are processed within 5 business days..."]
[Chunk 2: "To request a refund, contact support@company.com..."]
[Chunk 3: "Refunds are not available for digital downloads..."]
Question: How long do refunds take?
Answer: Based on our policy, refunds are processed within 5 business days.
04 — The Simplest Possible Working RAG System
Below is the simplest end-to-end RAG pipeline that actually works — no LangChain abstractions, no external APIs required. Pure Python with two libraries: sentence-transformers for embeddings and faiss-cpu for vector search. This is the clearest way to see exactly what's happening at each step.
# pip install sentence-transformers faiss-cpu
# That's all you need. No LangChain, no API keys, no cloud service.
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
# ── Your "knowledge base" (in a real system: loaded from PDFs/docs) ──────
documents = [
"Refunds are processed within 5 business days of the approved request.",
"To request a refund, email support@company.com with your order number.",
"Refunds are not available for digital downloads or subscription plans.",
"Shipping typically takes 3-7 business days within the country.",
"Express shipping takes 1-2 days and costs an additional $15.",
"Our office hours are Monday to Friday, 9am to 6pm.",
"You can track your order using the link sent to your email.",
]
# ── Stage 1: Embed all documents (done once, offline) ────────────────────
model = SentenceTransformer("all-MiniLM-L6-v2") # small, fast, free
doc_embeddings = model.encode(documents) # shape: (7, 384)
# ── Stage 2: Build the vector index ──────────────────────────────────────
dim = doc_embeddings.shape[1] # 384 dimensions
index = faiss.IndexFlatL2(dim) # exact nearest-neighbour search
index.add(doc_embeddings.astype(np.float32)) # store all vectors
print(f"Index built: {index.ntotal} documents stored\n")
# ── Stage 3: Answer a question ───────────────────────────────────────────
def ask(question: str, k: int = 3) -> str:
"""Retrieve top-k relevant chunks and build a grounded answer."""
# Embed the question using the SAME model
q_vec = model.encode([question]).astype(np.float32)
# Find k most similar document vectors
distances, indices = index.search(q_vec, k)
# Collect the retrieved chunks
retrieved = [documents[i] for i in indices[0]]
# Build a simple grounded answer (in production: send to an LLM)
context = "\n".join([f" • {c}" for c in retrieved])
return f"Question: {question}\n\nRelevant context found:\n{context}"
# Try it out
print(ask("How long does a refund take?"))
print("\n" + "─"*55 + "\n")
print(ask("Can I track my delivery?"))
print("\n" + "─"*55 + "\n")
print(ask("Is express delivery available?"))
Index built: 7 documents stored
Question: How long does a refund take?
Relevant context found:
• Refunds are processed within 5 business days of the approved request.
• To request a refund, email support@company.com with your order number.
• Refunds are not available for digital downloads or subscription plans.
───────────────────────────────────────────────
Question: Can I track my delivery?
Relevant context found:
• You can track your order using the link sent to your email.
• Shipping typically takes 3-7 business days within the country.
• Express shipping takes 1-2 days and costs an additional $15.
───────────────────────────────────────────────
Question: Is express delivery available?
Relevant context found:
• Express shipping takes 1-2 days and costs an additional $15.
• Shipping typically takes 3-7 business days within the country.
• You can track your order using the link sent to your email.
Two libraries. No API keys. No database server. No framework. The entire pipeline — embed, index, retrieve — is 25 lines of code. In a production system you would replace the "Relevant context found" section with a real LLM call (swap in OpenAI, LLaMA, or any model), replace the in-memory FAISS index with a persistent vector database, and replace the hardcoded list with a document loader that reads real PDFs. But the core logic is identical to what you see here. Every enterprise RAG system is this, with more engineering around it.
05 — A Real Use Case: The Personal AI Assistant
Let's make this concrete with the simplest possible real-world scenario. You have a folder of documents about yourself — your CV, portfolio projects, publications, a personal bio. You want to build a chatbot that can answer questions about you accurately, without hallucinating.
🧑💻 Use Case: Personal Branding Chatbot
The problem: Someone visits your portfolio and asks "What projects has Mohamed worked on in medical AI?" or "What is Mohamed's tech stack?" A generic LLM either says "I don't know" or invents something. Neither is acceptable.
The solution: A RAG system over your own documents — CV, project descriptions, GitHub README files, blog posts. Every question gets answered from your actual documented work. The model is grounded; the answers are accurate; and the system stays up to date whenever you update your documents.
The pipeline:
📄 Load your CV + project READMEs + bio as plain text
✂️ Chunk into ~300-token pieces (one chunk per project, one per skill section)
🔢 Embed with BGE-M3 or sentence-transformers
🗄️ Store in Chroma (runs locally, no cloud needed)
❓ User asks: "What has Mohamed built in computer vision?"
🔍 Retrieve: top-3 chunks mentioning brain tumour CNN, face recognition, object detection
✍️ Generate: LLaMA-3 answers from those specific chunks — accurate, cited, grounded
Result: A chatbot that represents you faithfully, available 24/7, and that gets smarter every time you add a new project to your docs.
I am currently building a full personal branding RAG project — an AI assistant trained on my own portfolio, CV, and project documentation. It will demonstrate the complete production pipeline: document loading, semantic chunking, BGE-M3 embeddings, FAISS/Chroma vector store, retrieval with re-ranking, and grounded generation with LLaMA-3. Follow along on GitHub — the repo will go live soon.
06 — The Connection to Word Embeddings (Day 23)
RAG is not a new idea — it's the same vector similarity from Day 23, applied at scale with a purpose. Let's trace the exact connection:
The only difference between "find the word closest to 'king − man + woman'" and "find the document chunk closest to 'What is the refund policy?'" is the scale and the purpose. The underlying operation — cosine similarity in an embedding space — is identical. This is why understanding word embeddings first makes RAG immediately intuitive: you've already seen how meaning maps to geometry. RAG just applies that geometry to search.
07 — RAG vs Fine-Tuning: Which One Do You Need?
After Day 28 (Fine-Tuning) and Day 29 (RAG), you now know both major approaches to making a model domain-specific. They are not alternatives — they serve fundamentally different purposes and are often used together.
| Question | Use RAG | Use Fine-Tuning |
|---|---|---|
| Does the model need to know specific facts? | ✓ RAG — facts live in documents, always up to date | ✗ Fine-tuning bakes facts in, which go stale |
| Does the model need a specific writing style or format? | ✗ Prompting or fine-tuning better for this | ✓ Fine-tuning — teach consistent behaviour |
| Do you need to update the knowledge frequently? | ✓ RAG — add new documents anytime, no retraining | ✗ Fine-tuning requires re-training for each update |
| Do you need to cite sources? | ✓ RAG — you know exactly which chunk was used | ✗ Fine-tuning — no traceability |
| Is the knowledge private and sensitive? | ✓ RAG — data only accessed at query time | ~ Fine-tuning exposes data during training |
| Does the model need to follow a complex task format? | ✗ RAG doesn't change model behaviour | ✓ Fine-tuning — teach structured output reliably |
The medical VLM project in this portfolio used both: fine-tuning (QLoRA on Qwen 2.5 7B) to teach the model to reason about retinal images and use clinical vocabulary, and RAG (FAISS + LangChain) over 20+ medical textbooks to ground the model's answers in cited clinical sources. Fine-tuning taught the model how to think about the domain. RAG gave it what to think about — the specific facts, guidelines, and cases it needs to answer accurately. Neither alone was sufficient.
08 — What Makes a RAG System Fail (and How to Fix It)
09 — A Thought to Sit With
RAG gives a language model access to external knowledge by finding documents whose meaning is close to the question's meaning. It doesn't understand the documents — it retrieves them based on geometric proximity in a learned vector space, then hands them to a model that generates text based on patterns in its weights.
At no point in this pipeline does anyone actually "understand" anything in the way a human does. Yet the outputs are often more accurate than a human working from memory. Is this a profound result about the nature of understanding — or just a very sophisticated pattern-matching system dressed up in terminology that implies comprehension? Does the distinction matter, as long as the answers are good?
10 — Coming Up Next
Tomorrow — Day 30: Prompt Engineering. You now know how to build systems that retrieve the right information. Day 30 covers how to talk to the model once you have it: zero-shot and few-shot prompting, chain-of-thought, system prompts, and the full picture of how prompt design affects output quality — including what can go wrong when prompts are manipulated.
11 — Go Deeper
📄 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — Lewis et al. (2020)
The original RAG paper from Facebook AI. Introduced the term and the architecture. Section 2 (model) and Section 4 (results) are the essential parts.
🔗 LangChain RAG Tutorial — Official Docs
The clearest step-by-step tutorial for building a production RAG system with LangChain, including document loaders, text splitters, embeddings, and retrieval chains.
🔗 Chroma — Getting Started
The easiest vector database to start with. Runs locally, integrates with LangChain and LlamaIndex out of the box. The quickstart takes under 10 minutes.
📄 RAGAS: Automated Evaluation of RAG — Es et al. (2023)
The evaluation framework for RAG systems — faithfulness, answer relevancy, context precision. Essential reading before deploying a RAG system in production.
▶ RAG from Scratch — LangChain (2024)
YouTube series building RAG from first principles in short focused videos. The best hands-on companion to this post.
- The problem: LLMs have a knowledge cutoff, can't read private documents, and hallucinate when they don't know something. RAG fixes all three without retraining the model.
- Two phases: offline (build the knowledge index — load, chunk, embed, store) and online (answer queries — embed question, retrieve chunks, generate answer).
- Chunking: documents are split into small, focused pieces (~300–500 tokens) so that retrieval is precise. One chunk = one coherent idea. Overlap (~50 tokens) prevents answers from being split across boundaries.
- Embeddings: each chunk is converted to a vector by an embedding model (BGE-M3, E5, sentence-transformers). Similar meaning = similar vector. This is the exact same concept as Day 23 word embeddings — applied to retrieval at scale.
- Vector database: stores all chunk vectors for fast similarity search. FAISS for local/research; Chroma for quick prototypes; Pinecone / Qdrant / Weaviate for production scale.
- Retrieval: the question is embedded with the same model, then compared to stored chunk vectors using cosine similarity. Top-k most similar chunks are retrieved.
- Generation: retrieved chunks + original question are assembled into a prompt. The LLM is instructed to answer only from the provided context. If no relevant chunk exists, it should say so.
- RAG vs fine-tuning: RAG gives the model updatable, traceable, private knowledge. Fine-tuning changes the model's behaviour and style. They are complementary — the best systems use both.
- Common failures: chunks too large, mismatched embedding models, no chunk overlap, missing re-ranking, and no explicit "I don't know" instruction.
- Simplest implementation:
sentence-transformers+faiss-cpu+ any LLM. Under 30 lines of Python. Every production RAG system is this, with more engineering around it.
- What is RAG and what problem does it solve?
RAG (Retrieval-Augmented Generation) solves the problem of LLMs having fixed knowledge — they cannot access private documents, don't know about events after their training cutoff, and hallucinate when asked about things they don't know. RAG adds a retrieval step: before generating an answer, it searches a knowledge base for the most relevant document chunks and injects them into the prompt. The model then generates a grounded response based on retrieved text rather than parametric memory alone. The knowledge base can be updated at any time without retraining. - Explain the role of embeddings in RAG. How does retrieval actually find relevant chunks?
Embeddings convert text into dense vectors in a high-dimensional space where semantically similar text produces geometrically close vectors. In RAG: (1) every chunk in the knowledge base is converted to a vector by an embedding model and stored in a vector database; (2) at query time, the user's question is embedded using the same model, producing a query vector; (3) cosine similarity is computed between the query vector and all stored chunk vectors; (4) the top-k highest-similarity chunks are retrieved. This works because the embedding model was trained to map semantically similar text to similar vectors — so "refund processing time" and "How long does a refund take?" will have high cosine similarity even though they share few exact words. It's semantic search, not keyword matching. - What is chunking and why does chunk size matter?
Chunking splits documents into smaller pieces that can be individually embedded and retrieved. Chunk size is one of the most impactful RAG hyperparameters: too large, and each chunk covers many topics — retrieval precision drops because an irrelevant chunk gets retrieved whenever it contains a related keyword anywhere in its 2,000 tokens. Too small, and chunks lack context — a 50-token chunk might contain the answer but not enough surrounding information for the model to use it correctly. The sweet spot for most use cases is 300–500 tokens with a 50–100 token overlap between adjacent chunks so that sentences spanning chunk boundaries don't lose context. For technical documentation or code, smaller chunks (150–300 tokens) often work better because information is denser. - How would you evaluate a RAG system? What metrics would you use?
RAG evaluation has two components: retrieval quality and generation quality. For retrieval: context precision (were the retrieved chunks actually relevant?) and context recall (were all the relevant chunks in the knowledge base actually retrieved?). For generation: faithfulness (does the answer only use information from the retrieved context, or does it hallucinate?) and answer relevancy (does the answer actually address the question asked?). The RAGAS framework automates these metrics using a small LLM as a judge. In practice: start with human evaluation on a gold standard question set (50–100 Q&A pairs with known correct answers), measure retrieval hit rate (is the correct chunk in top-3?), then measure answer correctness manually or with RAGAS. Never deploy without evaluating — it's easy to build a RAG system that looks reasonable in demos but fails silently on edge cases. - A user asks your RAG chatbot a question and the answer is wrong. How do you debug it?
Isolate which stage failed: (1) Log the retrieved chunks — did the correct chunk get retrieved? If not, the problem is in retrieval (embedding model mismatch, chunks too large, k too small, or the answer isn't in the knowledge base at all). (2) If the right chunk was retrieved but the answer is still wrong, the problem is in generation — the model is either ignoring the context (strengthen the grounding instruction) or misinterpreting it (try a stronger model or simplify the chunk). (3) If no chunk scored above a reasonable similarity threshold (say, 0.4), the model should have said "I don't know" — but if it didn't, your fallback instruction is missing or too weak. (4) Check the chunk content itself: is it clean text, or did extraction produce garbage from a scanned PDF? Systematic logging of retrieved chunks, similarity scores, and generated answers for every query is the most valuable investment you can make in a RAG system — without it, debugging is guesswork.
📄 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — Lewis et al. (2020)
The original RAG paper from Facebook AI. Sections 2 and 4 are the core contributions — the retrieval-augmented generation model and the knowledge-intensive task results.
🔗 LangChain RAG Tutorial
The most complete step-by-step guide for building a production RAG system. Covers document loading, splitting, embedding, vector stores, and retrieval chains with real runnable code.
📄 RAGAS: Automated Evaluation of Retrieval Augmented Generation — Es et al. (2023)
The standard evaluation framework. Defines faithfulness, answer relevancy, context precision, and context recall. Essential before deploying any RAG system.
🔗 Chroma Documentation
The easiest vector database to get started with — runs locally, no infrastructure required, integrates seamlessly with LangChain and LlamaIndex.
▶ RAG from Scratch — LangChain YouTube Series
A series of short, focused videos building RAG from first principles. The best hands-on companion to this post — watch alongside reading.