← Blog

RAG

Citation-first RAG: playbook voor legacy ERP-retail

Een baliemoment in Antwerpen. Een installateur belt. Een ketelhandleiding uit 2011 die niemand leest. Twee jaar geleden zes minuten. Vandaag vier seconden, met bronvermelding.

Jacob Molkenboer· Oprichter · A Brand New Company· 16 jun 2026· 9 min
Open leren handboek op ivoren bureau, messing lint over pagina, groene tab, ledgerkaarten met twijn, wasfragment.

Het is 10:47 op een dinsdag in Antwerpen. Een verkoper aan de balie heeft een installateur aan de lijn. Diens vraag: accepteert de Atag Q38S-ketel een concentrische rookgasafvoer 100/150, of moet de oudere 80/125 erop? De handleiding is een PDF van 47 pagina's uit 2011, in het Nederlands, ingescand op 200 dpi, weggestopt op een leveranciersextranet achter een login die om de twintig minuten verloopt. Twee jaar geleden kostte die vraag zes minuten en een collega. Vandaag typt de verkoper hem in een chatvenster en heeft hij binnen vier seconden antwoord, met een link naar pagina 23 van de PDF.

De retailer telt 33 mensen, 38.000 OEM-handleidingen verdeeld over ongeveer 180 leveranciers, een Navision-ERP die voor het laatst in 2010 is bijgewerkt, en een verkoopteam dat vorige week alleen al 1.420 van dit soort vragen heeft afgehandeld. Zo bouwden we de citation-first RAG-agent die die vragen beantwoordt, plus de regel die voorkwam dat we onderweg de rest van het bedrijf sloopten.

De regel komt voor de architectuur

Elke passage die op het scherm van een verkoper belandt, moet verwijzen naar een leverancier-PDF die de operations lead persoonlijk heeft goedgekeurd. Stuk. Voor. Stuk. Kan het systeem niet citeren, dan geeft het geen antwoord. Het zegt 'ik heb hiervoor geen geverifieerde bron' en stuurt de vraag naar de wachtrij voor mensen.

Dit is geen vangrail die je er achteraf aan vastschroeft. Dit is de dragende muur. De verleiding, als je RAG bouwt bovenop een ERP en een corpus van 38.000 bestanden, is om het model kennis te laten 'mixen': een beetje catalogusdata, een beetje handleiding, een zin redenering, een zelfverzekerd antwoord. Zo lever je een systeem op dat zes weken later tegen een installateur zegt dat 100/150 wordt ondersteund op een ketel die het op pagina 24 expliciet verbiedt. De installateur monteert hem. Er lekt iets. Je telefoon gaat.

Waarschuwing

Een agent die niet kan aanwijzen welke paragraaf hij parafraseert, mag die parafrase nooit wegschrijven naar een productpagina, een pickinstructie of een offerte-PDF. Eerst read-only. Altijd.

Het corpus komt voor het model

De eerste drie weken zaten we op de PDF's, niet op het LLM. Van de 38.000 bestanden waren er ongeveer 11.000 ingescand (geen embedded tekst), zo'n 2.800 met een wachtwoord per leverancier dat niemand had vastgelegd, en ongeveer 600 corrupt of 0-byte placeholders uit een allang dood migratiescript.

We deden drie dingen, in deze volgorde.

Eerst: we hebben elke PDF gehasht en gededupliceerd. Het corpus kromp tot 24.100 unieke bestanden. De andere 14.000 waren hernoemde kopieën, dezelfde handleiding die onder drie verschillende leveranciersmappen stond omdat twee leveranciers door een derde waren overgenomen.

Daarna: we hebben de scans door een layout-bewuste OCR-stap gehaald. Geen generieke Tesseract-run. We gebruikten een layout-bewuste parser die tabellen intact hield, want de meeste verkoper-vragen draaien om een getal in een tabel: doorlaatdiameter, kW-vermogen, NEN-klasse, compatibele onderdeel-SKU. Een platte tekstdump verliest de rij-kolom-relatie, en de agent gaat dan hallucineren welk kW-getal bij welk model hoort.

Tot slot: we bouwden een verificatie-workflow voor de operations lead. Elke leveranciersmap kreeg een review van één pagina: is dit de huidige revisie, zijn deze PDF's de autoritatieve bron, zijn er vervangen handleidingen die in quarantaine moeten. Ze verifieerde 180 leveranciers in elf middagen. We hebben elke PDF getagd met vetted_at, vetted_by en revision. Zonder die drie velden komt niets de retrieval-index in.

Chunking die het document respecteert

