Artificial Intelligence

Why Your RAG Pipeline Fails in Production (And the Architecture That Fixes It)

Table of Contents

Key takeaway: The gap between a RAG prototype and a reliable RAG system is almost never the language model. It is retrieval quality — and retrieval quality is determined by chunking strategy, hybrid search, and reranking long before the model ever sees a token.


The Demo-to-Production Gap

Retrieval-Augmented Generation has the unusual distinction of being one of the easiest architectures to demonstrate and one of the hardest to operate. A working prototype takes roughly forty lines of code: load documents, split them into fixed-size chunks, embed the chunks, store the vectors, embed the user’s question, fetch the nearest neighbours, and stuff the results into a prompt.

That prototype will impress a stakeholder in a meeting. It will also fail the moment it meets a real corpus.

The failure mode is specific and consistent. Engineering teams report that accuracy on curated test questions sits somewhere around ninety percent, then collapses to fifty or sixty percent once actual users start asking actual questions. Nothing in the code changed. What changed is that real questions are ambiguous, real documents are structurally messy, and real corpora contain near-duplicate content that fixed-size chunking shreds into unusable fragments.

Understanding why requires separating the two halves of the system. The generation half — the language model — has improved dramatically and continues to improve without any effort on your part. The retrieval half has not, because retrieval quality is a property of your data and your preprocessing decisions. No model upgrade rescues a pipeline that hands the model the wrong three paragraphs.

This article walks through where naive implementations break and what a defensible production architecture looks like.


Why Naive RAG Breaks

The canonical tutorial pipeline makes four assumptions that do not survive contact with production data.

Assumption one: semantic similarity equals relevance. Vector search retrieves text that is semantically near the query embedding. Semantic nearness and answer-bearing relevance are correlated but distinctly different. A question like “what is our refund window for enterprise contracts?” will happily retrieve three chunks discussing refund policy in general terms while missing the single clause that specifies thirty days for enterprise tiers — because that clause is phrased in legal language that embeds far from conversational phrasing.

Assumption two: documents are prose. Embedding models are trained predominantly on flowing text. Tables, nested lists, code blocks, and form fields embed poorly. A pricing table split across two chunks becomes two sets of orphaned numbers with no headers attached to either.

Assumption three: one retrieval pass is enough. Complex questions decompose into sub-questions. “How does our latency compare to the SLA we signed with the vendor?” requires retrieving both the observed latency figures and the contractual SLA — two different documents, likely two different embedding neighbourhoods.

Assumption four: more context is better. It is not. Research on long-context behaviour consistently shows a “lost in the middle” effect, where information positioned in the centre of a long context window is recalled substantially less reliably than information at the beginning or end. Padding the prompt with twenty marginally relevant chunks actively degrades answer quality while multiplying your token bill.


Document Parsing: The Stage Before the Bottleneck

Before chunking can go wrong, parsing has usually already gone wrong. This stage receives the least attention and quietly determines how much signal survives into the index.

The problem is that most enterprise knowledge lives in formats designed for human eyes rather than machine consumption. PDFs encode visual position, not logical structure — a two-column research paper extracted naively yields text that alternates between columns mid-sentence. Scanned documents require optical character recognition, and OCR errors propagate silently into embeddings where nobody will ever notice them. Slide decks carry meaning in spatial arrangement that flattens into meaningless word soup. Spreadsheets encode relationships in cell references that plain-text extraction discards entirely.

A useful discipline is to inspect the extracted text before it reaches the chunker. Sample twenty documents per format, read the raw extraction output, and count how many are comprehensible to you as a human reader. If you cannot follow the text, no embedding model will represent it usefully. Teams that skip this audit routinely discover months later that an entire document class — usually the scanned contracts or the slide archive — has been contributing pure noise to every query.

