altaruntime.comthe runtime log
the documents own the truth; the AI only does the wording
the fable files.governed q&a · no. 01

how RAG works
for document retrieval

(a high-level tour of the standard architecture)
Vinod Zachariahauthor & architecture ideationthe runtime log · no. 01

Back in 2023, I built a first version of this at home in Edmonton for a specific problem. Alberta Wildfire was publishing a new PDF bulletin every day during a bad spring, and reading a fresh twenty‑page report every morning to answer “is anything burning near us?” was tedious. I stood up Wildfire Insight Engine — Streamlit for the UI, LangChain to orchestrate, FAISS as the vector store, an OpenAI model behind it all — and pointed it at every day’s bulletin PDF. It ran in an afternoon and made a beautiful demo: pick a day, type a plain question, get a highlighted paragraph from that day’s report. It also happily invented answers when the bulletin didn’t cover the question, because nothing in that pipeline told it to refuse. That gap is what separated a party trick from a system anyone else could actually trust.

Two years on, the architecture has hardened. Streamlit is still fine for prototyping, but the vector stores that stuck around are the boring, indexable ones. LangChain and its cousins got quieter about what they promise. And the discipline that actually matters — a relevance floor, honest refusal, cited answers — became the price of admission rather than a stretch goal. This piece is what remained after the noise settled.

Every organization that has ever asked "what if we let people ask our documents questions?" ends up building the same thing. Not because it’s the only option, but because five or six years of open‑source work have converged on one shape. It has a name: Retrieval‑Augmented Generation, or RAG, and the reference implementation lives in a hundred GitHub repos wearing slightly different clothes.

This piece is the high‑level map. Five moving parts, one honest rule, three ways to deploy. If you have read a LangChain, LlamaIndex, or Haystack README, none of this will surprise you. If you haven’t, this is the shortest path to understanding what those frameworks are doing behind the curtain.

what RAG solvesin one paragraph
A general‑purpose language model is fluent but forgetful about your documents. Fine‑tuning it on your data is expensive, slow to update, and leaks. RAG sidesteps both problems: at query time, it finds the passages in your documents that are most relevant to the question, hands them to a general model as context, and asks the model to answer only from what it was given. The model does not know your documents. It reads them, once, for each question.
fig. 01 · the two‑line thesis

01the pipeline, in five boxes

Every RAG system is these five stages. Names differ across frameworks; the shape does not.

the RAG pipeline5 stages
01
chunk
split each document into overlapping passages of ~500 tokens
02
embed
turn every chunk into a vector using an embedding model
03
store
write vectors + source metadata to a vector database
04
retrieve
at query time, embed the question and pull the top‑K similar chunks
05
generate
send question + chunks to an LLM, ask for a cited answer
fig. 02 · ingestion happens once, retrieval + generation happen per question

Stages 01–03 run once when documents change — call this ingestion. Stages 04–05 run for every user question — call this inference. Keeping the two loops separate is the single biggest architectural decision.

01 · chunk

Documents don’t fit in a language model’s context window, and even if they did, the model reads more clearly when it sees only what matters. You break each document into passages of roughly 300–800 tokens, usually with some overlap so ideas that straddle a chunk boundary are still findable. Every chunk keeps a link back to its source: file name, page number, section, version.

02 · embed

An embedding model reads each chunk and outputs a vector — a long list of numbers — that captures its meaning. Chunks about the same topic land near each other in vector space, regardless of the exact words they use. Common choices: OpenAI’s text‑embedding‑3, Cohere’s embed models, or open‑source options like all‑MiniLM or bge running locally.

03 · store

Vectors go into a database built for similarity search. The database keeps the vector, the chunk text, and the source metadata together, so a retrieved chunk can always cite its origin. Common choices: pgvector if you already run Postgres, Chroma or Qdrant for standalone open source, Pinecone or Weaviate if you want managed cloud.

04 · retrieve

When a user asks a question, you embed the question with the same model, then ask the database for the top‑K most similar chunks — usually 3 to 10. Modern setups add a second pass: a reranker reads the top‑K and reorders them by real relevance, discarding matches that were vector‑similar but semantically off.

