RAG Basics

Retrieval-augmented generation: find the passages that match a question, put them in the prompt, and get answers grounded in your own documents.

15 minLesson 4 of 8
On this page
  1. Retrieve, augment, generate
  2. Retrieval by hand
  3. The code
  4. When RAG is the right tool
  5. Common mistakes
  6. Try it yourself
  7. Check yourself
  8. What to practise next

Two students sit the same exam. One studied the textbook a year ago and answers from memory. The other is allowed to bring three pages of notes, but only three, and must pick them right before the exam starts. On questions about the topics in those three pages, the second student wins easily, and can point to exactly where each answer came from.

A model answering from training is the first student. A shop's help pages, a company handbook or last week's meeting notes were never in that memory. You could paste them all into every request, but a real document collection is far bigger than any context window. The fix is to be the second student: look up the few passages that matter, then answer from them.

Retrieve, augment, generate

That is all RAG (retrieval-augmented generation) means:

  1. Retrieve. Search your documents for the passages most related to the question.
  2. Augment. Put those passages into the prompt, with instructions to answer from them.
  3. Generate. The model writes the answer, ideally quoting or citing the passages.

The model is unchanged. RAG is a context engineering technique: an automatic way to select the right material for each question.

The search usually works on meaning rather than exact words, using embeddings. That matters because people rarely use the same words as your documents. Someone asks "when do I get my money back?" and the answer sits under the heading "Refund processing times".

Indexing (once) documents split in chunks embed each vector store Every question question embed compare,take top k prompt withpassages answer stored vectors
Two phases. Indexing runs once (and again when documents change). The query path runs on every question.

Indexing is the slow part, so it happens ahead of time. Each document is cut into passages called chunks (how to cut them well is the whole of the next lesson), each chunk is turned into a vector, and the vectors are saved. At question time you only embed the question and compare.

Retrieval by hand

A bookshop's help centre has five chunks. To trace it by hand, pretend embeddings have just three numbers, meaning roughly "about money", "about shipping" and "about accounts". Real embeddings have hundreds or thousands of dimensions, but the arithmetic is the same.

The question "When will I get my money back?" embeds to [5, 1, 0]: mostly money, a little shipping. Its length is √26 ≈ 5.10. Score each chunk with cosine similarity, from the Embeddings lesson, and keep the top 2.

Top 2 chunks for "When will I get my money back?"Use the arrows or ← → keys
q = embed(question)for each chunk c:  score[c] = cosine(q, vector[c])sort chunks by score, highest firstreturn the first k
  1. chunk
    0refundc
    1returns
    2delivery
    3password
    4gift card
    score
    01
    1
    2
    3
    4
    q
    [5, 1, 0]
    dot
    21

    1Refund times, [4, 1, 0]: dot = 20 + 1 + 0 = 21, length √17 ≈ 4.12. Cosine = 21 / (5.10 × 4.12) ≈ 1.00. Nearly the same direction, with no shared words.

  2. chunk
    0refund
    1returnsc
    2delivery
    3password
    4gift card
    score
    01
    10.92
    2
    3
    4
    q
    [5, 1, 0]
    dot
    17

    2Returns rules, [3, 2, 0]: dot = 15 + 2 = 17, length √13 ≈ 3.61. Cosine ≈ 0.92. About money and about sending things back.

  3. chunk
    0refund
    1returns
    2deliveryc
    3password
    4gift card
    score
    01
    10.92
    20.19
    3
    4
    q
    [5, 1, 0]
    dot
    5

    3Delivery times, [0, 5, 1]: dot = 0 + 5 + 0 = 5, length √26 ≈ 5.10. Cosine ≈ 0.19. It is mostly about shipping, so it points a different way from a money question, even though both ask how long something takes.

  4. chunk
    0refund
    1returns
    2delivery
    3passwordc
    4gift card
    score
    01
    10.92
    20.19
    30
    4
    q
    [5, 1, 0]
    dot
    0

    4Password reset, [0, 0, 5]: dot = 0. Cosine = 0. Completely unrelated.

  5. chunk
    0refund
    1returns
    2delivery
    3password
    4gift cardc
    score
    01
    10.92
    20.19
    30
    40.88
    q
    [5, 1, 0]
    dot
    10

    5Gift cards are non-refundable, [2, 0, 1]: dot = 10, length √5 ≈ 2.24. Cosine ≈ 0.88. Close behind, because it is also about money.

  6. chunk
    0refund
    1returns
    2gift card
    3delivery
    4password
    score
    01
    10.92
    20.88
    30.19
    40
    k
    2
    comparisons
    5

    6Sort and keep k = 2: refund times and returns rules go into the prompt. Five comparisons, one per chunk. The gift-card chunk came a close third, a reminder that k is a judgement call.

1 / 6

The code

Here is the retrieval step and the prompt it builds. The embed function is whatever embedding model you use; the vectors below are the toy ones from the trace.

const dot = (a, b) => a.reduce((sum, x, i) => sum + x * b[i], 0);
const cosine = (a, b) => dot(a, b) / Math.sqrt(dot(a, a) * dot(b, b));
 
