Naive RAG is four lines of code: embed the query, top-k nearest neighbours, stuff them in the prompt, generate. It usually scores around 60–75% on a task-specific eval — good enough for a demo, nowhere near good enough for production.
Getting from 75 to 95 is the whole discipline. This page is that delta, in concrete steps, ranked by how much return each move produces.
@startuml
skinparam BoxPadding 10
|User / Client Interface|
start
:Submit Domain Query;
|Query Pipeline [[/wiki/AiPoweredSearch]]|
:Analyze Intent & Preprocess;
if (Requires Query Transformation?) then (yes)
:Generate HyDE or Decompose into Sub-Queries;
else (no)
:Normalize Query String;
endif
|Hybrid Retrieval Engine [[/wiki/HybridRetrieval]]|
fork
:Generate Dense Embedding Vector;
:Execute HNSW Approximate Nearest Neighbors [[/wiki/VectorDatabases]];
:Surface Top-K Dense Semantic Candidates;
fork again
:Tokenize & Stem Query Terms;
:Execute BM25 Inverted Index Keyword Scan;
:Surface Top-K Sparse Lexical Candidates;
fork again
:Evaluate Metadata & Security Constraints;
:Construct Tenant & Permission Filter Bitmask;
end fork
:Apply Permission Bitmask Filtering;
:Execute Reciprocal Rank Fusion (RRF);
note right
Combines dense & sparse ranks:
score = sum(1 / (k + rank_i))
end note
|Precision Scoring & Reranking|
:Pass Top-N Candidates to Cross-Encoder Model;
:Compute Deep Query-Document Attention Scores;
:Sort Candidates by Relevance Probability;
:Apply Context Compression & Token Budgeting;
|Synthesis & Verification [[/wiki/AgentPlanning]]|
:Inject Grounded Context Chunks into Prompt Template;
:Execute LLM Inference for Response Generation;
if (Grounding Verification Passed?) then (yes)
:Bind Inline Citations to Chunk Provenance;
|User / Client Interface|
:Deliver Verified Response with Citations;
stop
else (no)
|Synthesis & Verification [[/wiki/AgentPlanning]]|
:Flag Low Grounding / Uncertainty;
:Trigger Query Refinement or Fallback;
|User / Client Interface|
:Deliver Guarded Response with Warning;
stop
endif
@enduml
If your RAG is dense-only (pure vector search), adding BM25 keyword search and fusing with reciprocal rank fusion typically adds 5–15 points of recall at near-zero latency cost. Dense embeddings miss exact-string queries ("error 451", "SKU ABC-1234"); BM25 misses on semantic paraphrase. The fusion gets both.
Minimum RRF:
def rrf(dense_hits, bm25_hits, k=60):
scores = {}
for rank, d in enumerate(dense_hits):
scores[d.id] = scores.get(d.id, 0) + 1 / (k + rank + 1)
for rank, d in enumerate(bm25_hits):
scores[d.id] = scores.get(d.id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: -x[1])
See HybridRetrieval for the full ranking pipeline this wiki uses.
Do this first. Nothing else in this list beats the effort-to-payoff ratio of adding BM25.
"How do I chunk my documents" is the second question every RAG system gets wrong. The trap is either (a) fixed-size 500-token chunks that cut sentences in half, or (b) a section-header splitter that produces one 8000-token chunk and ten 50-token ones.
Working strategy:
{document_id, section_path, preceding_heading}. Your retriever uses the heading path for filtering and the context in the prompt.For structured content (code, tables, JSON) stop before applying text chunking. Treat code blocks and tables as atomic units. A chunk that contains half a table is actively harmful — the model will hallucinate the missing rows.
A cross-encoder reranker over the top-20 candidates typically adds another 3–8 points of nDCG. Cohere Rerank, BGE-reranker-large, or a locally hosted ms-marco-MiniLM-L-12-v2 all work.
Shape:
query → retrieve top 50 (hybrid) → cross-encode rerank → top 5-10 to prompt
Cross-encoders are slow compared to ANN (50ms vs 2ms for 50 candidates on GPU) but the recall gain outweighs it for anything with a human in the loop. Skip reranking only for real-time < 100ms budgets where you'd rather take the recall hit.
User queries are often bad retrieval queries. Three moves that help:
Use one of these, not all three. Stacking transformations adds latency faster than quality.
Most RAG failures in production aren't retrieval failures, they're context failures — the right information is retrieved but it's for the wrong tenant, wrong time period, or wrong document version. Metadata filtering is the defence.
Once you have the right chunks, the prompt determines whether the model uses them. Three rules:
[Source: doc-423, section "Returns Policy"] beats dumping raw text. The model cites correctly only if you tell it how.You cannot improve what you don't measure. Minimum RAG eval:
| Metric | What it catches | Cost |
|---|---|---|
| Retrieval recall@k | Did the right chunk surface? | Free once you have labelled query→doc pairs |
| nDCG@10 | Is the ranking sensible? | Same labels as recall |
| Answer faithfulness (RAGAS) | Does the answer stick to the retrieved context? | One LLM call per eval row |
| Answer correctness | Is the answer actually right? | Human label or LLM-as-judge |
| Latency p95 | Does the pipeline fit your budget? | Free |
Build a frozen eval set of 100–500 queries with gold-labelled relevant chunks and expected answers. Run it on every change. This wiki's own retrieval is evaluated this way — see HybridRetrieval and RetrievalExperimentHarness.
"Just put the whole corpus in a 1M-token context" sounds like it replaces RAG. It doesn't, for three reasons:
Long context is the right answer for some tasks (e.g. summarising a single 500-page contract). RAG is the right answer for retrieval over a knowledge base. They compose — use RAG to narrow to the relevant subset, then long-context for the deep analysis.
Deploying the complete multi-stage RAG pipeline requires balancing accuracy against inference latency. In interactive search or agentic workflows, end-to-end latency should typically remain under 1,500ms.
| Pipeline Stage | Subsystem | Typical Latency Budget | Fallback Strategy on Timeout |
|---|---|---|---|
| Query Transformation | Small Speculative LLM (HyDE / Multi-Query) | 150–250ms | Bypass HyDE; use raw normalized query string |
| Sparse Lexical Search | BM25 / Inverted Lucene Index | 10–25ms | Proceed with dense-only candidates if Lucene unresponsive |
| Dense Semantic Search | HNSW Index on Vector Store | 20–60ms | Proceed with sparse-only candidates if vector index lags |
| Rank Fusion (RRF) | In-Memory Score Combiner | 2–5ms | None (deterministic CPU operation) |
| Cross-Encoder Rerank | BGE-Reranker / Cohere Rerank API | 80–180ms | Skip reranking; take top-K directly from RRF scoring |
| Context Compression | Token Slicer / Selective Context Filter | 15–30ms | Truncate context to hard token ceiling |
| LLM Generation | Frontier Model Streaming Inference | 600–1,200ms | Stream tokens immediately with progressive citation markers |
By decoupling each stage with explicit fallback thresholds, the system degrades gracefully under heavy load without degrading user-facing uptime.
When RAG quality regresses in prod, look in this order:
Instrument each layer with a trace span so a single query shows retrieval, rerank, and generation cost/latency. Without this you'll spend hours reproducing intermittent issues by hand.
Cluster Hub: Agentic AI Hub: Cognitive Architectures, Orchestration, and Autonomous Systems (agentic-ai)