Where extraction quality is poor, the remedies are unglamorous but effective: use layout-aware parsers that understand reading order rather than raw text dumps; route scanned documents through a dedicated OCR pipeline with a confidence threshold and flag low-confidence pages for human review; and for formats that resist extraction entirely, consider using a vision-capable model to produce a clean textual description of each page. That last option is expensive per document but runs once at ingestion rather than on every query, which changes the economics considerably.

The general principle: garbage entering the index is far more costly than garbage entering a traditional database, because the failure is silent. A malformed database row throws an error. A malformed chunk simply produces subtly wrong answers forever.


Chunking Is the Real Bottleneck

If you fix one thing, fix chunking. It determines the ceiling on everything downstream, and it is the stage most teams treat as a solved problem with a default parameter.

Fixed-size character splitting — the default in most frameworks — is the worst reasonable option. It severs sentences mid-clause, separates headings from the content they describe, and splits tables from their column labels.

Better strategies, in rough order of implementation cost:

Structure-aware splitting. Parse the document’s native structure first. Split on heading boundaries in Markdown or HTML, on section breaks in PDFs, on function boundaries in source code. A chunk that corresponds to a semantic unit of the original document is inherently more coherent than one that corresponds to a character count.

Contextual headers. Prepend the document title and heading path to every chunk before embedding. A chunk reading “Thirty days from the invoice date.” is meaningless in isolation. The same chunk prefixed with “Enterprise Agreement › Section 7: Refunds › Refund Window” is retrievable and self-explanatory.

Parent-child indexing. Embed small, precise chunks for retrieval accuracy, but return their larger parent sections to the model for generation. You get the precision of narrow embeddings and the completeness of wide context. This single change often produces the largest measurable improvement in end-to-end answer quality.

Table extraction as a separate path. Do not embed tables as flat text. Extract them, serialise each row into a natural-language sentence that repeats the column headers, and index those sentences. A row becomes “For the Enterprise tier, the monthly price is $499 and the included seat count is 50.” This is verbose and it works.

def build_chunk_text(chunk, doc_title, heading_path):
    """Prepend structural context before embedding."""
    header = f"{doc_title}"
    if heading_path:
        header += " > " + " > ".join(heading_path)
    return f"{header}\n\n{chunk.text}"


def serialize_table_row(headers, row, table_caption):
    """Turn a table row into retrievable prose."""
    pairs = [f"the {h.lower()} is {v}" for h, v in zip(headers, row) if v]
    return f"In {table_caption}: " + ", ".join(pairs) + "."

Dense vector search and sparse keyword search fail in opposite directions, which makes them unusually good partners.

Dense retrieval excels at paraphrase and conceptual matching. It handles “how do I cancel?” matching a document that says “termination procedure” without either sharing a word. It fails on exact identifiers: product SKUs, error codes, version numbers, proper nouns, and acronyms. Ask a dense retriever for ERR_QUOTA_4041 and it will return chunks about quota errors generally, ranking the specific error code no higher than its neighbours.

Sparse retrieval — BM25 and its variants — is the inverse. It nails exact tokens and rare terms, and it fails completely on vocabulary mismatch.

Running both and fusing the results captures the strengths of each. Reciprocal Rank Fusion is the standard approach and is appealing precisely because it requires no score normalisation: it operates purely on rank position, which sidesteps the incompatible scoring scales of cosine similarity and BM25.

def reciprocal_rank_fusion(result_lists, k=60):
    """Fuse ranked lists by rank position, not score."""
    scores = {}
    for results in result_lists:
        for rank, doc_id in enumerate(results, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)


# Retrieve wide from both, fuse, then narrow.
dense_hits = vector_index.search(query_embedding, top_k=50)
sparse_hits = bm25_index.search(query_text, top_k=50)
fused = reciprocal_rank_fusion([dense_hits, sparse_hits])[:20]

The constant k=60 is the widely used default from the original fusion literature; it damps the influence of top-ranked outliers. It is worth tuning against your own evaluation set, but sixty is a reasonable starting point.


The Reranking Stage Nobody Budgets For

