Retrieval-augmented generation
Answer from your own documents instead of the model's memory.
Create a free account Try part of it here
Why it matters
A model knows the public internet up to its training date. It doesn't know your return policy. It doesn't know this season's prices, or what your brand guide says about exclamation marks. Retrieval-augmented generation (RAG) fixes that without retraining anything. Your code looks up the passages that matter and pastes them into the prompt. Then the model answers from them. It's the most common pattern in business AI, from support bots to "chat with your docs" tools.
What you'll be able to do
- Explain the three steps of RAG: index, retrieve, generate
- Write a grounded prompt that puts retrieved documents before the question
- Tell the model to answer only from the documents and to say when they don't cover the question
- Build a working RAG function that returns an answer and its sources
What it covers
- An open-book exam
- Step 2: retrieve
- Step 3: the grounded prompt
- Worked example: a complete RAG function
- When RAG is the right tool
An open-book exam
Think of two kinds of exam. In a closed-book exam, you answer from memory. If you never learned a fact, you guess. In an open-book exam, you look up the right page first and answer from it.
A model on its own sits a closed-book exam. RAG turns it into an open-book one. Before the model answers, your code finds the right pages and puts them on the desk. The model reads them and answers from what's in front of it.
The name says the same thing. Retrieval means fetching the pages. Augmented means added to. Generation is the model writing its reply. So RAG is "writing a reply with fetched pages added to the prompt."
Three steps
RAG joins the pieces from the last three lessons into one pipeline, which means a chain of steps where each feeds the next:
1. Index (once, ahead of time) chunk documents → embed each chunk → store vectors 2. Retrieve (per question) embed the question → find the top-k closest chunks 3. Generate (per question) prompt = instructions + retrieved chunks + question → model
Here's each step in plain words:
- Index. You prepare the library once. You split long documents into chunks, short pieces of a few paragraphs each. You turn each chunk into an embedding, a list of numbers that captures its meaning. Then you store those lists, usually in a vector database, which is a store built for searching such lists.
- Retrieve. A question comes in. You turn it into an embedding too. Then you find the chunks whose embeddings sit closest to it. You keep the best few, called the top-k (k is how many).
- Generate. You build a prompt from your instructions, the chunks you found and the question. You send it to the model, and it writes the answer.
The model never searches anything itself. Your code does the retrieval and hands the model a short reading list. That's why RAG works with any model. It's also why its answers can only be as good as what you retrieve.
Show the answer
Index
You chunk, embed and store the documents once. Retrieve and generate happen again for each new question.
Step 2: retrieve
The practice API has already done step 1 for you. Its help center is indexed. The endpoint GET /v1/docs?q=... runs the embedding search: it embeds the query and returns the closest documents. That's the same thing your search function did in the similarity search lesson.
import requests API = "https://api.gradient.test/v1" question = "Can I return a gift card?" hits = requests.get(f"{API}/docs", params={"q": question, "limit": 2}).json()["data"] for hit in hits: print(hit["score"], hit["id"], hit["title"])
Each hit has three parts. The score says how close the document is to the question, and higher means closer. The id is the document's short name, and the title is its heading. The hits come back best first, and "limit": 2 keeps the top two.
Go deeper What the search does behind the scenes
The help-center articles are short, so each one is kept whole as a single chunk. The API turns each article's title and text into an embedding. In a real system, that's the index step, done once ahead of time.
When you search, the API embeds your question the same way. Then it compares the question's embedding with every article's embedding. The score is the cosine similarity from the similarity search lesson. For these embeddings it runs from 0, unrelated, to 1, pointing the same way.
The API sorts the articles by score and returns the top limit of them. A real system with millions of chunks would use a vector database to do this quickly.
Search results carry ids and scores, but not the full text. Fetch the full text of each hit before building the prompt:
import requests API = "https://api.gradient.test/v1" doc = requests.get(f"{API}/docs/doc_giftcards").json() print(doc["title"]) print(doc["text"][:120], "...")
The [:120] keeps the printout short by showing the first 120 characters. Your prompt will use the whole text.
"limit": 2 to "limit": 3 and run it again. What changes in the printout?Show the answer
It prints three lines: the same two hits, then the third-closest document
limit is k, the number of hits to keep. The order is still best first, so the top two stay the same and one more line follows.
Step 3: the grounded prompt
A grounded prompt is one that ties the model to the documents you hand it. How you present those documents decides whether the model uses them. Four habits work well:
- Wrap each document in tags with its id and title. Then the model can tell where one ends and cite it:
<document id="doc_returns" title="Returns and refunds">...</document>. - Put the documents first and the question last. Claude answers long-context questions better when the material comes before the question.
- Tell it to answer only from the documents. If they don't contain the answer, it should say so plainly. Without that rule, a model fills gaps from its general knowledge. That's how a support bot invents a 90-day return window.
- Ask for citations by document id. A citation points at the source of a claim, so you (and your customers) can check the answer.
system: You answer customer questions for an outdoor clothing shop. Use only the documents
provided. If they don't contain the answer, say you don't know and suggest contacting
support. Cite the ids of the documents you used in square brackets, like [doc_returns].
user: <documents>
<document id="doc_giftcards" title="Gift cards">Digital gift cards are ...</document>
<document id="doc_returns" title="Returns and refunds">You can return ...</document>
</documents>
Question: Can I return a gift card?
The rules go in the system prompt because they apply to every question. The documents and the question change each time, so they go in the user message.
Watch out: retrieved text is data, not instructions. If a document says "ignore your rules," the model might follow it. You'll learn how to limit that in the prompt injection lesson.
Try it below. The two documents are shortened copies of the articles this question retrieves.
passages = ["Refunds take 5 days.", "Shipping is free."] context = "\n\n".join(passages) prompt = f"Answer only from this:\n{context}" print("Refunds" in prompt)
Show the answer
True
join and the f-string put both passages into prompt, so the word Refunds is in it, and in gives True. That's RAG: the retrieved passages go right into the prompt, and the model answers from them, not from memory.
A grounded request. Send it. Then try a question the documents don't cover, like "Who founded the company?" See whether the reply admits it doesn't know.
Show the answer
Tell the model to use only the provided documents and to say when they don't contain the answer
Without a clear rule to stay inside the documents, models fill gaps from their general knowledge.
Worked example: a complete RAG function
This puts the steps together: retrieve, fetch, format, ask. The reply comes from the academy's server. It's a real model if a key is set, and otherwise the labeled simulator.
import requests API = "https://api.gradient.test/v1" SYSTEM = ("You answer customer questions for an outdoor clothing shop. Use only the documents provided. " "If they don't contain the answer, say you don't know and suggest contacting support. " "Cite the ids of the documents you used in square brackets, like [doc_returns].") def ask(system, prompt, max_tokens=400): response = requests.post("https://api.anthropic.com/v1/messages", headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"}, json={"model": "claude-opus-5-5", "max_tokens": max_tokens, "system": system, "messages": [{"role": "user", "content": prompt}]}, timeout=60) response.raise_for_status() return "".join(b["text"] for b in response.json()["content"] if b["type"] == "text") def answer(question, k=2): hits = requests.get(f"{API}/docs", params={"q": question, "limit": k}).json()["data"] docs = [requests.get(f"{API}/docs/{h['id']}").json() for h in hits] context = "\n".join(f'<document id="{d["id"]}" title="{d["title"]}">{d["text"]}</document>' for d in docs) prompt = f"<documents>\n{context}\n</documents>\n\nQuestion: {question}" return ask(SYSTEM, prompt), [d["id"] for d in docs] reply, sources = answer("How long does express shipping take?") print("sources:", sources) print(reply)
Here's what answer did, line by line:
hitsholds the top k search results for the question.docsfetches the full text of each hit.contextwraps each document in tags, one per line.promptputs the documents first and the question last.- It returns two things: the model's reply and the list of document ids it retrieved.
Returning the sources has a practical benefit. Your app can show "Based on: Shipping and delivery" under the reply, even if the model forgets to cite. A real model replies along the lines of "1 to 2 business days if you order before 1pm Eastern [doc_shipping]".
answer("How long does express shipping take?", k=3) instead. How many ids does sources hold?Show the answer
3
k=3 overrides the default of 2, so the search keeps three hits. sources lists one id for each fetched document, whatever the model cites.
When RAG is the right tool
RAG is not always the answer. A small shop with a one-page price list doesn't need a librarian. It can hand the model the whole page every time.
| Use RAG when | Consider something else when |
|---|---|
| Answers live in documents that change (policies, catalogs, reports) | The knowledge fits in the prompt anyway: paste it all in |
| You need citations people can check | You need a style or format, not facts: use examples (few-shot) |
| The collection is too big for the context window | The task is calculation or live data: use tools (coming up) |
The context window is the most text a model can read in one request. A useful rule follows from it. Say your whole knowledge base is under about 100,000 tokens and doesn't change often. Then try putting all of it in the prompt before you build retrieval.
Pair that with prompt caching, which is a feature that lets the API reuse a long, unchanging start of a prompt across requests. Repeat requests then cost less and come back faster. You'll meet it in the cost and latency lesson. RAG earns its extra moving parts when the material is large or changes daily.
Show the answer
Put all the articles in the prompt, with prompt caching
The whole set fits well under the rough 100,000-token line and rarely changes. Pasting it all in is simpler, and caching keeps the repeated text cheap. RAG pays off when the material is large or changes often.
Practice what you just read
- 3 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 4 review cards for spaced repetition
Try a quiz question
In a RAG system, who searches the documents?