05 · generate

The retrieved chunks and the original question go into a prompt that tells the model: answer the question using only what is between these delimiters, cite your sources, and say “I don’t know” if the answer isn’t there. The model writes a short, sourced answer. That is the whole trick.

answer only from what you were given, and say when you can’t.

02a worked example · the fable

Abstract pipelines are hard to trust. Here are the same five stages processing one very small document, end to end.

A farmer must cross a river with a fox, a hen, and a sack of corn. His boat carries only himself and one item at a time. Left alone, the fox eats the hen, and the hen eats the corn. So: he takes the hen across first. He returns, takes the fox across, but brings the hen back. He leaves the hen, ferries the corn to the fox, then returns for the hen. Nothing is eaten, because the wrong pair is never left alone.
fig. 03 · the source document, all five sentences of it

chunk · cut into passages

the fable, chunked5 pieces
c1
the setup
a farmer, a fox, a hen, a sack of corn
c2
the boat rule
carries only himself and one item
c3
the danger
fox eats hen · hen eats corn
c4
the sequence
hen across, fox across, hen back, corn across, hen across
c5
the outcome
nothing is eaten · the wrong pair is never left alone
fig. 04 · every chunk keeps a link back to source, page, and version

embed · turn each chunk into a vector

An embedding model reads each chunk and outputs a vector. Real embeddings are 768 to 3072 dimensions; three are shown below for illustration.

c1  →  [ 0.42, -0.18,  0.66, ...]
c2  →  [ 0.11,  0.79, -0.24, ...]
c3  →  [-0.34,  0.62, -0.08, ...]   // "the danger" - matters later
c4  →  [ 0.71, -0.45,  0.02, ...]
c5  →  [ 0.28,  0.14,  0.83, ...]

store · put the vectors in a database

Every row keeps three things together: the vector, the original text, and the metadata that will let the answer be cited.

retrieve · find the closest chunks at query time

query: "what did the fox eat?"top 3
1
c3 · the danger
similarity 0.89 · "fox eats hen · hen eats corn"
2
c1 · the setup
similarity 0.52 · "a farmer, a fox, a hen, a sack of corn"
3
c4 · the sequence
similarity 0.31 · below threshold, discarded
fig. 05 · only chunks above the relevance floor are passed to the LLM

generate · answer only from what was retrieved

the LLM's answergrounded · cited

question · what did the fox eat?

answer · If left alone with the hen, the fox eats the hen. In the story as told, no eating occurs, because the farmer prevents the wrong pair from being left alone.

source · fable.txt · chunk c3 “the danger”

fig. 06 · a grounded, cited answer from a general LLM

the refusal · what “I don’t know” looks like

Now ask a question the document doesn’t answer.

query: “what colour was the boat?”refused
!
top similarity
0.18 · below the 0.35 relevance threshold
!
action
skip the LLM call · return refusal directly

answer · The document does not mention the boat’s colour.

no chunk crossed the threshold. the LLM was never asked. the user got the truthful answer · this isn’t in the source.

fig. 07 · engineered refusal · the difference between a demo and a system you can deploy

Five sentences of source, five pipeline stages, one grounded answer, and one honest refusal. Every real RAG deployment is this same shape at larger scale.

03where to run it · three shapes

The pipeline above is engine‑agnostic. What changes across deployments is where each box runs. There are three sensible shapes.

local
everything on‑prem
+
LLM: Ollama + Llama 3 or Mistral
runs on a workstation or on‑prem GPU
+
embeddings: bge or all‑MiniLM
CPU works; GPU is faster
+
vector DB: Chroma or pgvector
docker container beside the app
+
framework: LangChain or LlamaIndex
one process, one repo
·
data never leaves
the whole point of this shape
cloud
managed everything
+
LLM: Anthropic Claude or OpenAI GPT‑4
API call, no infrastructure
+
embeddings: OpenAI, Cohere, or Voyage
same vendor as the LLM or not
+
vector DB: Pinecone or Weaviate Cloud
managed · serverless‑ish
+
framework: LangChain or a thin wrapper
most of the code is glue
·
fastest to a demo
slowest to defend at an audit
fig. 03 · local vs cloud — same pipeline, different homes for each box
hybridthe shape most orgs land on
+
LLM: cloud API (Claude, GPT‑4)
generation quality where it matters most
+
embeddings + vector DB: on‑prem
the documents themselves never leave
+
what the cloud sees
only the retrieved passages, only when a question is asked
·
good for regulated industries
healthcare, legal, finance, public sector
fig. 04 · hybrid keeps ingestion private, borrows cloud quality only for generation

