← Back to home

Architecture · Retrieval-Augmented Generation

Production-Ready RAG Architecture

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.

Data Ingestion Managed: AWS Bedrock KB / Azure AI Search Managed: AWS Bedrock KB / Azure AI Search Scaling to Production Volume How retrieval works at scale — not a sequence after it Generation & Guardrails Managed: AWS Bedrock / Azure OpenAI writequeryFoundation ModelmTLS❄️Data Lake& Warehouse Deduplication (Exact) Hash the raw file, before extraction — catches repeat docs, audio, video Processed Docs ⚙️ Batch Service ⚙️ Batch Service Topic Kafka / SQS / Service Bus — Topic Kafka / SQS / Service Bus — OCR Docs & images → text — AWS Textract, Azure Doc AI 🔎 Speech-to-Text Audio/video → transcript — Whisper, AWS Transcribe 🎙️ Processed Data Deduplication (Near-Match) Now there's real text to compare — embedding similarity, Dedupe.io Noise & Boilerplate Removal Strips signatures, disclaimers, repeated headers — Unstructured.io Normalization Same fact, one format — dates, currency, entities, via dbt PII Tokenization Sensitive fields → safe tokens, before anything leaves — Presidio 🐧 Ready for Chunking Chunking Structure-aware splits, ~10–15% overlap — LangChain, LlamaIndex Embedding Chunk text → dense vector — text-embedding-3-large, BGE-large 🙋 User Query Query Caching Checked first, before anything else runs — exact + semantic cache, Redis Prompt Injection Defense Raw query + retrieved chunks are untrusted — screen both before they reach the model Query Understanding Raw text isn't retrieval-ready — expand, decompose, or route it first 🗄️ Vector DB Retrieval Scoring & Reranking Weighted hybrid score, then reranked — Cohere Rerank, BGE-reranker Sharding & Distributed Index One index can't scale past ~10-50M — partition by tenant, hash, or time Query Fan-Out & Global Merge Scatter to shards, merge before ranking — over-fetch per shard, then rerank Pre-Filtering & Tenant Isolation Filter permissions before the search runs — not after, on the results Incremental Indexing & Freshness Full re-index is impossible at this scale — upsert deltas, version, expire Conversational Memory Prior turns rewrite a follow-up into — a standalone query, before retrieval Source Confidence Scoring Freshness, trust, and overlap combine — before a chunk reaches the prompt Model & Context Assembly Which model, rarely fine-tuned — and how — context gets packed into its window Constrained Generation System prompt forces context-only answers — no outside knowledge, no assumptions Citation-Backed Responses Every claim links back to a source chunk — inline citations, not a trust-me answer Hallucination Fallback Insufficient evidence → decline, not guess — "I don't know" beats a wrong answer Continuous Evals Adversarial + benchmark sets, scheduled — not just tested once at launch Observability Trace every hop — query, chunks, prompt — not just the final response Structured 🗄️ DB 📇 CRM 🏭 ERP Semi-Structured { } JSON ✉️ Email </> XML Unstructured 📄 Docs 🖼️ Image 🎬 Video

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.

ComponentAWSAzureOn-prem
Vector databaseOpenSearch Serverless (vector engine), or Pinecone/Weaviate on EC2Azure AI Search — vector support built inQdrant, Milvus, or pgvector on your own Postgres
EmbeddingsBedrock — Titan Embeddings, or Cohere via BedrockAzure OpenAI embedding modelsSelf-hosted open model (BGE, E5) behind your own inference endpoint
LLM hostingBedrock (Claude, Llama, and others)Azure OpenAI ServiceSelf-hosted via vLLM or TGI on your own GPUs, or an air-gapped model
RerankingCohere Rerank via BedrockAzure AI Search's built-in semantic rankerSelf-hosted cross-encoder reranker (e.g. bge-reranker)
OrchestrationStep Functions + Lambda, or ECS/EKS for long-running workersDurable Functions, or AKS for workersKubernetes + a workflow engine (Temporal, Argo Workflows)
Ingestion & syncEventBridge + S3 event notifications trigger Lambda workersEvent Grid + Blob Storage triggers Azure FunctionsKafka, or a CDC tool like Debezium watching your own databases
Document storageS3Blob StorageMinIO, or a shared filesystem
Secrets & keysSecrets Manager / KMSKey VaultHashiCorp Vault
ObservabilityCloudWatch + X-Ray, or a third-party APMAzure Monitor + Application InsightsPrometheus + 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.