conceptRetrieval & Knowledge~3 min readUpdated 2026-06-10#rag#query-rewriting#hyde#multi-query

Query transformations (rewriting, HyDE, multi-query)

Mental model: there is a register mismatch between how users ask (terse, pronoun-laden, multi-part, typo'd) and how answers are written (complete, declarative prose). Query transformation spends an LLM call before retrieval to close that gap — often a bigger recall win than any index tuning, and the only fix for conversational follow-ups.

The techniques, with their papers

  • Rewriting / contextualization — resolve references and produce a standalone search query. In chat, "what about the second one?" is unsearchable until history is folded in. Rewrite-Retrieve-Read (Ma et al. 2023, arXiv:2305.14283) formalized the gain from rewriting before retrieval.
  • Multi-query — generate 3–5 paraphrases, retrieve for each, fuse with RRF. Reduces sensitivity to wording; cheap insurance for recall.
  • Decomposition — split compound questions ("compare our 2024 and 2025 refund policies") into sub-queries, retrieve per sub-query, answer over the union (task decomposition applied to retrieval).
  • HyDE (Gao et al. 2022, arXiv:2212.10496) — have the LLM draft a hypothetical answer, embed that instead of the question. The fake answer lives in the same linguistic register as real documents, so dense similarity improves. Shines zero-shot with weak/no domain tuning; loses value once embeddings are strong and hybrid+rerank are in place — and it injects the model's parametric guesses into retrieval (a hallucinated draft can steer retrieval wrong).
  • Expansion — add synonyms/related terms; mostly a BM25-side trick now, largely subsumed by multi-query.

Conversational rewriting (the one you'll definitely need)

import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();

export async function rewriteQuery(
  history: { role: "user" | "assistant"; content: string }[],
  question: string,
): Promise<string> {
  const msg = await anthropic.messages.create({
    model: "claude-haiku-4-5", // fast+cheap; rewriting doesn't need a frontier model
    max_tokens: 200,
    system:
      "Rewrite the user's last message as ONE standalone search query. " +
      "Resolve pronouns and references using the conversation. " +
      "Preserve exact identifiers (error codes, SKUs, names) verbatim. " +
      "If it is already standalone, return it unchanged. Return ONLY the query.",
    messages: [
      ...history.slice(-6), // recent turns suffice; full history adds cost, not signal
      { role: "user" as const, content: `Rewrite this as a standalone query: ${question}` },
    ],
  });
  const text = msg.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
  return text || question; // empty/failed rewrite → fall back to the original
}

Two details that matter in production: preserve identifiers verbatim (a rewrite that "fixes" E1042 to "error 1042" breaks the BM25 branch), and always retrieve with the original query too — union both result sets so an over-eager rewrite can't lose what the raw query would have found.

Decision rules

Symptom (from evals) Transform
Follow-up questions retrieve nonsense rewriting — mandatory for chat UIs
Recall varies wildly with phrasing multi-query (3–5 paraphrases + RRF)
Compound questions answered partially decomposition
Zero-shot domain, weak embeddings, no reranker yet HyDE
Exact-term queries miss not a transform problem — hybrid

Ordering heuristic: hybrid + rerank before transforms. They fix more failure modes per millisecond, cost no tokens per query, and transforms stack cleanly on top later. Exception: conversational rewriting, which nothing else replaces — build it into any chat-style RAG from day one.

Cost & latency lens

Every transform is an extra serial LLM call before retrieval starts: ~200–600 ms with a small fast model (Haiku-class), plus tokens. Multi-query also multiplies retrieval fan-out (cheap) and candidate volume for reranking (not cheap). Mitigations: small model, max_tokens ≤ 200, tight output format, run multi-query paraphrase retrievals in parallel, and cache rewrites for repeated questions (semantic caching). Trace the rewrite as its own span — when retrieval misbehaves you must see what was actually searched, which is no longer what the user typed.

Failure modes

  • Intent drift — the rewrite answers a slightly different question; user notices, metrics don't. Mitigate: original-query union (above) + eval set with conversational cases.
  • HyDE hallucination steering — the hypothetical answer asserts a wrong entity and retrieval dutifully follows it. Keep HyDE off factual-lookup paths where wrong-doc retrieval is costly.
  • Transform-by-default — adding all techniques "for robustness" buys latency and cost with no measured gain. Add each transform against a named, measured recall failure; delete it if the eval delta disappears after other upgrades.
  • Rewriter sees stale history — truncated/summarized history makes the rewriter resolve "it" to the wrong referent. Keep the last few raw turns, not a summary.

Connects to: recall · decomposition · measuring retrieval · conversation history

Sources