04the one rule that keeps it honest

A RAG pipeline that always answers is a RAG pipeline that lies. The system must be engineered to refuse when retrieval turns up nothing useful. That refusal has three moving parts:

engineered refusalthree checks
!
relevance threshold
if the best chunk’s similarity score is below X, refuse before calling the LLM
!
prompt discipline
system prompt explicitly permits “I don’t know” and forbids invention
!
post‑check
a second small model or rule verifies every claim in the answer is supported by a retrieved chunk
fig. 05 · the difference between a demo and a production system

The trap for most first builds is skipping the threshold in step one. Without it, a nonsense question retrieves nonsense chunks, and the LLM — being fluent — writes a nonsense answer that sounds authoritative. Every serious RAG system has a floor below which it says nothing.

05the common stack, named

You do not have to write any of this from scratch. Almost every serious RAG deployment picks from the same short list:

frameworks
the glue you probably don’t need to write
01
LangChain
largest ecosystem, most examples
02
LlamaIndex
retrieval‑first, cleaner for pure Q&A
03
Haystack
production‑shaped, popular for on‑prem
04
no framework at all
3 API calls in TypeScript — also fine
vector databases
where the vectors live
01
pgvector
if you already run Postgres
02
Chroma / Qdrant
standalone open source
03
Pinecone / Weaviate
managed, if you don’t want to run it
04
Elasticsearch / OpenSearch
if you already search a lot of text
fig. 06 · pick one from each column — opinionated defaults, not requirements
LLMs
the generation model
01
Anthropic Claude
strong at following the “answer only from context” rule
02
OpenAI GPT‑4
largest developer familiarity
03
Llama 3 · Mistral · Qwen
open weights, run via Ollama or vLLM
04
Google Gemini
long context is a natural fit for larger chunks
embedding models
the retrieval side
01
OpenAI text‑embedding‑3
default choice if you’re on their stack
02
Cohere embed‑multilingual
good for non‑English documents
03
bge‑large · nomic‑embed
open source, runs locally
04
Voyage AI
domain‑tuned models for legal, medical, code
fig. 07 · you can swap any single component without changing the shape

06why this shape wins

The reason every RAG project on GitHub ends up looking like the diagram in section 01 is not fashion. It is that RAG separates two things that have to be separated: knowledge (which changes constantly and must be citable) and language ability (which is stable and can be rented). A fine‑tuned model fuses them, and every change to your documents means retraining. RAG keeps them apart, and every change to your documents is one reindex away.

That is also why the boring parts — the chunking rule, the similarity threshold, the citation format — matter more than the choice of LLM. A great model with sloppy retrieval writes fluent lies. A modest model with rigorous retrieval writes short, correct, cited answers. The second is what you want.

rules of thumbfrom a hundred production builds
01
start with the simplest chunk size
500 tokens, 50‑token overlap, tune only if you have to
02
measure retrieval first, then generation
most RAG bugs are retrieval bugs wearing a language mask
03
add a reranker before adding a bigger LLM
cheaper, faster, larger accuracy gain
04
log every question, every retrieval, every answer
your test set writes itself
05
write “I don’t know” into the prompt on day one
refusal is a feature, not a fallback
fig. 08 · the things that separate a demo from something you can deploy

07closing

RAG is the reference architecture for grounding a language model in your data. It is boring, well‑understood, and almost commoditized. That is its strength: you can pick any framework, any vector DB, any LLM, and the pipeline still looks the same. What separates the demos from the systems is not the model you chose. It is the discipline of the retrieval floor, the honesty of the prompt, and the log that lets you prove where every answer came from.

the documents own the truth. the AI only helps them speak.
← more from the runtime log