Hallucinations and grounding
Why models make things up, and how to design against it.
Create a free account Try part of it here
Why it matters
Picture a model writing a blog post. It adds "9 in 10 hikers prefer recycled fleece". Nobody ever measured that. Or a support reply promises a discount that doesn't exist. Customers screenshot everything, and advertising law expects every claim to be backed up. You can't stop a model from ever making things up. But you can design prompts and checks that catch most of it before anyone sees it. This lesson shows you how.
What you'll be able to do
- Explain why language models produce confident, false statements
- Recognize which kinds of output carry the most risk
- Apply prompt techniques that reduce hallucination: grounding, permission to abstain, quotes first
- Write code that flags numbers and quotes an answer can't back up
What it covers
- Why models make things up
- Prompt techniques that help
- Check answers with code
- Catch the invented number
Why models make things up
Think of a very fluent new hire who hates saying "I don't know". Ask them your return window on day one and they'll give a confident answer. It sounds right. It may be wrong.
A language model works the same way. It writes the most likely next words, given everything before them. Most of the time, likely and true are the same thing. Sometimes the model doesn't have the fact. Maybe it was never in its training data. Maybe it happened after the model's training cutoff, the date its training data stops. Maybe it's in a document you didn't give it. The most likely next words are still a fluent, specific, confident sentence.
That's a hallucination: output that sounds right but isn't backed by any source.
Three things make hallucinations more likely:
- Specifics. Numbers, dates, names, prices, web addresses, quotes and citations. A plausible number is easy to write and hard to spot as wrong.
- Pressure to answer. Models are trained to be helpful. Ask "What's our return window?" with no documents, and a model may give a typical answer instead of admitting it doesn't know.
- Long answers. Every extra step and sentence is another chance for one wrong detail.
Today's models make things up much less than early ones did. They're also better at saying "I'm not sure". But less isn't never. Anything customer-facing or factual needs a design that expects some mistakes.
Four kinds to watch for
| Kind | Example |
|---|---|
| Fabrication | "Our fleece is made from 40 recycled bottles" (the source says about 25) |
| Unfaithful to the source | The returns page says 60 days; the answer says 90 |
| Wrong attribution | A correct fact credited to the wrong document |
| Outdated | Last year's prices, from the model's training data |
Show the answer
Unfaithful to the source
The model had the right document and still gave a different number. That's being unfaithful to the source. Outdated would mean old facts from training data; wrong attribution would mean a true fact credited to the wrong document.
Prompt techniques that help
1. Ground it. Give the model the facts, and tell it to use only those. The facts can come from retrieval (RAG), a pasted product sheet or data from your own systems. This is called grounding, and it's the biggest single lever you have.
2. Give it permission to say "I don't know". Write "If the documents don't contain the answer, say you don't know." It sounds obvious. It measurably cuts invented answers. Make "I don't know" an acceptable outcome, not a failure.
3. Quotes first, then the answer. For questions about long documents, ask the model to copy the exact sentences it will rely on first. Then it answers using only those quotes. Quotes are easy to check, and the extra step keeps the answer close to the text:
Answer the question using the documents. First, inside <quotes> tags, copy word for word the sentences from the documents that answer it. If no sentence answers it, write <quotes>none</quotes> and say you don't know. Then give your answer inside <answer> tags, using only the quoted sentences.
4. Limit the possible answers. Sometimes the answer must be one of a known set: a product id, a category, yes or no. Use structured output with a fixed list of allowed values (an enum, from the structured output lesson). A model can't invent a product id the list doesn't allow.
5. Keep a person on high-stakes claims. Prices, legal, medical and environmental claims, and anything quoting a real person: have someone approve them before they go out.
For marketers: treat model-written claims like a new copywriter's draft. Every number and every "proven" needs a source before it ships.
Show the answer
"Our jackets were rated #1 by 87% of testers in 2025"
Specific numbers, rankings and dates are where made-up details hide, and they carry the most risk.
Check answers with code
Some hallucinations can be caught by a simple program. Here's the idea. If an answer contains a number that appears in none of the source documents, something is off. At best the model calculated it. At worst it invented it. Either way, a person should look.
The code below fetches the practice shop's returns page. Then it pulls every number out of an answer and lists the ones the page never mentions:
import re import requests source = requests.get("https://api.gradient.test/v1/docs/doc_returns").json()["text"] answer = "You can return unworn items within 90 days, and refunds arrive within 5 business days." def numbers(text): return re.findall(r"\d+(?:\.\d+)?", text) source_numbers = set(numbers(source)) print("numbers in the answer:", numbers(answer)) print("not in the source:", [n for n in numbers(answer) if n not in source_numbers])
How it works:
re.findallfinds every piece of text that matches a pattern. The pattern\d+means "one or more digits", and the optional part catches decimals such as 4.5.set(...)makes a collection you can check membership in quickly.- The last line keeps only the answer's numbers that aren't in the source.
The same idea works for quotes. If you asked the model to quote first, check that every quote really appears in the documents. Compare after lowercasing and squeezing all spaces and line breaks into single spaces, so a line break doesn't count as a mismatch:
def normalize(text): return " ".join(text.lower().split()) source = "Gift cards never expire and can be used on any product,\nincluding sale items." quotes = ["Gift cards never expire", "Gift cards can be refunded for cash"] for q in quotes: print(normalize(q) in normalize(source), "|", q)
The first quote is in the source. The second one isn't, so it prints False: the model invented it.
These checks are cheap, so you can run them on every answer. They turn "the bot sometimes makes things up" into a short list of specific answers for a person to review. A second model asked "is every claim in this answer backed by these documents?" catches subtler cases. You'll build that in the LLM-as-judge lesson.
source = "The warranty lasts 2 years." quote = "lasts 5 years" print(quote in source)
Show the answer
False
The source says 2 years, so the words lasts 5 years aren't in it, and in gives False. A cheap check like this catches a quote that isn't really in the source.
Show the answer
A person should look: the number may be calculated or made up
The check only knows the number isn't in the source. It might be a fair calculation (2 x 30 days = 60) or an invention. Either way, it's a reason for a person to look, not proof of an error.
Catch the invented number
A shop's source text says returns are free within 60 days, and refunds take 5 to 10 business days. A model answers: "Return within 90 days and get your refund in 5 days."
By hand: the answer's numbers are 90 and 5. The source's numbers are 60, 5 and 10. So 5 is backed up, and 90 isn't. The same check in a few lines of Python:
import re source = "Returns are free within 60 days. Refunds take 5 to 10 business days." answer = "Return within 90 days and get your refund in 5 days." found = re.findall(r"\d+", answer) print(found) print([n for n in found if n not in re.findall(r"\d+", source)])
It prints ['90', '5'], then ['90']. The 90-day window is the made-up detail, so this answer goes to a person instead of to the customer. Notice the check doesn't know which number is right. It only knows 90 has no source.
The documents don't answer the question. With a real model, check the reply declines instead of guessing. Then delete the sentence about saying you don't know from the system prompt, and compare.
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
What is a hallucination?