A working prototype answers questions from ten PDFs over a weekend. Production is a different problem: thousands of documents that change every day, real users asking real questions, and real consequences when the answer is wrong. This is the architecture — and the plain-language reasoning behind every piece of it.
01 · Landing the data
From raw source to clean and ready
Hover any tile or the lake, and click a cleanup step for details — chunking and retrieval come next, once this part is settled.
Hover a tile or the lake to read what it does — click a cleanup step to open its details.
02 · The framework
What this should be — and what it shouldn't
Most production RAG failures trace back to one of these, not to the choice of language model.
Should be
Retrieval and generation as two separate, independently scalable services
Every retrieved chunk traceable back to its exact source document
Every answer checked against what was actually retrieved, before it's shown
Access control enforced at retrieval time, not filtered after the fact
An eval suite that runs on every change, not just before the first launch
Should not be
One giant prompt with the whole knowledge base pasted in
A single vector database call with no fallback if it's slow or down
Chunking by a fixed character count, ignoring document structure
An LLM call with no timeout, no retry, no circuit breaker
"We tested it and it worked" as the entire QA process
03 · The details that actually matter
Deep dives
Chunking, done right
Chunking is just cutting long documents into smaller pieces so retrieval can find the right piece instead of returning an entire 40-page PDF. Cut wrong and you split a sentence in half — or separate a table from its own header.
Cut on natural boundaries — headings, paragraphs — not a fixed character count
Overlap chunks by ~10–15% so a fact split across a cut isn't lost
Attach metadata to every chunk (title, section, date, source, permissions) — this is what filtering and access control run on later
For big documents, cut twice: one broad summary chunk, plus smaller detail chunks underneath it, so retrieval can start broad and drill down
Hybrid search & reranking
Vector search alone is bad at exact things — a policy number, a product SKU, a legal clause quoted word for word. Classic keyword search (BM25) is bad at understanding what you meant if you didn't use the exact words. Hybrid search runs both and merges the results.
Reranking is the step after: hybrid search might hand back 40 "maybe relevant" chunks. A reranker — a smaller, cheaper model whose only job is judging relevance — re-sorts them so only the best 5–8 reach the expensive LLM call. This one step usually improves answer quality more than switching to a bigger model.
Avoiding hallucinations
A hallucination isn't the model "lying" — it's the model generating something plausible when the retrieved context didn't actually contain the answer. Fight this at the retrieval step, not the prompt:
Set a minimum relevance score — if nothing retrieved clears the bar, say "I don't have that" instead of guessing
Require every claim in the answer to point back to a specific retrieved chunk
If two retrieved sources disagree, flag it instead of silently picking one
Route high-stakes answers (medical, legal, financial) to a human reviewer instead of auto-sending
Backpressure, retries, background jobs, fallbacks
Backpressure: if ingestion can process 100 documents a second and a source system dumps 50,000 changed files at once, queue that work and process it at a sustainable rate instead of falling over — the same idea as a message queue absorbing a spike.
Retries: an embedding call fails maybe 1 in 500 times. Retry with backoff and a cap, and make retries idempotent — retrying the same job twice should never create duplicate data.
Background jobs: indexing a document should never happen inside a request a user is waiting on. A source changes, an event fires, a worker indexes it — the user's next query just sees the updated result.
Fallbacks: reranker down? Skip it, use raw hybrid results. Primary LLM down or rate-limited? Fall back to a secondary model. Retrieval times out? Say "let me get back to you" instead of hanging the request. Every stage needs a defined "what if this fails," not just a happy path.
Evals — how to actually test this
You can't eyeball-test a RAG system the way you'd test a normal feature — the same question can get a slightly different answer every run. You need a fixed set of test questions with known-correct answers (or known-correct source documents), run automatically every time chunking, the embedding model, or a prompt changes.
Retrieval precision/recall — did it fetch the right chunks, and how much noise came with them
Answer correctness — does the generated answer match the expected one
Groundedness — is every claim actually backed by a retrieved chunk
Hallucination rate — how often it says something no retrieved chunk supports
Latency — end-to-end, not just the LLM call
Tools people actually use: Ragas and TruLens (open-source RAG eval frameworks that automate most of the above), LangSmith and Langfuse (tracing, eval, and prompt versioning together) — and for a lot of teams, a small custom eval harness, because off-the-shelf tools don't always match your exact definition of "correct."
Checking output quality in production
Beyond automated evals, three checks teams routinely skip:
Show the user which sources the answer came from — if you can't show them, you probably shouldn't trust the answer either
Log every query, its retrieved chunks, and the final answer — when someone reports a wrong answer, that log is the only way to tell whether retrieval or generation was at fault
Sample real production conversations weekly and have an actual human read them, not just watch a dashboard number
04 · Buy vs. build, per cloud
What to actually run this on
The pipeline above is cloud-neutral. Here's what fills in each box on AWS, Azure, and on-prem.
Component
AWS
Azure
On-prem
Vector database
OpenSearch Serverless (vector engine), or Pinecone/Weaviate on EC2
Azure AI Search — vector support built in
Qdrant, Milvus, or pgvector on your own Postgres
Embeddings
Bedrock — Titan Embeddings, or Cohere via Bedrock
Azure OpenAI embedding models
Self-hosted open model (BGE, E5) behind your own inference endpoint
LLM hosting
Bedrock (Claude, Llama, and others)
Azure OpenAI Service
Self-hosted via vLLM or TGI on your own GPUs, or an air-gapped model
Kafka, or a CDC tool like Debezium watching your own databases
Document storage
S3
Blob Storage
MinIO, or a shared filesystem
Secrets & keys
Secrets Manager / KMS
Key Vault
HashiCorp Vault
Observability
CloudWatch + X-Ray, or a third-party APM
Azure Monitor + Application Insights
Prometheus + Grafana + an OpenTelemetry collector
The one rule underneath all of this
Every box above exists to answer one question honestly: can this system say "I don't know" instead of guessing? Chunking, hybrid search, and reranking make it more likely the right answer is even in front of the model. Guardrails and evals make it possible to catch the system when it's wrong anyway. Retries, backpressure, and fallbacks make sure a slow dependency degrades the experience instead of breaking it outright. None of it guarantees a perfect system — it guarantees a system you can actually trust the failure modes of.