Als je bouwt aan retrieval-augmented generation (RAG), kom je er snel achter dat alleen vectorzoeken niet genoeg is. Onze eerste kennisbanken zochten uitsluitend met embeddings: elke tekst wordt een reeks getallen, en de zoekvraag vindt de fragmenten die daar wiskundig het dichtst bij liggen. Dat werkt goed voor vragen in gewone taal, maar mist precies de dingen waar juristen, bouwers en zorgprofessionals vaak naar zoeken: een exact artikelnummer, een ECLI-nummer of een vaste term. In deze post laat ik zien hoe onze zoekweg nu werkt: semantisch zoeken en trefwoordzoeken naast elkaar.
De semantische illusie: waarom vectoren alleen niet volstaan
Embeddings zijn gemaakt om betekenis te vangen, niet letterlijke precisie. Ze plaatsen begrippen in een wiskundige ruimte waarin teksten over hetzelfde onderwerp dicht bij elkaar liggen. Voor een vraag als "wat zijn de regels voor opzegging van een huurcontract?" is dat precies wat je wilt.
Maar zoek je naar artikel 14, lid 2, onder b van de AVG, dan liggen lid 1, onder a en artikel 15 in die ruimte vrijwel op dezelfde plek: het is allemaal tekst over informatieplichten bij gegevensverwerking. Vectorzoeken kan je dan het bijna juiste artikel teruggeven. Voor juridisch werk is het bijna juiste artikel het verkeerde artikel. Hetzelfde geldt voor een ECLI-nummer: een advocaat die in LEO, onze juridische AI-collega, zijn eigen dossierstukken doorzoekt op één uitspraak, wil die uitspraak vinden en niet een uitspraak die erop lijkt.
Terug naar de basis: trefwoordzoeken voor letterlijke precisie
Daarom zoeken we sinds februari 2026 standaard op twee manieren tegelijk. Een kennisbank kan alleen op betekenis zoeken, alleen op trefwoorden, of hybride; hybride is sindsdien de standaard voor nieuwe kennisbanken.
Trefwoordzoeken geeft niets om de diepere betekenis van een zin. Het kijkt naar welke woorden er letterlijk in een fragment staan. Voor een exacte term zoals "7:658 BW" of een ECLI-nummer scoort het fragment met die exacte tekst hoog. Het omgekeerde probleem heeft trefwoordzoeken ook: zoekt iemand op "huur opzeggen" en staat er in het document "beëindiging van de huurovereenkomst", dan mist het die link, terwijl de semantische zoekweg hem wel vindt. Juist daarom combineren we de twee.
Zo werkt het bij ons
Elke kennisbank wordt bij het inlezen opgeknipt in fragmenten. Die fragmenten gaan naar twee plekken:
- Semantische zoekweg. De fragmenten krijgen een embedding van het model
gemini-embedding-001, via Google Vertex AI in de regio europe-west4, en worden opgeslagen in Weaviate. Een zoekvraag wordt op dezelfde manier omgezet en vindt de fragmenten die er inhoudelijk het meest op lijken. - Trefwoordzoekweg. Dezelfde fragmenten staan in een trefwoordindex in Meilisearch, op onze eigen servers. In onze code heet dat de BM25-zoekweg. Hij zoekt op de letterlijke woorden van de vraag.
Bij een hybride kennisbank lopen beide zoekwegen voor elke vraag. We voegen de resultaten samen zonder ingewikkelde score-fusie: eerst de trefwoordtreffers, daarna de semantische, en een fragment dat in beide lijsten staat telt één keer. Een aparte herrangschikking met een extra model (een reranker) gebruiken we niet.
Eén document mag niet alles opslokken
In de praktijk zagen we dat één document dat inhoudelijk sterk op de vraag leek, alle plaatsen in de resultaten kon bezetten. Een tweede document dat ook relevant was, kwam dan helemaal niet meer in beeld.
Sinds augustus 2026 halen we daarom eerst meer kandidaten op dan we nodig hebben, en kiezen we daaruit met een plafond per document: één document krijgt hooguit de helft van de plaatsen. Blijven er plaatsen over, dan vullen we die met de beste overgeslagen kandidaten. Een vraag over één document verliest daardoor niets, en een vraag over meerdere documenten laat ze ook allemaal zien.
Een vast budget voor het taalmodel
Je kunt niet onbeperkt fragmenten in de prompt van een taalmodel stoppen. Ook modellen met een groot contextvenster gebruiken informatie in het midden van een lange prompt slechter dan informatie aan het begin of het eind; dat staat bekend als "lost in the middle". Daarom krijgt het taalmodel bij ons een vast maximum aan fragmenten mee. Rond elk gevonden fragment kunnen we de directe buurfragmenten uit hetzelfde document toevoegen, zodat een passage niet midden in een zin begint, maar binnen dat budget.
Als één zoekweg hapert
Valt de semantische zoekweg uit, bijvoorbeeld omdat de embeddingdienst tijdelijk niet antwoordt, dan geeft een hybride kennisbank toch antwoord met de trefwoordtreffers in plaats van de hele vraag te laten mislukken.
Retrieval levert de kennis; hoe dat samenwerkt met MCP, waarmee een agent acties uitvoert, lees je in Voorbij de buzzwords: MCP en RAG.
Stop met het blindelings implementeren van de standaard RAG-tutorials. Zoek op betekenis én op de letter.
Bronnen
- Liu e.a., Lost in the Middle: How Language Models Use Long Contexts (arXiv 2307.03172)
- Weaviate, Google embeddings (text2vec-google)
- Meilisearch, Relevancy
Wil je sparren over retrieval-augmented generation? Neem contact op.
