hello·ai

Retrieval & RAG

AssumesEmbeddingsVector space intuition

In one line

Fetch the relevant text at request time and put it in the prompt, because almost everything worth answering about is either private, recent, or needs a citation.

Why it exists

A model knows what was in its training corpus, frozen at a date, with no notion of your private data and no ability to cite anything. That covers almost nothing a business actually asks about: current inventory, this customer's history, the policy that changed last Tuesday, the internal runbook.

is the fix, and it is not a clever trick — it is the obvious one. Search for the relevant passages, put them in the , ask the model to answer from them. is that pattern, and its main virtue is that updating a fact is a database write rather than a training run.

questionembedbi-encodersearchtop 50reranktop 5LLMindexFour of the five boxes are search. When a RAG system answers badly,that is nearly always where it went wrong.The model cannot use a passage retrieval never returned.
Four of the five stages are search. When a RAG system answers badly, that is nearly always where it went wrong.

Chunking is the decision that matters most#

Chunk boundaries determine what can be retrieved, and they are chosen before any query exists. Get them wrong and no downstream component recovers.

Too small and chunks lose their context. A paragraph reading "This does not apply to enterprise accounts" is meaningless without knowing what "this" was, and it will be retrieved for queries it actively misleads.

Too large and precision collapses. A 4,000-token chunk retrieved for one relevant sentence drags 3,900 tokens of noise into the prompt, crowding out other results and diluting attention.

The starting point that works for most prose is 400 to 800 tokens with of 10 to 15 per cent, so an idea split across a boundary still appears whole somewhere. Better than any fixed number, though, is splitting on structure: a heading, a section, a function, a table. Documents already contain the author's own boundaries, and those are usually better than anything a character counter will find.

Two refinements repay their cost quickly. Contextual prefixing prepends a line of document and section context to each chunk before embedding, so a chunk carries where it came from — this is one of the largest single quality gains available and costs a few extra tokens per chunk. And metadata on every chunk — source, date, section, permissions — lets you filter before searching, which is both a relevance win and, for permissions, a correctness requirement.

Retrieval quality beats model quality#

The reflex when RAG underperforms is to reach for a bigger generator. The ordered list of things that actually help looks nothing like that.

Add keyword search. runs alongside the vector query and merges the rankings. Embeddings are poor at exact identifiers — error codes, SKUs, surnames, version numbers — and keyword search is perfect at them. One extra query, and it removes most of the embarrassing misses.

Add a reranker. A scoring the top fifty candidates sees query and document together, which no pair of independently computed s can. It typically improves what reaches the prompt more than any embedding upgrade, and it only runs on a shortlist so the cost is bounded.

Rewrite the query. "What about the other one?" is unanswerable standalone. Rewriting it against conversation history into "What is the refund window for annual plans?" fixes a large class of multi-turn failures for one cheap model call.

Then, maybe, a better embedding model. It is the expensive option — a full corpus re-embed and index rebuild — and usually the smallest gain of the four.

Grounding, and what it does not fix#

Putting sources in the prompt reduces substantially. It does not eliminate it, and the residual failure modes are specific enough to design against.

The model can contradict the passages, blending retrieved text with training knowledge. It can answer from parameters when retrieval returned nothing useful, which is the most dangerous case because the answer looks identical. And it can cite a passage that does not support the claim it is attached to.

Three mitigations, in order of cost. Instruct explicitly that the answer must come from the provided context and that "I don't know" is an acceptable response — cheap, and it works more often than it sounds like it should. Require inline citations by chunk id, then verify programmatically that every cited id was actually retrieved; a fabricated id is a bug you can catch in code. And for high-stakes paths, run a second cheap call that checks whether each claim is supported by the cited passage.

Note that this is where RAG and divide cleanly. Retrieval carries facts, because facts change. Fine-tuning carries form, because form does not.

Worked example

An internal documentation assistant over 2,000 pages. First version: 1,000-token fixed chunks, vector search, top 3, straight into the prompt. Accuracy on 100 real questions: 61%.

Failures, categorised by hand — which is the step people skip:

retrieval missed the answer entirely      27 cases
right document, wrong chunk               8 cases
model ignored the context                 4 cases

Thirty-five of thirty-nine failures were search. Changes, measured one at a time:

baseline                                            61%
+ split on headings instead of fixed size           68%   (+7)
+ prepend doc title and section to each chunk       74%   (+6)
+ hybrid search with BM25                           79%   (+5)
+ cross-encoder rerank, top 50 -> top 5             86%   (+7)
+ query rewriting against chat history              89%   (+3)

61% to 89%, and the generator never changed. The two interventions that look least like AI work — splitting on headings and adding keyword search — accounted for twelve points between them.

Gotchas

  • Debugging the prompt when retrieval is the problem. Log what was retrieved for every request. If the answer was not in the context, the prompt is irrelevant, and you will otherwise spend days rewording instructions that cannot possibly help.

  • Skipping keyword search because vectors feel more modern. Any query containing an exact token — an error code, an account id, a person's name — is a case where BM25 wins outright and embeddings have nothing to contribute. Hybrid is the default, not an optimisation.

  • Retrieving without filtering on permissions. A has no concept of who is asking. Every chunk needs an access-control field applied as a pre-filter, and this is a data leak rather than a relevance bug if you get it wrong.

  • Stuffing the context window because it is large. More passages mean more noise, more cost and worse attention to the ones that mattered. Five good chunks beat twenty mediocre ones, reliably, and the difference grows with context length.

  • Having no retrieval . Generation quality and retrieval quality are separate numbers and need separate measurement. Track recall at k against a labelled set, and you will usually find the ceiling on your system sitting there.

Mental model

RAG is a cache-aside read path with a very unusual cache. You check the store, assemble what you found, and hand it to something that renders an answer. All the familiar cache-aside questions apply — what is the key, how stale can it be, what happens on a miss — and one new one: your lookup is fuzzy, so "miss" is not a binary. It is a recall percentage, and it is the number your whole system's quality sits on top of.

In practice

Problem statements that lean on this topic. Rated by the phase that makes them land.

Done reading?

Nothing marks itself complete. Say so only when you could explain this to someone else.

Next: Vector indexes

This topic has 4 subtopics.