Het standaard RAG-advies (splits op 500 tokens, overlap 50) klopt niet voor technische handleidingen. Een tabel van 14 rijen wordt doormidden gehakt. Een waarschuwingskader 'niet gebruiken in combinatie met…' raakt losgekoppeld van het modelnummer waar het bij hoort. Het model haalt vervolgens de waarschuwing op zonder de context die ze triggert.

We knipten op layout-block, daarna op semantische sectie, en pas bij lange lopende tekst vielen we terug op een tokencount. Elke chunk draagt de sectiekop mee, het paginanummer, het tabelbijschrift als hij in een tabel staat, en de PDF-revisie.

def chunk_block(block, doc):
    return {
        "text": block.text,
        "doc_id": doc.id,
        "page": block.page,
        "section": block.nearest_heading(),
        "table_caption": block.table_caption,  # None if not a table
        "bbox": block.bbox,                    # for the citation viewer
        "revision": doc.revision,
        "vetted_at": doc.vetted_at,
        "supplier": doc.supplier,
    }

De bbox doet meer dan je zou denken. Klikt de verkoper op de bronvermelding, dan openen we de PDF op die pagina met de exacte bounding box gemarkeerd. Hij ziet de paragraaf, niet een vage 'pagina 23'. Die ene feature deed meer voor het vertrouwen dan welke andere keuze ook.

Retrieval die met opzet saai is

Hybride zoeken. BM25 plus een vector-index plus een re-ranker. Niets exotisch.

Het interessante zit in de filterketen. Voordat het model ook maar één chunk te zien krijgt, filteren we op:

  • Leverancier-scope. De vraag van de verkoper noemt een Atag-ketel; we willen geen chunks uit Vaillant-handleidingen in de top-k.
  • Recentheid van de revisie. Heeft een leverancier een revisie uit 2023 van dezelfde handleiding, dan valt de revisie uit 2011 weg, tenzij de verkoper expliciet om legacy voorraad vraagt.
  • Verificatiestatus. Geen vetted_at, geen retrieval, geen uitzonderingen.

Leverancier-scope klinkt triviaal. Dat is het niet. De Navision-SKU verwijst vaak naar een 'huismerk' dat eigenlijk een rebrand is van een echte OEM. Een SKU die met KV- begint is een Karver, maar Karver is Atag in een blauw doosje. We hebben de mapping van SKU naar de echte OEM opgebouwd door de Navision-inkoophistorie te parsen, niet de productmaster, want die productmaster was te vaak met de hand bijgewerkt om nog te vertrouwen.

De Navision-brug die niet schrijft

Navision 2009 is niet aardig. Een schone API ontbreekt. De integratieroute die de agent gebruikt is een read-replica van drie tabellen, elke nacht geëxporteerd naar Postgres: Item, Item Variant en de laatste 36 maanden Sales Line. Dat is genoeg om vragen te beantwoorden als 'hebben we dit onderdeel vorig jaar aan deze installateur verkocht, en voor welke prijs'.

De agent leest. De agent schrijft niet. Geen enkele kolom. De verleiding, als je een werkende RAG-laag hebt die de catalogus tot in detail kent, is om die productpagina-updates te laten voorstellen of ontbrekende OEM-specs op de productpagina te laten invullen. Wij weigerden, om twee redenen.

Eén: de productpagina staat downstream van Navision in deze shop. Alles wat we daar zouden schrijven, wordt door de volgende nachtelijke sync overschreven. Twee: zodra een agent naar de catalogus kan schrijven, wordt elke retrieval-bug een data-integriteitsbug. We houden het lees-pad en het schrijf-pad aan weerszijden van een hek, en dat hek wordt op netwerkniveau afgedwongen. De retrieval-service heeft geen credentials voor de Navision-schrijf-API. Punt.

Het WMS dat de agent nooit aanraakt

Zelfde regel, lastiger toepassen. Het magazijn draait een pickinstructie-wachtrij die gevoed wordt door Navision-orders. Een verkoper vroeg, terecht, of de agent automatisch een pickernotitie kon maken bij een complexe order: 'denk aan de pakkingset, de handleiding zegt dat die los wordt verzonden'.

Nee.

De picker leest wat het systeem hem voorlegt. Genereert de agent die tekst en heeft hij het mis, dan kan de picker dat niet vangen, want hij is de domeinexpert niet. Dus bouwden we het omgekeerde: de agent markeert de order voor de verkoper, die de bronvermelding in de handleiding leest en zelf de pickernotitie in Navision schrijft, zoals altijd. De agent scheelt de verkoper vier minuten per gemarkeerde order. Hij slaat de mens niet over.

De evalset die ops bouwde, niet wij