Retrieval and ranking are different problems, and conflating them is a common architectural mistake.

Your vector index optimises for approximate recall at scale — finding roughly the right neighbourhood among millions of vectors, fast. It does this by comparing two independently computed embeddings, which means the query and the document never actually interact during scoring. That independence is what makes the index fast and what makes its ranking coarse.

A cross-encoder reranker does the opposite. It takes the query and a candidate chunk together as a single input and outputs a relevance score. Because the model attends across both simultaneously, it can evaluate whether the chunk actually answers the question rather than whether it merely discusses the same subject. The cost is that it cannot be precomputed or indexed — every query-document pair requires a forward pass, so it only works on a shortlist.

The resulting pattern is a funnel:

Stage Candidates in Candidates out Typical latency
Dense + sparse retrieval Full corpus 50–100 20–50 ms
Reciprocal rank fusion 50–100 20–30 < 1 ms
Cross-encoder rerank 20–30 3–5 80–300 ms
Generation 3–5 chunks 1 answer 1–4 s

Retrieve wide, rerank hard, generate narrow. Teams that skip reranking and simply increase top_k are trading precision for recall in the worst possible place — the prompt.

One practical note on model selection here: reranker quality varies enormously between models, and the differences are far more consequential than equivalent differences between embedding models. An embedding model that is ten percent better shifts your candidate set slightly. A reranker that is ten percent better changes which three chunks the language model actually reads. Budget your evaluation time accordingly.


Query Rewriting: The Cheapest Win Available

Most pipelines embed the user’s raw input and retrieve against it directly. This works for well-formed standalone questions and fails for almost everything else people actually type.

Three patterns break naive retrieval, and all three are fixable with a single inexpensive model call before retrieval begins.

Conversational references. In a multi-turn session, the third question is rarely self-contained. “What about for annual plans?” carries no retrievable signal on its own — the subject lives two turns back. Rewriting it against conversation history into “What is the refund window for annual enterprise plans?” transforms an unanswerable embedding into a precise one.

Compressed or telegraphic input. Users type “refund enterprise 30 days?” rather than complete sentences. Expanding this into a well-formed question aligns it with the prose distribution the embedding model was trained on.

Multi-part questions. “How does our uptime compare to what we promised customers?” contains two retrieval targets that live in different documents. Decomposing into sub-queries, retrieving independently, then merging results is the only reliable approach. Single-pass retrieval on compound questions systematically returns evidence for whichever half embeds more strongly and silently drops the other.

REWRITE_PROMPT = """Rewrite the user's question as a standalone search query.
Resolve pronouns using the conversation history. Expand abbreviations.
If the question has multiple distinct parts, output one query per line.

History:
{history}

Question: {question}

Standalone queries:"""


def rewrite(question, history, model):
    raw = model.complete(
        REWRITE_PROMPT.format(history=history, question=question)
    )
    return [line.strip() for line in raw.splitlines() if line.strip()]

A useful refinement is hypothetical document embedding: rather than embedding the question, ask the model to draft a short passage that would answer it, then embed that. Because answers resemble documents more closely than questions do, the resulting vector often lands nearer the correct neighbourhood. The trade-off is an extra generation call in the critical path, so it suits accuracy-sensitive applications more than latency-sensitive ones.


Metadata Is Not Optional

Teams routinely treat metadata as bookkeeping and discover its importance during an incident. Every chunk should carry, at minimum: source document identifier, document type, section path, publication and last-modified timestamps, and the access-control identifiers that govern who may see it.

Three capabilities depend entirely on this.

Filtered retrieval. Users frequently want a scoped answer — this quarter’s figures, the current policy version, documentation for the release they are running. Without metadata filters, the retriever cheerfully returns a superseded 2023 policy alongside the current one, and the model has no way to tell which is authoritative. Filters must be applied inside the index query, not as post-processing, or you pay full retrieval cost to discard most results.

