An LLM knows what it was trained on – but not your notes, your PDFs, your code, or your company's docs. RAG (Retrieval-Augmented Generation) fixes that by letting a model answer questions using your documents, without retraining. It's how "chat with your PDF" apps work, and you can build it entirely on your own machine with a local LLM.

This guide explains the four moving parts of RAG and walks through a working, local-first example.

The four parts of RAG

  1. Load – read your documents (PDF, markdown, text) into chunks of text.
  2. Embed – turn each chunk into a vector (a list of numbers that captures its meaning) using an embedding model.
  3. Retrieve – when a question comes in, embed it too and find the chunks whose vectors are closest to it.
  4. Generate – give the LLM the retrieved chunks plus the question, and let it answer using only that context.

That's the whole trick. The LLM never sees your whole document set – only the few relevant chunks, which it uses as grounded context. That's why answers are accurate and why you don't need to retrain anything.

Step 1: Load and chunk

Split your documents into chunks of a few hundred characters, with a little overlap between them. Why overlap? So that context that straddles a chunk boundary isn't lost. A simple approach in Python:

import re
text = open("notes.md").read()
chunks = [text[i:i+500] for i in range(0, len(text), 400)]  # 500 chars, 100 overlap

Tools like LlamaIndex and LangChain handle smarter chunking out of the box, but the idea is the same.

Step 2: Embed

An embedding model converts text into a vector – a fixed-size list of numbers – where similar meaning lands near each other. A tiny embedding model runs fast on any CPU, so this is cheap. Ollama has several, e.g. nomic-embed-text:

ollama pull nomic-embed-text

Embed every chunk and store the vectors (a simple JSON or SQLite file is fine for personal use; vector databases like Chroma or Qdrant are for scale).

Step 3: Retrieve

When someone asks a question, embed it with the same model, then find the chunks whose vectors are most similar. "Similarity" is usually cosine similarity – smaller angle between vectors means more related meaning. Pick the top 3–5 chunks:

import numpy as np
# assume emb(q) is the question vector, emb(chunks) is a matrix
scores = emb(chunks) @ emb(q) / (norms * norm_q)   # cosine sim
top = np.argsort(scores)[::-1][:5]                  # top 5 chunk indices

If the question and a chunk contain similar words or concepts, they'll rank highly – that's how RAG finds the right passages without the LLM seeing everything.

Step 4: Generate

Finally, prompt the LLM with the retrieved chunks as context. The magic is the instruction: answer using only this context.

system: You are a helpful assistant. Answer the question using ONLY the
context below. If the answer isn't there, say you don't know.

Context:
--- chunk 3 ---
--- chunk 7 ---
--- chunk 11 ---

Question: What did the notes say about X?

With a local model (e.g. a 7B via Ollama), this runs fully offline. The local AI guide shows how to run the model; the prompting guide explains why the "only use the context" instruction matters so much.

A simpler shortcut: Ollama + Open WebUI

If you don't want to write the code, Open WebUI has RAG built in: upload a document, and it chunks, embeds, and retrieves for you automatically against your local Ollama models. It's the fastest way to try RAG – the "from scratch" version above is how it works under the hood.

When RAG works (and when it doesn't)

Chunk size and retrieval count are the two knobs that matter most. Too few or too-small chunks → missing context. Too many → the LLM gets distracted or the answer gets long. Start with ~500-char chunks and top-5 retrieval, then tune.

Bottom line

RAG is load, embed, retrieve, generate – four steps you can run entirely locally. It turns a general LLM into one that genuinely knows your documents, with grounded, citable answers and no retraining. Whether you use a library or the raw steps above, the concept is the same, and it's one of the most useful things you can build on top of a local model.