Academy

How RAG actually works

Your RAG demo answered beautifully in the meeting. In production it confidently cited a document from three quarters ago. Here is what is really happening under the hood.

The Felesh team Updated 9 min

The demo was flawless. You fed it your product documentation, asked a question a real user would ask, and the answer came back precise and fluent. Then you shipped it. A week later a support ticket arrived: the model had confidently cited a pricing tier you discontinued three months ago. The retrieval had pulled the most plausibly similar document — not the most correct one.

That gap, between plausible and correct, is where RAG lives. Understanding it properly requires looking at what RAG actually does, step by step, and why naive implementations break in predictable ways.

The most similar page is not the current oneA user asks how much the Pro plan costs per month. Two pricing pages are scored: the March page, which says Pro is $49 a month, scores 0.91; the April page, which says $59, scores 0.87. The March page ranks first, so the bot answers $49. The March page was retired three months ago; the April page is the correct one, ranked second.a user asks“How much is the Pro plan per month?”pricing · MarchPro: $49 / monthpricing · AprilPro: $59 / monthsimilarity0.91similarity0.87bot: “Pro is $49 a month.” · source: Marchretired 3 months agothe correct page, ranked 2nd
Retrieval ranks by similarity, not by date. The retired March page scores 0.91 against the current page's 0.87, so the bot quotes the old price.

What you are choosing between

Before the pipeline, the alternatives. Retrieval-augmented generation (Lewis et al., 2020) is not the only way to give a model access to a corpus.

Long-context stuffing drops everything — or a large slice of everything — directly into the model’s context. It is the simplest architecture: no retrieval infrastructure, no chunking decisions, no index to maintain. It works well when the corpus is small and stable. It fails at scale: token cost rises linearly with corpus size, and attention dilutes as context grows longer. A model asked to synthesise a 200-page document whose answer lives on page 7 can miss it.

Keyword search / BM25 is the classic information-retrieval approach: match tokens, rank by term frequency and inverse document frequency. It is fast, interpretable, and surprisingly hard to beat on exact-term lookups. If your users search for invoice number INV-20240312-007, BM25 will find it. A dense embedding will blur that string into a region of meaning-space where it is surrounded by other invoice documents — close, but perhaps not the right one.

GraphRAG (a design popularised by Microsoft Research) stores the corpus as a knowledge graph and traverses edges at query time instead of doing vector lookup. It handles multi-hop questions well: “who approved the contract signed by the person who reported to the VP of sales in Q3?” can be answered by following edges. It adds significant infrastructure and graph maintenance overhead.

Naive vector (single-vector) RAG is the simplest dense approach: chunk the corpus, embed the chunks, retrieve the top-k by cosine similarity, drop them in context. This is what most tutorials show. It is where most production failures begin.

The real pipeline

The production-grade approach — the one that addresses the most common failure modes — looks like this.

Chunk. Split your corpus into pieces the model can use. Chunk size is a real decision: too small and you lose context; too large and you retrieve a lot of noise around the answer. Overlapping windows help. Metadata tagging at chunk time (document date, section, source) pays dividends later.

Embed. Run each chunk through an embedding model to produce a dense vector. The embedding model is not a footnote: different models have different strengths across domains. A general embedding model trained on web text may underperform on legal or clinical corpora.

From a page to a tagged vectorThe pricing page has six lines. Chunk c1 covers the first four; chunk c2 covers lines three to six, so the two share the lines about yearly billing and seats. Chunk c2 is tagged with its date 2026-04-02, its section Pro plan and its source pricing.md, and is then embedded as a vector of 768 numbers.pricing.md## Pro planPro is $59 a month.Yearly saves twomonths. Seats cost$12 each, prorated.## Team planc1c2chunk c2date: 2026-04-02section: Pro plansource: pricing.md[0.12, -0.48, … ×768]
Chunk windows overlap, so no sentence is cut in half, and each chunk carries its date, section and source before it becomes a vector.

Retrieve — dense + BM25 in parallel. At query time, embed the query and retrieve top-k by cosine similarity (dense retrieval). Simultaneously, run BM25 over the same corpus. Dense retrieval finds semantically related chunks even when the user’s words differ from the document’s words. BM25 finds exact tokens — model names, IDs, proper nouns — that dense retrieval blurs.

Rerank. Merge the two candidate lists and pass them to a cross-encoder reranker. A cross-encoder sees the query and a candidate together and scores their relevance jointly — it is slower than embedding similarity but significantly more accurate. The reranker does the triage that neither retrieval pass can do alone.

The reranker reorders five candidatesThe question is: refund status for INV-20240312-007. Dense retrieval brought three chunks: refunds paid within 14 days, refund policy for annual plans, and invoices are emailed monthly. BM25 brought two: INV-20240312-007 refunded 3 Apr, and INV-20240312-006 paid in full. The cross-encoder scores each with the question: 0.88, 0.47, 0.21, 0.96 and 0.12. The BM25 invoice line ranks first, the two refund chunks second and third, and those three go into the context.“Refund status for INV-20240312-007?”from densescore with the questionfrom BM25Refunds paid within 14 daysRefund policy for annual plansInvoices are emailed monthlyINV-20240312-007: refunded 3 AprINV-20240312-006: paid in full0.88 · #20.47 · #30.21 · #40.96 · #10.12 · #5#1 to #3 go into the context; #4 and #5 are dropped
The cross-encoder scores each candidate together with the question. The invoice line only BM25 found ranks first, and the dense near-miss about emailing falls out.