Recency weighting. In corpora where documents supersede one another, pure semantic similarity is actively misleading. An outdated document may be the closest semantic match precisely because it addresses the topic in more detail than its concise replacement. Blending a recency signal into the ranking score corrects this.

Permission enforcement. This is the failure mode with genuine consequences. If access control is applied after retrieval, information leaks through response timing and through the model’s awareness of content it should never have received. Permission predicates belong in the index query itself.

Attribution also depends on metadata. Citations that point to a document title and section are verifiable by the reader; citations that point to “chunk 4,192” are not. Given that the primary defence against quiet hallucination is a reader who can check the source, this is not a cosmetic concern.


A Production Reference Architecture

Putting the pieces together yields a pipeline with five distinct stages, each independently observable and independently replaceable.

Stage one — ingestion. Parse documents with structure preservation. Extract tables and figures down separate paths. Attach metadata: source, document type, section path, last-modified timestamp, and access-control identifiers. Metadata is not optional; it is what makes filtered retrieval and permission enforcement possible later.

Stage two — indexing. Write small child chunks with contextual headers to the vector index. Write the same text to a sparse index. Store parent sections in a document store keyed by chunk identifier. Version your embedding model in the index metadata, because you will eventually need to re-embed and you will need to know what is stale.

Stage three — query processing. Rewrite the incoming query before retrieving. Resolve pronouns against conversation history, expand acronyms, and for multi-part questions, decompose into sub-queries and retrieve for each. A single cheap model call here frequently outperforms any amount of retrieval tuning downstream.

Stage four — retrieval and reranking. Run dense and sparse retrieval in parallel with metadata filters applied at the index level. Fuse, then rerank with a cross-encoder. Apply a relevance threshold and — critically — allow the result set to be empty.

Stage five — generation. Assemble the surviving parent sections with explicit source labels. Instruct the model to answer only from the provided context and to state plainly when the context is insufficient. Return citations alongside the answer.

That fourth-stage detail deserves emphasis. A pipeline that always returns its best three chunks regardless of score will confidently answer questions your corpus cannot answer. Permitting an honest “I don’t have information on that” is a feature, and it is the single cheapest defence against hallucinated answers.


Evaluation Without a Ground-Truth Dataset

Every team asks how to measure RAG quality, and most stall because they assume they need a labelled dataset they do not have. You can bootstrap one.

Generate synthetic questions from your own corpus. Sample chunks, prompt a capable model to write the question each chunk answers, and keep the pairs. You now have a retrieval test set where ground truth is known by construction: the correct chunk is the one the question was generated from. Measure recall@k and mean reciprocal rank against it. This catches chunking and retrieval regressions immediately.

Separate retrieval metrics from generation metrics. They fail independently and demand different fixes.

Failure symptom Likely stage First thing to change
Correct chunk never retrieved Retrieval Chunking strategy, hybrid search
Correct chunk retrieved but ranked low Ranking Add or upgrade the reranker
Right context, wrong answer Generation Prompt structure, model choice
Answer contains unsupported claims Generation Grounding instructions, citations
Confident answer to unanswerable question Retrieval threshold Enforce a minimum relevance score

Log everything in production. Store the query, the rewritten query, retrieved chunk identifiers with scores, the final context, and the answer. When a user reports a bad response you can determine within seconds whether retrieval or generation failed. Without this, every debugging session is guesswork.


Latency and Cost Benchmarks

Approximate figures for a corpus of roughly one million chunks, useful for capacity planning rather than as precise predictions:

Configuration p50 latency p95 latency Relative cost Answer quality
Dense only, top-5 1.4 s 2.6 s 1.0× Baseline
Dense only, top-20 1.9 s 3.8 s 2.7× Often worse
Hybrid, top-20 fused to 5 1.6 s 3.0 s 1.2× Clearly better
Hybrid + rerank to 4 1.9 s 3.4 s 1.4× Best
Hybrid + rerank + query rewrite 2.4 s 4.2 s 1.7× Best on hard queries

