Siddhant Deval
Siddhant Deval
backend5 min read

Query Transformation, Decomposition & Multi-Query Expansion

Raw conversational queries diverge linguistically from formal technical documentation, producing vocabulary mismatches that silently collapse retrieval recall. This article implements query rewriting, Hypothetical Document Embeddings (HyDE), step-back abstraction, and parallel multi-query decomposition for the Workspace knowledge router.

Series·Part 13 of 24

Full-Stack AI Architecture: From Streaming Foundations to Resilient Agentic Systems in Node.js & React

Query Transformation, Decomposition & Multi-Query Expansion

A user asking "how do I stop my server from running out of memory when streaming" does not use the phrase "V8 heap exhaustion" or "TransformStream highWaterMark" — the technical terms present in the documentation. Dense vector search finds semantic neighbours of the query vector, but the vocabulary gap between casual user language and precise technical writing reduces recall. Exact phrase lookups (Part 10's BM25) do not help when none of the words match.

This article implements four query transformation strategies for the Workspace knowledge router: query rewriting to align vocabulary, HyDE to embed a hypothetical answer rather than the question, step-back abstraction to retrieve foundational context, and parallel multi-query decomposition for compound questions — all orchestrated before the hybrid RRF retrieval pipeline of Part 10.

Architectural Note

Who this is for: Engineers whose RAG system has high recall for technical queries from documentation-fluent users but fails on natural language questions from end users. Query transformation is the gap-bridger.


1. Query Rewriting

1.1 The Vocabulary Gap

User query:    "my server keeps crashing during long AI sessions"
Documentation: "V8 heap exhaustion under concurrent LLM streaming connections"

Shared vocabulary: (none)
Dense similarity: ~0.62 (below threshold for confident retrieval)

Query rewriting prompts the LLM to rephrase the user's query in the technical vocabulary of the knowledge domain, before retrieval.

TYPESCRIPT
// ✅ Query rewriter — aligns vocabulary to documentation style
// file: packages/db/src/retrieval/QueryTransformer.ts

import { generateObject } from 'ai'
import { createAnthropic } from '@ai-sdk/anthropic'
import { z } from 'zod'

const anthropic = createAnthropic()

const RewriteResultSchema = z.object({
  rewrittenQuery: z.string(),
  keywords: z.array(z.string()).max(5),
})

export async function rewriteQuery(
  userQuery: string,
  domainContext: string = 'Node.js, TypeScript, React, LLM streaming, RAG, vector databases',
): Promise<string> {
  const { object } = await generateObject({
    model: anthropic('claude-3-haiku-20240307'),
    schema: RewriteResultSchema,
    prompt: `Rewrite the following user question as a precise technical query using terminology 
appropriate for engineering documentation about: ${domainContext}.

User question: "${userQuery}"

Produce a concise technical rewrite (1-2 sentences) that would match the vocabulary used in 
technical documentation and code comments. Also list up to 5 key technical keywords.`,
  })

  // Combine rewritten query with keyword boosting for BM25
  return object.keywords.length > 0
    ? `${object.rewrittenQuery} ${object.keywords.join(' ')}`
    : object.rewrittenQuery
}

2. Hypothetical Document Embeddings (HyDE)

2.1 Why Embedding the Answer Beats Embedding the Question

Query vectors and document vectors occupy different regions of the embedding space — even when semantically related. A question vector points toward "uncertainty" and "asking", while document vectors point toward "definition" and "stating facts".

HyDE bridges this gap by generating a hypothetical answer to the question and embedding that instead.

TYPESCRIPT
// ✅ HyDE — generate and embed a hypothetical answer
// file: packages/db/src/retrieval/HyDE.ts

import { generateText } from 'ai'
import { createAnthropic } from '@ai-sdk/anthropic'
import { embedAndNormalise } from '../embeddings'

const anthropic = createAnthropic()

export async function hydeEmbed(userQuery: string): Promise<number[]> {
  // Generate a hypothetical answer — quality intentionally modest to stay fast
  const { text: hypotheticalAnswer } = await generateText({
    model: anthropic('claude-3-haiku-20240307'),
    maxTokens: 300,  // Short answer — we're generating for embedding, not display
    temperature: 0.1,
    prompt: `Write a brief, factual technical answer (3-5 sentences) to this question as if 
you were writing documentation. Include technical terms. Question: "${userQuery}"`,
  })

  // Embed the hypothetical answer — its vector is closer to documentation vectors
  return embedAndNormalise(hypotheticalAnswer)
}
Crucial Requirement

HyDE augments only the dense retrieval leg. Pass the hypothetical passage embedding as the vector input, but still pass the original user query as queryText to the sparse BM25 leg of hybridSearch(). HyDE and BM25 are complementary: HyDE improves semantic recall while BM25 preserves exact-match coverage. Using HyDE output for BM25 would break exact-match retrieval.


3. Step-Back Abstraction

3.1 Retrieving Foundational Context First

For questions about specific implementations, retrieving the foundational concept often provides the necessary context for a complete answer.

TYPESCRIPT
// ✅ Step-back: retrieve at a higher abstraction level
// file: packages/db/src/retrieval/StepBack.ts

import { generateText } from 'ai'
import { createAnthropic } from '@ai-sdk/anthropic'

const anthropic = createAnthropic()

export async function stepBack(specificQuery: string): Promise<string> {
  const { text } = await generateText({
    model: anthropic('claude-3-haiku-20240307'),
    maxTokens: 100,
    prompt: `Given this specific technical question, generate a more general/foundational 
question that would retrieve conceptual documentation useful for answering the specific question.

Specific: "${specificQuery}"
General (one sentence):`,
  })
  return text.trim()
}

// Example:
// Specific: "How do I set highWaterMark for pgvector HNSW index queries?"
// General:  "What are the HNSW indexing parameters in pgvector and how do they affect performance?"

4. Multi-Query Decomposition

4.1 Splitting Compound Questions

Compound user questions span multiple document topics. A single query vector is a centroid of all topics — precisely the wrong location for any of them individually.

TYPESCRIPT
// ✅ Multi-query decomposition — parallel retrieval, fused results
// file: packages/db/src/retrieval/MultiQueryDecomposer.ts

import { generateObject } from 'ai'
import { createAnthropic } from '@ai-sdk/anthropic'
import { z } from 'zod'
import { hybridSearch, HybridSearchResult } from './HybridSearch'

const anthropic = createAnthropic()

const DecomposedQueriesSchema = z.object({
  queries: z.array(z.string()).min(1).max(4),
})

export async function multiQueryRetrieve(
  orgId: string,
  userQuery: string,
  candidatesPerQuery = 20,
): Promise<HybridSearchResult[]> {
  // Decompose compound query into atomic sub-queries
  const { object } = await generateObject({
    model: anthropic('claude-3-haiku-20240307'),
    schema: DecomposedQueriesSchema,
    prompt: `Decompose the following question into 2-4 atomic sub-questions, each targeting 
a specific topic. Each sub-question should be independently answerable.

Question: "${userQuery}"`,
  })

  // Retrieve for all sub-queries in parallel
  const subResults = await Promise.all(
    object.queries.map(q => hybridSearch(orgId, q, candidatesPerQuery))
  )

  // Deduplicate by ID and apply RRF across sub-query result sets
  const seen = new Map<string, { result: HybridSearchResult; ranks: number[] }>()

  subResults.forEach((results, queryIdx) => {
    results.forEach((result, rank) => {
      const existing = seen.get(result.id)
      if (existing) {
        existing.ranks.push(rank)
      } else {
        seen.set(result.id, { result, ranks: [rank] })
      }
    })
  })

  // Compute RRF score across sub-query ranks
  const K = 60
  const merged = Array.from(seen.values()).map(({ result, ranks }) => ({
    ...result,
    rrfScore: ranks.reduce((sum, rank) => sum + 1 / (K + rank), 0),
  }))

  return merged.sort((a, b) => b.rrfScore - a.rrfScore)
}

5. The Query Router: Choosing the Right Strategy

TYPESCRIPT
// ✅ Adaptive query router — selects transformation strategy by query type
// file: packages/db/src/retrieval/QueryRouter.ts

import { rewriteQuery } from './QueryTransformer'
import { hydeEmbed } from './HyDE'
import { stepBack } from './StepBack'
import { multiQueryRetrieve } from './MultiQueryDecomposer'
import { hybridSearch } from './HybridSearch'
import { embedAndNormalise } from '../embeddings'

export type QueryStrategy = 'direct' | 'rewrite' | 'hyde' | 'multi-query'

export async function routeAndRetrieve(
  orgId: string,
  userQuery: string,
  strategy: QueryStrategy = 'rewrite',
  limit = 50,
) {
  switch (strategy) {
    case 'direct': {
      const embedding = await embedAndNormalise(userQuery)
      return hybridSearch(orgId, userQuery, limit)
    }
    case 'rewrite': {
      const rewritten = await rewriteQuery(userQuery)
      return hybridSearch(orgId, rewritten, limit)
    }
    case 'hyde': {
      // Use HyDE embedding for dense, original query for BM25
      const hydeEmbedding = await hydeEmbed(userQuery)
      // Custom hybrid search that accepts a pre-computed query embedding
      return hybridSearch(orgId, userQuery, limit, hydeEmbedding)
    }
    case 'multi-query': {
      return multiQueryRetrieve(orgId, userQuery, Math.ceil(limit / 3))
    }
  }
}

Summary

Strategy Solves Token Cost
Query Rewrite Vocabulary gap between user language and documentation ~100 tokens (Haiku)
HyDE Query vector far from document vectors in embedding space ~300 tokens (Haiku)
Step-Back Specific implementation question needs foundational context ~100 tokens (Haiku)
Multi-Query Compound questions spanning multiple document topics ~200 + N × retrieval

What's Next

Part 14 assembles the complete context for the LLM: the Context Window Scheduler that enforces token budgets across system instructions, tool schemas, RAG evidence, and conversation history — preventing silent overflow and the "Lost-in-the-Middle" attention failure.

Research & Synthesis Note

This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.

#Query Transformation#HyDE#RAG#Node.js#LLM#Information Retrieval#Multi-Query
Siddhant Deval

Written by Siddhant Deval

Senior Full-Stack Engineer building high-scale architectures, browser performance engineering systems, and SaaS platforms.

Series·Part 13 of 24

Full-Stack AI Architecture: From Streaming Foundations to Resilient Agentic Systems in Node.js & React

Curriculum Roadmap
01The Full-Stack AI Paradigm Shift: Beyond Toy Wrappers & Prompt Concatenation
02The Anatomy of an LLM Call: Tokens, Context Windows & Probabilistic Mechanics
03Structured Outputs & Formal Invariants: Taming Non-Determinism with Zod & TypeBox
04Full-Stack Streaming Protocols: SSE, Fetch Streams & WebSockets
05V8 Streams, Server-Side Backpressure & Cancellations in Node.js
06React 18/19 Streaming UI & Generative Components
07Generative UI & Interactive Canvas Synchronization
08High-Dimensional Vector Embeddings & Similarity Math
09Vector Storage & Indexing at Scale: HNSW vs IVF in PostgreSQL (pgvector)
10Hybrid Search: Dense Vectors Meet Sparse Lexical BM25
11Multi-Stage Retrieval & Cross-Encoder Reranking
12Advanced RAG Architectures: Contextual Retrieval & Parent-Document Splitting
13Query Transformation, Decomposition & Multi-Query Expansion
Reading
14Dynamic Context Window Management & Long-Context Packing
15Agent Loops & Deterministic State Machines
16Model Context Protocol (MCP) in Full-Stack Architecture
17Human-in-the-Loop & Durable Agent Workflows
18Multi-Agent Orchestration & Collaborative Swarms
19Semantic Caching & Provider Cost Optimization
20Multi-Provider AI Gateways: Fallbacks, Rate Limiting & Hedged Requests
21Client-Side AI & Edge Hybrid Execution: WebGPU & Transformers.js
22Production Evals & Guardrails: Measuring Retrieval and Generation
23Full-Stack AI Observability & Tracing with OpenTelemetry
24Production Capstone: Assembling The Enterprise Collaborative AI Workspace