Ground. Drop the top reranked chunks into the model’s context with an instruction to answer from what is provided and to say when it cannot. Grounding instructions matter: without them, the model will mix retrieved content with its parametric knowledge, and the seam is invisible.

A grounded answer cites or declinesThe model’s context holds an instruction to answer only from chunks 1 to 3 and to say so when the answer is not there, and three chunks: INV-20240312-007 refunded 3 Apr, refunds paid in 14 days, annual plans have no refund. Asked where the refund is, the model answers refunded on 3 Apr, citing chunk 1, and lands within 14 days, citing chunk 2. Asked about an NGO discount, its memory offers yes, 20% off; that guess is struck out and the answer is: not in the documents.Answer only from [1]–[3].If it is not there, say so.[1] INV-…007 refunded 3 Apr[2] Refunds paid in 14 days[3] Annual plans: no refundthe model’s contextWhere is my refund?Refunded on 3 Apr[1]lands within 14 days[2]Any NGO discount?from memory: “Yes, 20% off”Not in the documents.
Grounded: each answer line cites the chunk it came from, and the question no chunk answers gets “Not in the documents” instead of the model's guess from memory.

This is the architecture production deployments have converged on — a step beyond the dense-only retrieval of the original RAG paper (Lewis et al., 2020). Hybrid retrieval plus reranking addresses the failure modes that pure vector RAG cannot.

Knowledge is not memory

One distinction worth stating plainly, because conflating them causes real design errors.

Knowledge is facts in a corpus — documents, records, policies, code. RAG retrieves knowledge: at query time it fetches relevant facts from a store and passes them to the model as context. The store does not grow because of the conversation; it grows because someone updates the corpus.

Memory is the running state of a relationship — what was said in prior turns, what a user prefers, what happened in a past session. Memory is tracked across time; knowledge is indexed across documents.

They need different stores. A knowledge base is a retrieval index (vector store, search index, graph). Memory is a session log, a preference record, a summary of past interactions. Routing a memory question to the knowledge retrieval pipeline — or vice versa — is a category error. For a deeper look at the memory side, see how agents remember.

Where even good RAG breaks

Hybrid retrieval with reranking solves the most common failures. It does not solve all of them.

Multi-hop questions. “What did the Q2 report say about the supplier mentioned in the renewal clause of the contract the CFO signed last year?” requires chaining: find the contract → find the clause → find the supplier → find the Q2 report → retrieve the relevant passage. Single-pass retrieval, dense or BM25, retrieves one set of chunks. It cannot follow a chain of reasoning across documents. GraphRAG was designed for exactly this; it is worth the infrastructure cost when multi-hop is the dominant query pattern.

Exact-term and ID lookups. Dense embeddings are trained to represent meaning. A product SKU or invoice ID is a token sequence with no semantic neighbourhood worth exploiting. BM25 is better here — but even BM25 can fail if the token is rare or if the index was built with aggressive normalisation. For high-precision ID lookups, a dedicated structured lookup (a database query, not a vector search) is more reliable.

Freshness. A retrieval index is only as current as its last update. If your corpus is updated at midnight and a user asks at 11 pm about an event that happened at 6 pm, the index does not have it. This is not a retrieval failure; it is an architecture failure. Streaming ingestion, incremental indexing, and cache invalidation are operational concerns that RAG does not solve by itself.

A question the index is too old to answerA timeline from yesterday to the next midnight. The index was last built at 00:00, so it holds everything before then. At 18:00 the Pro price changes to $59. At 23:00 a user asks what Pro costs; the retriever searches the midnight index and answers $49, from a stale index. At the next 00:00 the index is rebuilt and $59 is found.index rebuilt nightly at 00:00yesterday00:0000:00in the index18:00 Pro price → $5923:00 “What does Pro cost?”23:00 answer: “$49”, from a stale index00:00 rebuild: $59 is found
The 23:00 question searches an index built at midnight, so the 18:00 price change does not exist for it until the next rebuild.

Attention dilution at top-k. Passing the top 20 retrieved chunks into context does not guarantee the model uses all of them equally. Research community findings (reported, not Felesh results) show models attend more strongly to content at the beginning and end of context than to the middle — sometimes called the “lost in the middle” effect. A small, high-quality top-k is usually better than a large noisy one.

The choice, and where it loses

The principled default for a domain knowledge retrieval system is hybrid dense + BM25 with a rerank stage. It handles the widest range of query types, is robust to vocabulary mismatch, and does not require graph infrastructure or a very large context window.

Where it loses: it is not simple. You are running two retrieval passes instead of one, maintaining both an embedding index and a BM25 index, and adding a reranker in the serving path. Latency increases. Operational surface grows. If your corpus is small, stable, and fits in context, long-context stuffing with a single call is faster to build and cheaper to run. If your users ask mostly exact-term questions against a well-structured corpus, a search index plus BM25 alone may be enough.

The architecture that answered beautifully in the demo was probably naive vector RAG. The one that holds in production is usually not.


For the adaptation decision — when RAG is the right tool versus fine-tuning or prompt engineering — see fine-tune, RAG, or prompt. For memory architecture — the other store, the one RAG does not touch — see how agents remember.