RAG in Production · Operating RAG Systems
When RAG Is the Wrong Tool
"How many orders did we refund last month?" cannot be answered from five chunks; route aggregate and structured questions to tools.
Scenario
Example company: an internal analytics assistant indexes order records, support tickets and reports as text chunks and answers everything with RAG.
What you see in production
"How many orders were refunded last month?" gets confident, wrong numbers, different each time. "Summarise this 40-page contract" misses sections. Questions about the employee handbook, on the other hand, work well.
The naive code
def answer(question):
chunks = search(question, k=5) # five refund records out of 3,000
return llm(question, chunks) # "about 5 refunds last month"
Reproduce it
An aggregate question answered from the top 5 chunks versus from a query over the data:
import random
random.seed(3)
orders = [{"id": i, "month": random.choice(["2026-09", "2026-10"]), "status": random.choice(["paid"] * 9 + ["refunded"])}
for i in range(3000)]
chunks = [f"Order {o['id']} in {o['month']} was {o['status']}." for o in orders]
top5 = [c for c in chunks if "refunded" in c and "2026-09" in c][:5] # what retrieval can show the model
print("RAG sees", len(top5), "refund records, so it answers: about", len(top5))
print("SQL-style count:", sum(o["status"] == "refunded" and o["month"] == "2026-09" for o in orders))
Output
RAG sees 5 refund records, so it answers: about 5 SQL-style count: 145
Root cause
RAG retrieves a few passages that look relevant. That suits questions answered by a specific piece of text. It cannot answer questions that need all the data (counts, sums, trends) or the whole of one long document (a full summary), because the answer is not in any five chunks.
The fix
Route each question to the right method:
- Lookup in text (policies, docs, how-to): RAG.
- Aggregates and filters over records (counts, totals, top N): a tool that queries the database, for example a parameterised query tool or carefully constrained text-to-SQL against a read-only replica, with the result returned to the model.
- Whole-document tasks (summarise, compare two contracts): load the full document into a long context, or summarise section by section and combine.
- Mixed questions: an agent with both a search tool and a query tool chooses per step.
tools = [search_docs_tool, run_report_tool] # run_report: fixed, parameterised analytics queries
# "How many refunds last month?" -> run_report(metric="refunds", period="2026-09")
Trade-offs
- Text-to-SQL is powerful but risky: use read-only credentials, allowlisted tables, query timeouts and row limits, and show the query used.
- Long-context processing of whole documents costs more per question; cache summaries for frequently used documents.
- Routing adds a step that can be wrong; evaluate routing decisions on a labelled set of question types.
Detect it in production
- Questions with numbers in the answer but no tool call: likely guessed aggregates.
- Answer variance for the same question (different numbers on repeated runs).
- User feedback grouped by question type.
Key points
- RAG answers questions whose answer is in a few passages.
- Counts, totals and trends need queries over the data; whole-document tasks need the whole document.
- Route by question type and give agents both search and query tools.
Further reading: Tool use with Claude · Long context prompting tips · OWASP SQL injection prevention
Practice questions
Why can't RAG reliably answer 'how many orders were refunded last month?'
Answer
Retrieval returns a few passages; the answer requires counting across all records, which no small set of chunks contains. A query over the data is needed.
What safeguards does text-to-SQL need?
Answer
Read-only credentials on a replica, allowlisted tables and columns, query timeouts and row limits, and showing the generated query for transparency.
How should a system handle 'summarise this 40-page contract'?
Answer
Process the whole document, either in a long context window or by summarising sections and combining them, rather than retrieving a few chunks.
Open in the app to mark lessons complete, take notes and track your progress.