AI Engineer Dojo Contents
Chapter 7

Query Understanding & Transformation

You've improved the index and the retriever. Many remaining failures come from the query itself: it's too short, too vague, or worded nothing like the document that answers it. Rewriting the query before searching is often the cheapest large gain in recall.

Case study

TravelDesk: one question, two facts

TravelDesk's policy assistant was asked things like "Can I expense a business-class flight to Tokyo?" and got them right only 58% of the time. The question needed two facts from different documents: the rule that business class is allowed on flights over eight hours, and how long the flight to Tokyo is. One retrieval pulled chunks about one fact and missed the other.

They added a step that splits compound questions into sub-questions, retrieves for each, and then answers. Accuracy on multi-fact questions rose to 0.83. They also rewrote follow-ups like "what about Osaka?" into full questions before searching, since a retriever can't do anything with a pronoun.

Retrieval assumes the user's words resemble the document's words (lexical) or its meaning (dense). Real queries break that assumption all the time: "it broke after the update" (which product, which update?), "cheapest plan" when the document says "pricing tiers," or a two-part question whose halves live in different documents. A small language-model step can transform the query into something easier to retrieve. Here are four techniques, roughly from cheapest to most powerful:

TechniqueWhat it doesBest for
RewriteResolve pronouns, add implied context, fix jargonShort, vague or conversational queries
Multi-queryGenerate 3 or 4 paraphrases, retrieve for each, merge the resultsRaising recall
HyDEHave the model write a hypothetical answer, then search with thatQueries that look nothing like the answer text
DecompositionSplit a multi-part question into sub-queriesQuestions that need facts from several documents

Why multi-query helps

One phrasing finds the documents near that phrasing and misses the rest. Paraphrase "how do I stop the app crashing on launch" three ways ("resolve startup failures," "fix boot crash," "application won't start"), retrieve for each and merge the hits. Each phrasing lands near different documents, so together they recover answers no single one would. It's the same idea as hybrid search, applied to the query instead of the retriever.

HyDE: search with a draft answer

A question and its answer often share few words, but two answers to the same question tend to look alike. Hypothetical Document Embeddings (HyDE) takes advantage of that. Ask the model to draft a plausible answer (it doesn't need to be right, only shaped like the real document), embed the draft and search with it. The draft usually lands much closer to the real answer than the question did. It costs one extra model call.

Pay only for what helps

Every transformation adds a model call before retrieval starts, which means latency and cost. Use the cheapest one that fixes the failures you have: rewrite for vague queries, multi-query when recall is the limit, HyDE when questions and answers use different vocabulary, decomposition for multi-part questions. Measure the gain before keeping any of them.

Try it · ~30 minFree · no API key

Measure the multi-query lift

Retrieve for a set of short, hard queries two ways: with the raw query, and with the merged results of three paraphrases. Compute recall@3 for each. The paraphrases are provided here; in production a model would write them. You'll need pip install rank-bm25. Find one query the paraphrases saved.

Starter code

from rank_bm25 import BM25Okapi
import re
def toks(s): return re.findall(r"[a-z0-9]+", s.lower())

CORPUS = [
  "Resolving application startup failures and boot crashes",   # 0
  "Reducing monthly subscription costs on the Basic plan",     # 1
  "How to reset a forgotten account password",                 # 2
  "Troubleshooting slow query performance in the database",    # 3
  "Enabling two-factor authentication for your login",         # 4
]
# (raw query, [paraphrases the LLM would generate], gold doc id)
CASES = [
  ("app won't launch", ["application startup failure","boot crash fix","program won't start"], 0),
  ("make it cheaper",  ["reduce subscription cost","lower monthly price","cheapest plan"],      1),
  ("can't log in, forgot my code", ["reset forgotten password","account recovery","login help"], 2),
]
bm25 = BM25Okapi([toks(d) for d in CORPUS])
def ranks(q):
    s = bm25.get_scores(toks(q)); return sorted(range(len(CORPUS)), key=lambda i:s[i], reverse=True)

# TODO single: recall@3 using ranks(raw)[:3]
# TODO multi:  union of ranks(p)[:3] for each paraphrase p; hit if gold in the union
#      (union recall@3 = fraction of CASES whose gold is in the unioned set)
Worked solution
def single_hit(raw, paras, gold): return gold in ranks(raw)[:3]

def multi_hit(raw, paras, gold):
    hits = set()
    for p in paras:
        hits.update(ranks(p)[:3])
    return gold in hits

sr = sum(single_hit(*c) for c in CASES) / len(CASES)
mr = sum(multi_hit(*c)  for c in CASES) / len(CASES)
print(f"single-query recall@3: {sr:.2f}")
print(f"multi-query  recall@3: {mr:.2f}")

Single-query recall should land between 0.33 and 0.67, and multi-query around 1.00. "Make it cheaper" shares no content words with "Reducing subscription costs," so BM25 misses it, but the paraphrase "reduce subscription cost" matches directly. Keep the cost in mind: you tripled the retrieval calls and added a model call. That's worth it when recall is the problem and wasted latency when it isn't.

To take it further, implement HyDE with a real model call, embed the draft answer with Chapter 4's embed(), and compare its recall with multi-query. HyDE tends to win when question and answer vocabularies differ most.

Running case · Acme Vault

Acme rewrites short queries using the recent conversation ("it's broken" becomes the endpoint just discussed) and expands "vault export" with its synonyms, "dump" and "backup." Those rewrites recover about 15% of the retrievals Acme had been missing.

If you're starting out

HyDE is a nice idea to be able to explain simply, because it sounds odd at first: search with an answer that might be wrong. The explanation is that answers resemble each other more than questions resemble answers. If you can say that clearly, it shows you understand retrieval and haven't only read the name.

Query rewriting is also a good small project. Log a few dozen real-looking vague questions, rewrite them with a model, and report the recall change.

If you run the team

Query transformation is attractive because it improves results without rebuilding the index. It isn't free, though: each technique adds a model call and a bit of delay to every question. Ask the team which failures a transformation fixes and what it did to recall on the evaluation set. If they added three techniques at once "to be safe," ask them to show the gain from each.

For conversational products, rewriting follow-up questions ("what about the other plan?") into full questions is usually a clear win and worth prioritizing.

Quiz · Chapter 7

  1. Why does multi-query retrieval raise recall?
  2. Why does HyDE embed a hypothetical answer instead of the question?
  3. Single-query recall is already high, and multi-query adds nothing but triples latency. What should you do?
  4. "Which plan is cheapest, and does it include 2FA?" What's the best transformation?
  5. What makes query transformation attractive compared with other fixes?
Back Continue

The AI Search / RAG Engineer · aiengineerdojo.com