RAG Query Rewriting: Multi-Query, HyDE, and Sub-Query Decomposition
TL;DR — RAG query rewriting transforms user queries before retrieval to improve accuracy. Four techniques: (1) Multi-query retrieval generates 3-5 query reformulations and combines results for higher recall, (2) HyDE generates a hypothetical answer and uses it as the search query, (3) Step-back prompting extracts a broader principle before retrieving, (4) Sub-query decomposition breaks complex questions into simpler parts. Query rewriting bridges the gap between how users ask questions and how documents are written. Use it when queries are short, conversational, complex, or reference previous context. Don't use it for simple factual lookups — it adds latency (one LLM call) without benefit. Combine with hybrid retrieval and cross-encoder reranking for maximum accuracy.
Users don't ask questions the way documents are written. A user asks "How do I handle a breach?" but the document says "Incident Response Procedures for Security Compromises." The semantic intent is the same, but the lexical and vector similarity may be low.
Query rewriting bridges this gap by transforming the user's query before retrieval. Instead of searching with the original question, the system generates one or more modified queries that are more likely to match relevant documents.
The Query Rewriting Problem
| User Query | Document Language | Problem | Solution |
|---|---|---|---|
| "How do I handle a breach?" | "Incident Response Procedures" | Vocabulary mismatch | Query rewriting |
| "What about John's team?" | "Engineering Department org chart" | Ambiguous reference | Context-aware rewriting |
| "Compare our top 3 products" | Individual product datasheets | Aggregation required | Sub-query decomposition |
| "Why did revenue drop?" | Q3 financial report, market analysis | Multi-hop reasoning | Sub-query decomposition |
| "That policy I asked about" | "Remote Work Policy v2.3" | Follow-up reference | Conversation-aware rewriting |
Four Query Rewriting Techniques
(no rewrite needed)"] Router -->|Short, conversational| HyDE["1. HyDE
Generate hypothetical answer
→ use as search query"] Router -->|Broad, low recall| MultiQuery["2. Multi-Query
Generate 3-5 reformulations
→ retrieve all, deduplicate"] Router -->|Complex, multi-part| Decompose["3. Sub-Query Decomposition
Break into sub-questions
→ retrieve each separately"] Router -->|Abstract, principle-based| StepBack["4. Step-Back Prompting
Extract broader principle
→ retrieve on principle"] HyDE --> Retrieve["Hybrid Retrieval"] MultiQuery --> Retrieve Decompose --> Retrieve StepBack --> Retrieve Original --> Retrieve Retrieve --> Rerank["Cross-Encoder Reranking"] Rerank --> Generate["LLM Generation"]
Technique 1: Multi-Query Retrieval
Generate multiple reformulations of the query, retrieve for each, and combine results:
class MultiQueryRetriever:
"""Generate multiple query reformulations and combine results."""
REWRITE_PROMPT = """Rewrite this query in {n} different ways to improve
document retrieval. Keep the same intent but use different phrasing,
synonyms, and perspectives.
Original query: {query}
Rewrites (one per line):
"""
def retrieve(self, query: str, retriever, n_rewrites: int = 3) -> list:
# Generate rewrites
rewrites_text = self.llm.generate(
self.REWRITE_PROMPT.format(query=query, n=n_rewrites)
)
rewrites = [query] + rewrites_text.strip().split('\n')
# Retrieve for each query
all_results = []
for rewritten_query in rewrites:
results = retriever.search(rewritten_query, top_k=20)
all_results.extend(results)
# Deduplicate by document ID, keep highest score
seen = {}
for result in all_results:
doc_id = result["id"]
if doc_id not in seen or result["score"] > seen[doc_id]["score"]:
seen[doc_id] = result
return list(seen.values())[:20]
When to use: Broad queries where recall matters more than precision. When the original query might not match the document's vocabulary.
Cost: One LLM call for rewriting + n_rewrites × retrieval calls.
Technique 2: HyDE (Hypothetical Document Embeddings)
HyDE generates a hypothetical answer and uses it as the search query:
class HyDERetriever:
"""Use hypothetical answer as search query."""
HYDE_PROMPT = """Generate a brief, factual answer to this question.
Don't worry about accuracy — this will be used for document retrieval,
not as a final answer.
Question: {query}
Hypothetical answer:
"""
def retrieve(self, query: str, vector_store, top_k: int = 10) -> list:
# Generate hypothetical answer
hypothetical_answer = self.llm.generate(
self.HYDE_PROMPT.format(query=query)
)
# Use hypothetical answer as search query
# (it's closer in semantic space to the real answer document)
query_embedding = self.embedder.embed(hypothetical_answer)
results = vector_store.search(
query_embedding=query_embedding,
top_k=top_k
)
return results
When to use: Short, conversational queries. When the question is far in semantic space from the answer document.
Cost: One LLM call for hypothetical answer generation + one embedding + one retrieval.
Why it works: A hypothetical answer like "Our refund policy allows enterprise customers to request refunds within 90 days of purchase..." is semantically closer to the actual refund policy document than the question "How do I get my money back?"
Technique 3: Sub-Query Decomposition
Break complex questions into simpler sub-questions:
class SubQueryDecomposer:
"""Decompose complex queries into sub-questions."""
DECOMPOSE_PROMPT = """Break this complex question into {n} simpler
sub-questions that can each be answered independently. Each sub-question
should cover one aspect of the original question.
Original question: {query}
Sub-questions (one per line):
"""
def retrieve_and_answer(self, query: str, retriever, llm) -> dict:
# Decompose into sub-queries
sub_queries_text = llm.generate(
self.DECOMPOSE_PROMPT.format(query=query, n=3)
)
sub_queries = sub_queries_text.strip().split('\n')
# Retrieve and answer each sub-query
sub_answers = []
for sq in sub_queries:
results = retriever.search(sq, top_k=5)
context = self._format_context(results)
answer = llm.generate(
f"Answer this question using the context:\n"
f"Question: {sq}\nContext: {context}\nAnswer:"
)
sub_answers.append({
"sub_query": sq,
"answer": answer,
"sources": results
})
# Synthesize final answer from sub-answers
final_answer = llm.generate(
f"Synthesize a complete answer from these sub-answers:\n"
f"Original question: {query}\n"
f"Sub-answers: {json.dumps(sub_answers, indent=2)}\n"
f"Final answer:"
)
return {
"answer": final_answer,
"sub_queries": sub_answers,
"decomposition_used": True
}
When to use: Multi-hop questions, comparison questions, questions requiring information from multiple documents.
Cost: One LLM call for decomposition + n × (retrieval + LLM call) + one LLM call for synthesis.
Technique 4: Step-Back Prompting
Extract a broader principle or concept before retrieving:
class StepBackRetriever:
"""Extract broader principle before retrieval."""
STEPBACK_PROMPT = """Given this specific question, identify a broader,
more general principle or concept that would help answer it.
Specific question: {query}
Broader principle:
"""
def retrieve(self, query: str, retriever, top_k: int = 10) -> list:
# Extract broader principle
principle = self.llm.generate(
self.STEPBACK_PROMPT.format(query=query)
)
# Retrieve using both original and step-back query
original_results = retriever.search(query, top_k=top_k)
stepback_results = retriever.search(principle, top_k=top_k)
# Combine and deduplicate
combined = self._deduplicate(original_results + stepback_results)
return combined[:top_k]
When to use: Questions that require domain principles or general rules. When the specific query is too narrow to retrieve relevant background.
Comparison of Techniques
| Technique | Latency Added | Recall Improvement | Best For |
|---|---|---|---|
| Multi-query | 1 LLM call | +15-25% | Broad queries, vocabulary mismatch |
| HyDE | 1 LLM call + 1 embedding | +10-20% | Short, conversational queries |
| Sub-query decomposition | 1 + n LLM calls | +20-30% | Complex, multi-hop questions |
| Step-back prompting | 1 LLM call | +10-15% | Abstract, principle-based questions |
| No rewriting | 0 | Baseline | Simple, specific queries |
When NOT to Use Query Rewriting
- Simple factual lookups — "What is our PTO policy?" already matches the document
- Exact term matching — "ISO 27001" is better served by BM25 than rewriting
- Latency-sensitive applications — rewriting adds 200-500ms per query
- High-volume, low-complexity queries — cost of rewriting outweighs benefit
Combining Query Rewriting with Other Techniques
class AdvancedRAGPipeline:
"""Full RAG pipeline with query rewriting, hybrid retrieval, and reranking."""
def answer(self, query: str, user_id: str) -> dict:
# Step 1: Classify query — does it need rewriting?
query_type = self._classify_query(query)
# Step 2: Apply appropriate rewriting
if query_type == "simple":
search_queries = [query]
elif query_type == "conversational":
search_queries = [self.hyde.generate(query)]
elif query_type == "complex":
search_queries = self.decomposer.decompose(query)
else:
search_queries = self.multi_query.rewrite(query, n=3)
# Step 3: Hybrid retrieval for each query
all_results = []
for sq in search_queries:
results = self.hybrid_retriever.retrieve(sq, user_id, top_k=20)
all_results.extend(results)
# Step 4: Deduplicate and rerank
deduped = self._deduplicate(all_results)
reranked = self.reranker.rerank(query, deduped[:50])
# Step 5: Generate with citations
answer = self.generator.generate(query, reranked[:10])
return answer
RAG Query Rewriting Checklist
- [ ] Classify queries: simple, conversational, complex, or abstract
- [ ] Don't rewrite simple queries — it adds latency without benefit
- [ ] Implement multi-query retrieval for broad, low-recall queries (3-5 rewrites)
- [ ] Implement HyDE for short, conversational queries
- [ ] Implement sub-query decomposition for complex, multi-hop questions
- [ ] Implement step-back prompting for abstract, principle-based questions
- [ ] Combine with hybrid retrieval (BM25 + vector)
- [ ] Apply RRF fusion when combining multi-query results
- [ ] Add cross-encoder reranking after retrieval
- [ ] Deduplicate results when using multi-query or sub-query techniques
- [ ] Measure recall improvement: compare rewritten vs non-rewritten retrieval
- [ ] Monitor latency: query rewriting adds 200-500ms per query
- [ ] Cache rewritten queries for repeated or similar questions
- [ ] Use conversation-aware rewriting for follow-up questions
- [ ] Enforce document-level ACLs in all retrieval calls
- [ ] Test with real enterprise queries (short, ambiguous, complex)
- [ ] Consider semantic answer caching for query-level dedup
- [ ] Integrate with AI knowledge base pipeline
- [ ] Measure hallucination rates with and without rewriting
- [ ] A/B test rewriting techniques on your actual query distribution
FAQ
What is RAG query rewriting?
RAG query rewriting is the process of transforming a user's original query into one or more modified queries before retrieval. Users often ask questions in conversational, ambiguous, or incomplete ways that don't match the language in source documents. Query rewriting bridges this gap by making queries more specific, decomposing complex questions into sub-queries, or generating hypothetical answers to use as search queries. Techniques include multi-query rewriting, HyDE, step-back prompting, and sub-query decomposition.
What is HyDE in RAG?
HyDE (Hypothetical Document Embeddings) is a query rewriting technique where the LLM first generates a hypothetical answer to the user's question, then uses that hypothetical answer as the search query instead of the original question. The idea is that a hypothetical answer is closer in semantic space to the real answer document than the question is. HyDE improves retrieval when queries are short or conversational but adds one LLM call of latency before retrieval.
What is multi-query retrieval?
Multi-query retrieval generates multiple reformulations of the user's query, retrieves documents for each reformulation, and combines (deduplicates) the results. This improves recall by casting a wider net — different reformulations may match different relevant documents. The original query plus 3-5 reformulations is typical. Results are fused using Reciprocal Rank Fusion or simple deduplication with score averaging.
What is sub-query decomposition?
Sub-query decomposition breaks a complex question into simpler sub-questions that can each be answered independently. For example, "What's the risk profile of this customer given their recent acquisitions?" decomposes into "What are the customer's recent acquisitions?" and "What is the risk profile of companies with those acquisition patterns?" Each sub-query is retrieved separately, and results are combined for the final answer.
When should you use query rewriting in RAG?
Use query rewriting when: (1) queries are short or conversational and don't match document language, (2) queries are complex and require multiple pieces of information, (3) retrieval recall is low despite having relevant documents, (4) users ask follow-up questions that reference previous context. Don't use query rewriting for simple factual lookups where the original query already matches document language well — it adds latency without benefit.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →