Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Query expansion voor betere RAG

Verrijk gebruikersvragen voor scherpere RAG-retrieval met HyDE, step-back prompting, query decomposition en synoniem-expansie.

Waarom query expansion werkt

Een gebruikersvraag is kort en informatie-arm. "Hoe werkt de verlofregeling?" bevat weinig semantische informatie. Een relevant document bevat woorden als "verlofaanvraag", "vakantiedagen", "HR-portaal" en "goedkeuring leidinggevende" die niet in de vraag staan.

Query expansion vergroot de semantische breedte van de zoekopdracht door de vraag te transformeren naar een representatie die dichter bij de documenten ligt.

info

Wanneer gebruiken

Query expansion is het meest waardevol als je RAG-systeem relevante documenten mist terwijl ze er wel zijn. Werkt de retrieval al goed, dan voegt het weinig toe en kost het alleen extra latentie.

HyDE: Hypothetical Document Embeddings

async function hydeRetrieval(question: string, vectorDB: VectorDB, k = 5): Promise<Document[]> {
  const hypotheticalDoc = await generateHypotheticalDocument(question);
  const embedding = await embedText(hypotheticalDoc);
  return vectorDB.search(embedding, k);
}

async function generateHypotheticalDocument(question: string): Promise<string> {
  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 300,
    messages: [{
      role: "user",
      content: `Schrijf een kort, informatief antwoord op deze vraag alsof je een expert bent.
Het antwoord hoeft niet per se correct te zijn, maar moet de juiste terminologie en structuur bevatten.

Vraag: ${question}

Antwoord:`,
    }],
  });
  return response.content[0].type === "text" ? response.content[0].text : question;
}

HyDE werkt goed als de documenten een formele schrijfstijl hebben die verschilt van hoe gebruikers vragen stellen. Het hypothetische antwoord hoeft niet feitelijk juist te zijn: je gebruikt alleen de embedding ervan, niet de inhoud.

Step-back prompting

Genereer een bredere, meer algemene vraag naast de specifieke vraag:

async function stepBackRetrieval(question: string, vectorDB: VectorDB, k = 5): Promise<Document[]> {
  const stepBackQuestion = await generateStepBackQuestion(question);

  const [specificResults, broadResults] = await Promise.all([
    vectorDB.search(await embedText(question), k),
    vectorDB.search(await embedText(stepBackQuestion), k),
  ]);

  const seen = new Set<string>();
  const combined: Document[] = [];
  [...specificResults, ...broadResults].forEach(doc => {
    if (!seen.has(doc.id)) {
      seen.add(doc.id);
      combined.push(doc);
    }
  });
  return combined.slice(0, k);
}

async function generateStepBackQuestion(question: string): Promise<string> {
  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 100,
    messages: [{
      role: "user",
      content: `Genereer een bredere, meer algemene versie van deze vraag die de achterliggende kennis aanspreekt.

Specifieke vraag: ${question}
Bredere vraag:`,
    }],
  });
  return response.content[0].type === "text" ? response.content[0].text.trim() : question;
}

Query decomposition

Splits complexe vragen in eenvoudigere deelvragen:

async function decomposeAndRetrieve(
  question: string,
  vectorDB: VectorDB,
  k = 3
): Promise<{ subQuestion: string; docs: Document[] }[]> {
  const subQuestions = await decomposeQuestion(question);

  return Promise.all(
    subQuestions.map(async (sq) => ({
      subQuestion: sq,
      docs: await vectorDB.search(await embedText(sq), k),
    }))
  );
}

async function decomposeQuestion(question: string): Promise<string[]> {
  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 300,
    messages: [{
      role: "user",
      content: `Splits deze complexe vraag in 2-4 eenvoudigere deelvragen.
Geef elke deelvraag op een nieuwe regel.
Als de vraag al eenvoudig is, geef hem ongewijzigd terug.

Vraag: ${question}

Deelvragen:`,
    }],
  });
  const text = response.content[0].type === "text" ? response.content[0].text : "";
  const parts = text
    .split("
")
    .map(l => l.replace(/^\d+\.\s*/, "").trim())
    .filter(Boolean);
  return parts.length > 0 ? parts : [question];
}

Synoniem-expansie

Voeg synoniemen en gerelateerde termen toe voor domeinspecifieke taal:

const DOMAIN_SYNONYMS: Record<string, string[]> = {
  "verlof": ["vakantie", "afwezigheid", "vrije dag", "snipperdag"],
  "ziek": ["ziekmelding", "verzuim", "arbeidsongeschikt"],
  "computer": ["laptop", "werkstation", "pc", "apparaat"],
};

function expandWithSynonyms(query: string): string {
  let expanded = query;
  for (const [term, synonyms] of Object.entries(DOMAIN_SYNONYMS)) {
    if (query.toLowerCase().includes(term)) {
      expanded += " " + synonyms.join(" ");
    }
  }
  return expanded;
}

Synoniem-expansie is goedkoop en deterministisch, maar werkt alleen als je de domeintermen vooraf kent. Voor open domeinen levert HyDE of step-back meestal meer op.

Combineren

Combineer meerdere technieken en fuseer de resultaten met Reciprocal Rank Fusion, zodat documenten die in meerdere lijsten hoog scoren naar boven komen:

async function advancedRetrieval(question: string, vectorDB: VectorDB): Promise<Document[]> {
  const expandedQuestion = expandWithSynonyms(question);

  const [hydeResults, stepBackResults, directResults] = await Promise.all([
    hydeRetrieval(question, vectorDB, 5),
    stepBackRetrieval(question, vectorDB, 5),
    vectorDB.search(await embedText(expandedQuestion), 5),
  ]);

  const allResults = [...hydeResults, ...stepBackResults, ...directResults];
  const rankings = [hydeResults, stepBackResults, directResults].map(r => r.map(d => ({ id: d.id })));
  const fused = reciprocalRankFusion(rankings);

  const docMap = new Map(allResults.map(d => [d.id, d]));
  return fused.slice(0, 8).map(r => docMap.get(r.id)!).filter(Boolean);
}
lightbulb

Begin klein en meet

Zet niet meteen alle technieken aan. Begin met HyDE als losse stap, meet de retrieval-kwaliteit met een vaste set testvragen en voeg pas een techniek toe als die meetbaar beter scoort. Anders betaal je latentie en kosten zonder aantoonbare winst.

Kosten-batenafweging

Techniek Kwaliteitswinst Extra LLM-aanroepen Latentieverhoging
HyDE Hoog 1 (klein model) 300-500ms
Step-back Gemiddeld 1 (klein model) 300-500ms
Decomposition Hoog (complexe vragen) 1 plus k retrievals 500ms tot 2s
Synoniem-expansie Laag tot gemiddeld 0 minder dan 10ms
warning

Latentie stapelt op

Elke techniek met een extra LLM-aanroep voegt honderden milliseconden toe. Draai onafhankelijke aanroepen parallel (zoals in het combineer-voorbeeld) en gebruik een snel, goedkoop model zoals claude-haiku-4-5 voor de expansiestap. Voor interactieve chat is meer dan een seconde extra al snel merkbaar.

Werkt HyDE goed voor Nederlandse content?

Ja, als je een meertalig model gebruikt voor zowel de HyDE-generatie als de embedding. Controleer of het model vloeiend Nederlands genereert voor de hypothetische documenten, anders verschuift de embedding richting de verkeerde taal.

Wanneer gebruik ik decomposition versus multi-query?

Gebruik decomposition voor vragen die meerdere feitelijke antwoorden vereisen, bijvoorbeeld "Wat zijn de beleidspunten voor X en Y?". Gebruik multi-query voor vragen die je semantisch op meerdere manieren kunt formuleren maar die naar hetzelfde antwoord wijzen.

Kan ik query expansion automatisch uitschakelen voor eenvoudige vragen?

Ja. Classificeer eerst de vraagcomplexiteit: korte feitelijke vragen krijgen geen expansion, complexe analysevragen wel. Een snelle LLM-aanroep of zelfs een eenvoudige heuristiek op lengte en vraagwoorden volstaat vaak voor die classificatie.

Verhoogt query expansion de kosten significant?

Met een snel model zoals claude-haiku-4-5 (rond 1 dollar per miljoen invoertokens en 5 dollar per miljoen uitvoertokens in 2026) blijft de expansiestap doorgaans onder de 10 procent van de totale kosten. Voor de meeste use cases rechtvaardigt de kwaliteitswinst dat.

Welk model gebruik ik het best voor de expansiestap?

Een klein, snel model is hier ideaal omdat de expansie geen perfecte feitelijkheid vereist, alleen de juiste terminologie en structuur. claude-haiku-4-5 is in 2026 het snelste Haiku-model en een logische keuze. Reserveer een groter model voor de uiteindelijke generatie van het antwoord.