function topK(queryVec, chunks, k) {
  return chunks
    .map((c) => ({ ...c, score: cosine(queryVec, c.vec) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, k);
}
 
function buildPrompt(question, passages) {
  const sources = passages.map((p, i) => `[${i + 1}] ${p.text}`).join("\n");
  return `Answer using only the sources below. Cite them like [1].
If they do not contain the answer, say "I don't know."

${sources}

Question: ${question}`;
}
 
const chunks = [
  { text: "Refunds reach your card 5 to 7 days after we receive the return.", vec: [4, 1, 0] },
  { text: "You can return items within 30 days of delivery.", vec: [3, 2, 0] },
  { text: "Standard delivery takes 2 to 4 working days.", vec: [0, 5, 1] },
  { text: "Reset your password from the sign-in page.", vec: [0, 0, 5] },
  { text: "Gift cards cannot be refunded.", vec: [2, 0, 1] },
];
const best = topK([5, 1, 0], chunks, 2);
console.log(buildPrompt("When will I get my money back?", best));
import math
 
def dot(a, b):
    return sum(x * y for x, y in zip(a, b))
 
def cosine(a, b):
    return dot(a, b) / math.sqrt(dot(a, a) * dot(b, b))
 
def top_k(query_vec, chunks, k):
    scored = [{**c, "score": cosine(query_vec, c["vec"])} for c in chunks]
    scored.sort(key=lambda c: c["score"], reverse=True)
    return scored[:k]
 
def build_prompt(question, passages):
    sources = "\n".join(f"[{i + 1}] {p['text']}" for i, p in enumerate(passages))
    return (
        "Answer using only the sources below. Cite them like [1].\n"
        'If they do not contain the answer, say "I don\'t know."\n\n'
        f"{sources}\n\nQuestion: {question}"
    )
 
chunks = [
    {"text": "Refunds reach your card 5 to 7 days after we receive the return.", "vec": [4, 1, 0]},
    {"text": "You can return items within 30 days of delivery.", "vec": [3, 2, 0]},
    {"text": "Standard delivery takes 2 to 4 working days.", "vec": [0, 5, 1]},
    {"text": "Reset your password from the sign-in page.", "vec": [0, 0, 5]},
    {"text": "Gift cards cannot be refunded.", "vec": [2, 0, 1]},
]
best = top_k([5, 1, 0], chunks, 2)
print(build_prompt("When will I get my money back?", best))

The printed prompt, sent to a model, looks like this:

Augmented promptIllustrative example. Real replies vary.
You
Answer using only the sources below. Cite them like [1]. If they do not contain the answer, say "I don't know." [1] Refunds reach your card 5 to 7 days after we receive the return. [2] You can return items within 30 days of delivery. Question: When will I get my money back?
Model
Your refund reaches your card 5 to 7 days after the shop receives your return [1]. You need to send the item back within 30 days of delivery [2].

The instruction "say I don't know" is not decoration. Without it, when retrieval finds nothing useful, the model falls back on its training and produces a confident, generic answer that looks just as grounded as a real one.

ApproachTimeSpaceWhy
Compare with every chunkO(n·d + n log n)O(n)n chunks with d numbers each, then a sort. Fine for thousands of chunks.
Approximate indexmuch less than O(n·d)O(n·d)Vector databases use indexes that check only promising regions, trading a little accuracy for big speed-ups at millions of chunks.

When RAG is the right tool

  • Use it when the material is too big to send whole, changes often, or is private. Updating the documents updates the answers, with no retraining.
  • Skip it when the material is small. If the whole handbook fits comfortably, sending all of it avoids retrieval mistakes entirely.
  • Do not use fine-tuning instead for facts that change. Fine-tuning is good for teaching a style or format; it is a poor way to store facts you will need to correct next month.

Common mistakes

Try it yourself

Try it: Retrieval or generation

A staff assistant was asked "How many days of parental leave do we get?" It answered "Most companies offer 12 weeks." The two retrieved passages were: [1] "Annual leave: 25 days per year, plus public holidays." [2] "Sick leave must be reported to your manager before 10am." Where did the system fail, and what would you change? There are two separate problems.

Show a sample answer

Problem 1: retrieval. Neither passage is about parental leave. Either the parental leave policy is not in the index, or it was chunked or embedded badly so a generic "leave" match beat it. Check whether the policy exists in the store, then search for it directly to see where it ranks.

Problem 2: generation. Given passages that did not answer the question, the model should have said so. Instead it answered from general knowledge. Add (or strengthen) an instruction such as: "If the sources do not answer the question, reply: I couldn't find this in the staff handbook." Asking for citations also helps, because an answer with no source to cite stands out.

Fixing only the prompt would make it honest but still unhelpful. Fixing only retrieval would leave it free to invent answers next time something is missing. Fix both.

Check yourself

Quick check

Why does RAG usually search with embeddings rather than exact keywords?

Your company's policies change every month. Which approach keeps a staff assistant's answers current most easily?

In the trace, the gift-card chunk scored 0.88 but was left out. What would happen with k = 3?

What to practise next

Retrieval quality starts with how documents are cut up. Chunking and Retrieval covers chunk size, overlap, and the tricks that fix the most common misses.