Two observations matter here. First, naively increasing top_k is the worst trade in the table: it raises cost and latency while frequently reducing quality through context dilution. Second, the full hybrid-plus-rerank pipeline costs roughly forty percent more than the naive baseline while substantially improving accuracy — which is an excellent trade in almost any application where wrong answers carry real cost.


Common Pitfalls

Re-embedding without re-indexing everything. Embeddings from different models occupy incompatible vector spaces. Mixing them silently corrupts similarity scores. Version the model in metadata and rebuild the whole index on change.

Ignoring access control at retrieval time. Filtering results after retrieval leaks information through timing and through the model’s awareness of documents it should never have seen. Apply permission filters inside the index query.

Treating the vector database as a source of truth. It is a derived index. Keep canonical documents elsewhere so you can rebuild from scratch.

Skipping deduplication. Near-identical chunks from versioned documents will occupy your entire result set with the same information stated five ways.

No feedback loop. Thumbs up and down on answers, joined to the logged retrieval traces, is the highest-value dataset your system can produce. Collect it from day one.


What Comes Next

Three developments are actively reshaping this space. Late-interaction retrieval models, which store per-token rather than per-document embeddings, deliver much of a cross-encoder’s precision at closer to vector-search latency, at the cost of significantly larger indexes. Agentic retrieval, where the model itself decides whether to search, what to search for, and whether to search again, handles multi-hop questions that single-pass pipelines cannot. And expanding context windows continue to compress the low end of the market — for corpora small enough to fit entirely in context, retrieval may soon be unnecessary.

None of these eliminate the fundamentals. Structure-aware chunking, hybrid retrieval, and honest evaluation remain the difference between a system that works and one that demos well.


Conclusion

RAG failures are retrieval failures wearing a generation costume. The instinct to fix a bad answer by changing the model is almost always misdirected effort, because the model was faithfully working with the context it was handed.

Invest in the order that matches impact: chunking first, because it caps everything else; hybrid retrieval second, because dense and sparse fail in complementary ways; reranking third, because retrieval and ranking are genuinely different problems; and evaluation throughout, because a pipeline you cannot measure is a pipeline you cannot improve. The synthetic question-generation trick removes the last excuse for flying blind.


Frequently Asked Questions

Do I still need RAG now that context windows hold hundreds of thousands of tokens? For corpora that fit entirely in context, often no. For anything larger, or where cost and latency matter, or where you need citations pointing to specific sources, retrieval remains necessary. Long contexts also exhibit measurable recall degradation for information buried in the middle.

Which chunk size should I use? Chunk on semantic boundaries rather than character counts. If you must pick a number, 300 to 500 tokens for retrieval chunks with parent sections of 1,500 to 2,000 tokens is a reasonable default. Then test it against a synthetic evaluation set rather than trusting the default.

Is a reranker worth the added latency? In most cases yes. It typically adds 100 to 300 milliseconds and produces the largest single-stage quality improvement available after chunking. If latency is critical, rerank a shorter shortlist rather than removing the stage.

How often should I re-embed the corpus? Only when documents change or when you deliberately change embedding models. Embeddings do not decay. Incremental updates on document change are sufficient; full rebuilds are for model migrations.

Can I skip the sparse index and rely on vectors alone? Only if your queries never contain exact identifiers — no SKUs, error codes, version strings, or unusual proper nouns. Most real query logs contain plenty of these, which is exactly where dense retrieval underperforms.

What causes a system to hallucinate despite correct retrieval? Usually prompt construction. If the instructions do not clearly bound the model to the provided context, or do not sanction refusal, the model fills gaps from parametric memory. Explicitly permitting “the context does not contain this information” resolves a large share of these cases.

How do I handle questions requiring information from several documents? Decompose the query into sub-questions, retrieve for each independently, then merge the deduplicated results. Single-pass retrieval systematically underperforms on multi-hop questions because no single embedding sits near all the required evidence.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button