De operations lead en twee senior verkopers schreven de evalset. Driehonderdtwaalf vragen, elk met een ground-truth-antwoord en een ground-truth-paginaverwijzing. Bij elke serieuze wijziging draaiden we de agent tegen die set. Het cijfer waar we naar keken was niet 'antwoordcorrectheid' in het abstracte. Het ging om citatieprecisie: als de agent verwees naar pagina 23 van handleiding X, stond het antwoord daar dan ook?

Beginpunt: 71% citatieprecisie. In zes weken duwden we dat naar 94%. De resterende 6% zijn eerlijke ambiguïteiten (het antwoord staat op pagina 23 en 24, de agent koos 24, de evalset houdt vol dat 23 juist is), en we stopten met daarachteraan jagen omdat de verkopers vonden dat het systeem al sneller was dan zij.

Wat brak

Wat ons verraste: taal. De PDF's zijn in Nederlands, Frans, Duits, Engels en af en toe Italiaans. Verkopers stellen hun vraag in het Nederlands, maar citeren de klant in welke taal die klant ook gebruikte. Onze eerste re-ranker was op het Engels afgesteld en duwde de Nederlandse chunks stilletjes naar achteren zodra een vraag een Engelse merknaam bevatte. Citatieprecisie zakte van de ene op de andere dag met 11 punten toen we van merk wisselden. We gingen over op een meertalige re-ranker en plakten er een querytaal-detector op die chunks in de werktaal van de verkoper boost. Opgelost.

De tweede verrassing waren de leveranciers-extranetten. Drie grote leveranciers werken hun PDF's bij zonder de bestandsnaam te wijzigen. Onze nachtelijke sync was tevreden: zelfde hash, niets te doen. Behalve dat de hash wél veranderd was omdat de inhoud was veranderd, en onze hashfunctie ook een tijdstempel uit de header meenam die de leverancier had ingesloten. We zijn over op alleen-inhoud-hashing (eerst metadata strippen, dan hashen) en vingen 340 stiekem bijgewerkte handleidingen in de eerste week.

Het kleinste wat je vandaag kunt doen

Heb je een corpus aan leveranciers-PDF's en een verkoopteam dat elke week dezelfde vragen opnieuw beantwoordt? Begin dan niet bij het model. Begin bij je operations lead en een spreadsheet. Lijst elke leverancier op. Vink degenen aan wiens handleidingen de autoritatieve bron zijn. Zet de rest in quarantaine. Die spreadsheet, geverifieerd door iemand die het bedrijf kent, is het meest waardevolle artefact in een citation-first RAG-systeem. Embeddings zijn commodity. Verificatie niet.

Toen we deze agent voor de retailer in Antwerpen bouwden, was het ding waar we steeds tegenaan liepen het gat tussen 'het LLM gaf een plausibel antwoord' en 'de verkoper kan dat antwoord verdedigen tegenover een installateur aan de telefoon'. We losten het op door elk antwoord te weigeren dat niet naar een geverifieerde pagina kon wijzen, en door de schrijf-paden naar Navision en het WMS volledig dicht te houden. Dezelfde discipline loopt door de rest van ons werk rond AI-agents.

Kern

Kan de agent niet de exacte geverifieerde PDF-pagina aanwijzen, dan geeft hij geen antwoord. Het lees-pad en het schrijf-pad blijven aan weerszijden van een hek dat op netwerkniveau wordt afgedwongen.

FAQ

Waarom een bronvermelding eisen bij elk antwoord?

Omdat de verkoper het antwoord moet verdedigen tegenover een installateur aan de telefoon. Zonder een geverifieerde pagina om naar te wijzen, weet je niet op tijd of een zelfverzekerd model gewoon mis zit.

Kan de agent productpagina's of pickernotities in het WMS automatisch bijwerken?

Nee. De agent leest alleen. De schrijf-paden naar Navision en het WMS hebben op netwerkniveau geen credentials. Mensen schrijven nog steeds, de agent scheelt ze opzoektijd.

Hoe groot moet de evalset zijn?

De onze telt 312 vragen, geschreven door de operations lead en twee senior verkopers. De omvang doet er minder toe dan wie ze schrijft. Door ops gebouwde evalsets vangen fouten die synthetische sets missen.

En ingescande of wachtwoord-beveiligde PDF's?

Haal ze door een layout-bewuste OCR-stap, zodat tabellen overleven. Documenteer voor wachtwoorden de credentials per leverancier één keer en zet ze in een secret manager. Alles wat corrupt is gaat in quarantaine.

Heb je voor 38.000 PDF's een vector database nodig?

Postgres met pgvector trok dit corpus prima. De bottleneck was nooit vector-recall. Het was chunking-kwaliteit, filtering op leverancier-scope en de meertalige re-ranker.

ragknowledge baseai agentsarchitectureintegrationsoperations

Iets bouwen?

Start een project