Query transformation
The basic RAG pipeline searches with exactly what the user typed. That works in a demo, where questions are complete sentences written by the people who wrote the documents. Real users type "and if I paid by UPI?", "refund??", or a question that bundles three things together. The search engine can only find what the query describes, so when the query is poor, retrieval fails before it starts.
Query transformation means using a model to turn what the user typed into one or more better searches before retrieval runs. It is the cheapest, highest payoff upgrade in this whole section, and most production RAG systems do some version of it.
A user in a chat asks 'and what about the car loan?'. Your vector search returns nothing useful. Name one way to fix the query before searching, and what it costs.
Five techniques, one question#
A customer asked about a late order refund, got an answer, and then followed up. Two documents in the help centre are needed to answer. Try each technique.
- and if I paid by UPI?
- 1.Setting up UPI autopay for subscriptions
- 2.Payment failed but money was debited
- 3.Wallet credit expires after 90 days
Rewrite. Turn the latest message into a standalone question using the conversation so far. One small model call, and it fixes follow ups, pronouns ("it", "that one") and typos. If you add only one technique, add this one.
Multi-query. Ask for three to five differently worded searches, run them in parallel, and merge the results with reciprocal rank fusion (from the retrieval quality chapter). It catches vocabulary you did not anticipate, at the cost of several searches per question.
HyDE, hypothetical document embeddings, from a 2022 paper. The model writes a plausible answer and you search with that, because an answer looks far more like your documents than a short question does. Strong on vague, short questions. Weak when the invented answer contains a wrong detail that drags in the wrong documents, so never show the fake answer to anyone.
Decomposition. Split a compound or multi-hop question into sub-questions, retrieve for each, then answer with everything. "Compare the refund policies for late and cancelled orders" becomes two searches.
Step back. For very specific questions, also search a more general version ("what is the refund policy?") so the model gets the principle as well as the detail.
What the code looks like#
A rewrite step is a single small model call with structured output. Here it is as a function you put in front of any retriever.
from anthropic import Anthropic
client = Anthropic()
REWRITE = """Rewrite the customer's last message as one standalone search
query for a help centre. Use the conversation for context. Return only
the query, no explanation."""
def rewrite_query(history: list[dict], last_message: str) -> str:
convo = "\n".join(f"{m['role']}: {m['content']}" for m in history)
response = client.messages.create(
model="claude-haiku-5-5", # a small, fast model is enough here
max_tokens=256,
system=REWRITE,
messages=[{"role": "user", "content": f"{convo}\nuser: {last_message}"}],
)
return next(b.text for b in response.content if b.type == "text").strip()The pattern is the same with any provider or framework: a cheap model, a narrow instruction, and the original message kept as a fallback if anything goes wrong.
Routing the query#
Not every question should go to the same index. A rewrite step can also classify: is this about an order (use the orders tool, not the help centre), a policy (search the policy index), or small talk (search nothing)? Routing is the routing pattern applied to retrieval, and it often saves a search entirely.
It can also extract filters: "UPI refunds in the last month" becomes a search for "UPI refund" with a date filter, which vector similarity alone handles badly.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Rewrite | Fixes follow ups and vague messages with one cheap call. | Adds a short delay before retrieval, and a bad rewrite can lose meaning. | Any chat interface. The default. |
| Multi-query | Covers many phrasings, raises recall. | Several searches per question, and more noise to rerank. | Diverse vocabulary, or users who do not know the right terms. |
| HyDE | Matches answer-shaped documents from short questions. | A full generation before searching, and invented details can mislead. | Short or vague questions against long, formal documents. |
| Decomposition | Handles compound and multi-hop questions. | More searches and a harder final answer to assemble. | Comparisons and questions that need facts from several places. |
Each technique adds latency and cost, and on some datasets HyDE or multi-query make results worse. Run your retrieval evals with and without each step and keep only what moves recall.
Checkpoint#
1. Why does a follow-up like "and if I paid by UPI?" retrieve the wrong documents?
2. What does HyDE search with?
3. A question compares two policies that live in different documents. Which technique fits best?
- Rewrite follow ups into standalone queries. One small call, the biggest win in chat.
- Multi-query and decomposition raise recall by running several searches.
- HyDE searches with a fake answer. Good for vague questions, never shown to users.
- Route and extract filters before searching at all.
Users type follow ups, fragments and compound questions, and retrieval can only find what the query describes. So before searching I transform the query. A small model rewrites the last message into a standalone question using the conversation, which fixes most chat failures for one cheap call. Multi-query writes several phrasings and fuses the results, HyDE searches with a hypothetical answer for vague questions, and decomposition splits compound questions into sub-questions. The same step can route to the right source and extract filters like dates. Each one costs latency, so I keep only the ones my retrieval evals show actually raise recall.