Wat een RAG-chatbot in productie moet kunnen
Deze gids is voor teams met een RAG-chatbot die op een laptop werkt en die nu voor een hele organisatie moet werken. Retrieval-augmented generation (RAG) beantwoordt vragen vanuit je eigen documenten. Het systeem zoekt de relevante passages op, geeft ze aan een taalmodel en vraagt om een antwoord dat op die passages is gebaseerd. Een eerste versie die een handvol vragen beantwoordt, bouw je in een middag. Een versie die duizenden vragen per week beantwoordt, uit documenten die veranderen en voor gebruikers met verschillende rechten, is een engineeringproject.
In productie moet een RAG-chatbot:
- De juiste passage vinden bij de vraag, ook bij exacte termen zoals productcodes, clausulenummers en namen.
- Zijn bronnen noemen, zodat een gebruiker het antwoord binnen een paar seconden kan controleren.
- Rechten respecteren, zodat niemand een antwoord ziet dat is gebaseerd op een document dat hij zelf niet mag openen.
- Zeggen wanneer hij het antwoord niet weet, en gaten niet opvullen met tekst die aannemelijk klinkt.
- Aantoonbaar goed blijven terwijl documenten, prompts en modellen veranderen.
De rest van deze gids behandelt elke laag in de volgorde waarin we ze bouwen.
Documenten inlezen: hier beginnen de meeste kwaliteitsproblemen
Retrieval (het ophalen van passages) kan alleen teruggeven wat bij het inlezen uit de documenten is gehaald. Een eenvoudige tekstextractor maakt rommelige tekst van PDF's met twee kolommen, gescande pagina's, tabellen die over meerdere pagina's doorlopen en kopteksten die op elke pagina terugkomen. Het model antwoordt dan vanuit fragmenten, en geen enkele aanpassing aan de prompt lost dat op.
Een goede pipeline voor het inlezen doet vier dingen goed:
- 01Parse per documenttype. Gebruik voor PDF's een parser die de lay-out begrijpt, bewaar de structuur van tabellen als Markdown of HTML en gebruik OCR of een vision-model alleen voor pagina's die dat nodig hebben.
- 02Haal standaardtekst weg. Verwijder herhaalde kop- en voetteksten, paginanummers en disclaimers, die anders bij elke zoekvraag een match geven.
- 03Bewaar metadata. Sla de bron-URI, titel, het sectiepad, de versie, datum en taal op, en de toegangsgroepen die het document mogen lezen.
- 04Synchroniseer incrementeel. Bereken per document een hash, indexeer alleen opnieuw wat is gewijzigd en verwijder chunks als de bron verdwijnt. Verouderde chunks zijn een veelvoorkomende oorzaak van antwoorden die stellig klinken en toch fout zijn.
Behandel het inlezen als een pipeline met logs en retries, die volgens een schema draait of reageert op wijzigingen in het bronsysteem. Voor rommelige input zoals facturen en formulieren lees je onze gids over documenten parsen met LLM's.
Strategieën voor chunking die standhouden
Chunking bepaalt hoe één vindbaar stuk tekst (een chunk) eruitziet. Is een chunk te klein, dan verliest hij de context die hem betekenis geeft. Is hij te groot, dan wordt de embedding (de numerieke weergave van de betekenis) vager en vult het contextvenster zich met tekst die er niet toe doet.
| Strategie | Hoe het werkt | Geschikt voor |
|---|---|---|
| Vaste grootte met overlap | Splits elke N tokens, met 10 tot 20% overlap | Gelijkmatige lopende tekst, en als snel startpunt |
| Op structuur | Splits op koppen, secties, lijstitems en tabelgrenzen | Beleid, handleidingen, contracten, documentatie |
| Parent-child | Haal kleine chunks op en geef het model hun grotere bovenliggende sectie | Lange documenten waarin zowel detail als context telt |
| Contextuele koppen | Zet voor het embedden de documenttitel en het sectiepad voor elke chunk | Collecties met veel vergelijkbare documenten |
Meestal beginnen we met chunks op structuur van ongeveer 300 tot 800 tokens. Elke chunk krijgt de titel en het sectiepad ervoor, en we bewaren een verwijzing naar de bovenliggende sectie. Daarna laten we de testset beslissen. Chunkgrootte is een parameter die je meet, en de beste instelling voor een contractarchief is zelden de beste voor een kennisbank van de klantenservice.
Hybride zoeken en reranking
Vectorzoeken is goed in betekenis: een vraag over "een contract vroegtijdig beëindigen" vindt een clausule met de titel "opzegging zonder opgave van redenen". Het is zwakker bij exacte tokens zoals ISO 27001, een artikelnummer of een SKU. Zoeken op trefwoord met BM25 heeft het omgekeerde profiel. Hybride zoeken voert beide uit en voegt de resultaten samen. Daarom is het onze standaard voor zakelijke documenten.
Staat je data al in Postgres, dan kun je dit doen met pgvector en de ingebouwde full-text search, samengevoegd met reciprocal rank fusion (RRF). De full-text ranking van Postgres lijkt in opzet op BM25. Is exacte BM25-scoring belangrijk, zet dan een BM25-extensie of een zoekmachine naast de database.
Let op: de filters voor tenant en rechten staan in beide zoekopdrachten. Filter je pas na het ophalen, dan houd je soms te weinig resultaten over. Bovendien gaat afgeschermde tekst dan door code die die tekst nooit had mogen zien.
Daarna scoort een reranker de beste 20 tot 50 kandidaten tegen de vraag, met een cross-encoder of een LLM, en houdt de beste vijf tot tien over. Reranking (opnieuw rangschikken) is vaak de goedkoopste grote verbetering in antwoordkwaliteit. Je betaalt er wat extra latency voor.
Bronnen controleren en toegang afschermen
Vraag het model om bij elke bewering chunk-ID's als bron te noemen. Controleer die bronnen daarna in code, voordat het antwoord de gebruiker bereikt:
- Elk genoemd ID moet horen bij een chunk die je echt aan het model hebt meegegeven.
- Geciteerde tekst moet in de genoemde chunk staan, nadat je de witruimte hebt genormaliseerd.
- Vervang een antwoord zonder bronnen door een duidelijk "Ik kon dit niet vinden in de documenten", en log de zaak zodat iemand hem kan nakijken.
Deze controles zijn deterministisch en goedkoop, en ze vangen een groot deel van de verzonnen verwijzingen af. Wil je grondiger controleren of een antwoord echt door de bronnen wordt gedragen, lees dan hoe je RAG evalueert.
Toegangsbeheer hoort in de retrieval thuis. Sla bij het inlezen op elke chunk de toegangsgroepen op, houd ze gelijk met het bronsysteem en geef bij elke zoekopdracht de groepen van de gebruiker mee. Vertrouw niet op de prompt om afgeschermde inhoud te verbergen.
Evalueer retrieval en antwoorden apart
Een RAG-chatbot kan op twee plekken fout gaan: de retrieval mist de juiste passage, of het model gebruikt een passage die het wel kreeg verkeerd. Meet allebei. Stel een set samen van 100 tot 300 echte vragen met de documenten die ze beantwoorden, en volg:
- Retrieval: recall@k en nDCG. Die laten zien of de juiste chunks bij de bovenste resultaten zitten.
- Antwoorden: juistheid ten opzichte van een referentieantwoord, trouw aan de opgehaalde bronnen en de nauwkeurigheid van de bronvermelding.
- Weigeringen: of de chatbot vragen afwijst die de documenten niet kunnen beantwoorden.
- Beheer: p95-latency en kosten per gesprek.
Draai de set bij elke wijziging in chunking, prompts, modellen of rerankers, en houd releases tegen die slechter scoren. Ons artikel over LLM-evaluaties voor elke release gaat dieper in op die releasecontrole.
Waar je begint
- 01Verzamel 100 echte vragen van de mensen die de chatbot gaan gebruiken, met de documenten die ze beantwoorden.
- 02Bouw het inlezen voor je twee of drie belangrijkste bronnen, met metadata en rechten.
- 03Begin met chunks op structuur en hybride zoeken, en meet recall@10.
- 04Voeg een reranker en broncontroles toe, en meet opnieuw.
- 05Zet de testset in CI voordat je meer bronnen toevoegt.
- 06Ga live met één team, bekijk elke week een steekproef van de gesprekken en voeg fouten toe aan de set.
Dit is de volgorde die we volgen als we RAG-systemen voor klanten bouwen, van het inlezen tot het beheer in productie. Onze RAG-case voor een advocatenkantoor laat het resultaat zien, en onze aanpak legt uit hoe we zulke projecten uitvoeren.