← Blog

RAG

RAG voor veldmonteurs: het Roermondse citatie-playbook

Het is dinsdag, 11:00 uur in Venlo. Een monteur staat aan de voet van een Demag bovenloopkraan van 22 ton, foutcode E-407 op haar tablet, negen minuten tot de volgende ploegwisseling.

Jacob Molkenboer· Oprichter · A Brand New Company· 20 mei 2026· 10 min
Open eiken kaartenbak op ivoren bureau, één crème kaart omhoog bij messing tab met groen lint, leren logboek en messing sleutel ernaast.

Het is een dinsdag in maart, net na 11:00. Een monteur — noem haar Saskia — staat in een distributiehal in Venlo aan de voet van een Demag bovenloopkraan van 22 ton. Op haar tablet staat foutcode E-407 op de DC-Pro controller. Ze heeft negen minuten voordat het volgende pick-venster opengaat. De groothandel in Roermond waar ze in dienst is heeft 47.200 EPLAN-schema's, een Trimergo T2-servicearchief dat sinds 2015 alleen maar groeit, en ergens daarin staat de precieze paragraaf uit NEN 3140 die ze nu nodig heeft over werken aan een spanningvoerend B-circuit voordat de lockout in gaat.

Tot vorig jaar belde ze het kantoor. Maaike of Bram van de technische dienst gingen er een kwartier in zitten zoeken, soms langer. Nu typt ze haar vraag in een Telegram-bot — een retrieval-gegronde RAG-agent die we vorig voorjaar opleverden — krijgt binnen acht seconden een antwoord, en bij elke passage op het scherm zit een klikbare link terug naar de bronparagraaf. De bot zat de afgelopen 90 dagen twee keer fout. Beide keren weigerde hij te antwoorden — dat is de enige soort fout waar we mee kunnen leven.

Zo hebben we het gebouwd.

Het corpus dat we erbij kregen

De groothandel telt 24 mensen, draait zo'n €11M omzet en bedient ongeveer 380 industriële locaties in Limburg en Noord-Brabant. Ze gaven ons drie stapels mee.

  • 47.200 EPLAN-schema's, geëxporteerd als PDF/A uit EPLAN Electric P8, teruggaand tot 2011. De meeste hebben OCR'de tekstlagen; ongeveer 6% zijn gescande herdrukken zonder bruikbare tekst.
  • 12.800 IEC 60204- en NEN 3140-clausules, uit gelicenseerde NEN Connect XML-exports, opgedeeld in artikel + sub-artikel + opmerking-blokken.
  • ~218.000 servicenotities uit Trimergo T2, het ERP/service-systeem dat het bedrijf sinds 2015 draait. Elke notitie is een vrijetekst-monteurverslag plus een gestructureerde foutcode, machine-ID en klant-ID.

En één harde regel van de directie: geen antwoord verlaat het systeem zonder een citaat dat een monteur ter plekke kan verifiëren. Wil het model zeggen "sluit de B-rail uit en overbrug het veiligheidsrelais", dan moet het verwijzen naar de paragraaf die dat zei. Geen paragraaf, geen antwoord.

Waarom retrieval en geen fine-tune

Deze vraag krijgen we elke maand. Waarom geen model fine-tunen op het corpus en de retrieval-laag overslaan? Drie redenen waar we steeds op terugkomen.

Eén: het corpus verandert. Wekelijks landen er bulletins van fabrikanten. NEN herziet clausules op haar eigen cyclus. Een fine-tune bevriest een moment; retrieval leest wat er vanavond staat.

Twee: citaties zijn hier geen UI-feature. Ze zijn een wettelijke vereiste. NEN 3140 verplicht dat de elektrotechnisch verantwoordelijke kan aantonen welke clausule een werkprocedure rechtvaardigt. Een fine-tuned model kan een clausule uit zijn hoofd citeren. Het kan niet bewijzen dat dat citaat uit jouw gelicenseerde kopie komt.

Drie — en dit is degene die niemand wil horen — fine-tuned modellen hallucineren met overtuiging. Met retrieval kappen we het antwoord af op de passages die we vonden. Vonden we niets, dan zegt de agent dat.

Kernpunt

Bij werk met zware consequenties is de vraag niet "hoe maken we het model slimmer?" maar "hoe zorgen we dat het niet kan antwoorden zonder bewijs?"

De ingestion-pipeline

De pipeline draait elke nacht op één Hetzner AX52. De eerste cold ingest van alle drie de corpora kostte 4 dagen en 11 uur. Incrementele nachtelijke runs zijn in 23 minuten klaar.

De vorm, in pseudocode:

# ingest.py — runs nightly, idempotent on content hash
import hashlib

def ingest(source):
    written = 0
    for doc in source.iter_documents():
        h = hashlib.sha256(doc.bytes).hexdigest()
        if db.exists(content_hash=h):
            continue
        blocks = segment(doc)              # source-specific
        for block in blocks:
            block.embed = embed_model(block.text)
            block.citation = build_citation(doc, block)
            db.upsert(block)
            written += 1
    return written

Het interessante werk zit in segment en build_citation, en dat verschilt per bron.

EPLAN-schema's

EPLAN-PDF's zijn tabellarisch en dichtbedrukt. Naïef per pagina chunken vernietigt de context. We parsen elk schema met een extractor per sjabloon — Demag, Siemens S7-1500, Wago 750, Phoenix Contact, enzovoort — die het titelblok en de functiegroep-labels herkent (=K01+S04-Q12-stijl). Elke functiegroep wordt een blok. Het citaat is het schema-ID plus het functiegroep-adres, dat monteurs sowieso al hardop kunnen voorlezen.

NEN- en IEC-clausules

We hebben de NEN Connect XML-exports gelicenseerd. Geen kleine papierwinkel; reken op twee maanden. Elke clausule wordt een blok met het volledige hiërarchische pad als citaat: NEN 3140:2018 §6.3.2 b. Clausules vatten we niet samen. We slaan ze woordelijk op.

Trimergo T2-servicenotities

Dit is de rommelige stapel. Elf jaar monteurverslagen, geschreven in drie regionale Nederlandse dialecten, met afkortingen als kk vk (kortsluiting vermogensschakelaar) die niemand buiten het bedrijf snapt. We bouwden een woordenlijst van 412 termen die de indexer toepast vóór het embedden, en we houden de originele woordelijke tekst bewust aan als citaat, zodat de ontvangende monteur de woorden ziet die zijn collega in 2017 daadwerkelijk opschreef.

Waarschuwing

Heeft jouw corpus intern jargon, bouw die woordenlijst dan voor je embedt, niet erna. 218k notities opnieuw embedden omdat je drie weken later "kk vk" ontdekt, is een rotdag.

De vorm van de retrieval

Eén vector store is niet genoeg. We draaien hybride retrieval — BM25 plus dense embeddings in Qdrant — en we routeren eerst op intent.

Een kleine classifier (DistilBERT, fine-tuned op 1.800 handgelabelde monteurvragen) tagt elke query met één of meer van: safety, schematic, history, procedure. Elke tag waaiert uit naar een eigen index met eigen k en eigen rerank-drempel. Een safety-query zoekt eerst in NEN/IEC en raadpleegt Trimergo alleen als de safety-pass niets opleverde. Een history-query ("hebben we deze E-407 op deze kraan al eerder gehad?") gaat direct naar Trimergo, gefilterd op machine-ID.

De reranker is een Cohere Rerank 3-call over de top-50 kandidaten. We houden de top 6 boven een drempel van 0,42. Komt niets boven de 0,42 uit, dan weigert de agent. Die drempel was het getal dat verreweg de meeste impact had bij het tunen — een halve punt lager en de citatieprecisie stort in.

Het contract voor onderbouwde antwoorden

Dit is het stuk waar niemand over schrijft, en precies het stuk dat het verschil maakt.

We laten de LLM geen vrij antwoord schrijven met citaties erbij gestrooid. We laten 'm een contract invullen. De prompt naar het antwoordmodel (standaard Claude Sonnet 4.5, met een self-hosted Llama 3.1 70B als fallback wanneer we tegen rate limits aanlopen) ziet er grofweg zo uit:

You are answering a question from a field monteur.

You have been given N passages, each with an ID and a citation.
You may ONLY use facts that appear in these passages.

Return JSON of this shape:
{
  "answer_nl": "...",          // Dutch, <=120 words, imperative voice
  "steps": [                    // ordered procedure if applicable
    { "do": "...", "from": "P3" }
  ],
  "citations": ["P1","P3","P4"],
  "refuse": false,              // true if passages are insufficient
  "refuse_reason": null
}

If any step would normally require a citation and you cannot find one
in the passages, set refuse=true and explain what is missing.

Elke stap verwijst naar een passage-ID. De frontend rendert de stappen met de passagetekst die eronder openvouwt. Een monteur kan het antwoord lezen en in één beweging op de bron tikken. Staat refuse op true, dan vertelt de bot haar precies welk soort bron hij niet kon vinden en routeert de vraag naar wie op dat moment op kantoor dienst heeft.

Evaluatie: de gold set van 312 vragen

We gingen twee middagen lang zitten met de drie senior monteurs en bouwden een gold set van 312 vragen die de long tail dekt. Bij elke vraag hoort een bekend-correct antwoord, het citaat dat dat antwoord rechtvaardigt, en een bekend-correcte weigering voor de gevallen waarin het corpus het antwoord echt niet bevat.

We draaien de gold set bij elke wijziging in de pipeline. De metrics die we volgen:

  • Citatieprecisie. Van de citaties die de agent eruit gooit, welk deel bevat de claim daadwerkelijk. Nu: 0,97.
  • Weigeringsrecall. Als het corpus geen antwoord ondersteunt, weigert de agent dan? Nu: 0,99. Op deze metric leveren we niet in.
  • Bruikbaarheid van het antwoord. Een mens scoort het antwoord op een schaal van 1–5. Gemiddelde nu: 4,3, was 3,6 bij oplevering.

We kijken niet naar BLEU of ROUGE. We kijken niet naar perplexity. Een monteur wil weten of het antwoord klopt en of ze het kan controleren. Verder niets.

Waarom begrensd het wint van onbegrensd

Twee winters geleden hield een small-claims tribunal in British Columbia Air Canada aansprakelijk voor een rouwtariefbeleid dat zijn chatbot uit het niets had verzonnen. De vervoerder voerde aan dat de bot een aparte juridische entiteit was. Het tribunaal vond van niet. De zaak wordt steeds opnieuw aangehaald omdat de failure mode universeel is: een zelfverzekerd gegenereerd antwoord, geen bron die de klant kon nakijken, en een bedrijf dat opdraait voor wat het model ook maar zei.

Zo denken wij ook over tooling voor het veld. Een monteur aan de voet van een hijskraan is geen recensent van taalmodellen. Zij wil dat het antwoord klopt of dat het eerlijk ontbreekt. Onbegrensde generatie kan dat niet beloven. Een retrieval-pipeline met een hard citatiecontract wel.

De les is in beide contexten dezelfde: waar de kosten van een zelfverzekerd fout antwoord hoog zijn, moet het systeem ook niets kunnen zeggen.

Wat we anders zouden doen

Twee dingen, achteraf.

Eén: we zouden de woordenlijst bouwen voordat we het embedding-model aanraakten. We hebben in week drie 218k Trimergo-notities opnieuw moeten embedden nadat we ontdekten dat de reranker kk vk-entries op vrijwel nul scoorde, omdat het embedding-model geen idee had wat de afkorting betekende. Duur in euro's, duurder in doorlooptijd.

Twee: we zouden de weigerings-UX op dag één hebben uitgerold. We zijn live gegaan met een flow die zei "Ik heb te weinig informatie." Monteurs zagen dat als een bug en stopten met de bot. De huidige UX zegt precies welk soort bron ontbreekt — "Ik heb wel een fabrikantenbulletin maar geen NEN-clausule die stap 3 ondersteunt" — en routeert de vraag mét context naar een mens. Adoptie sprong in acht dagen van 41% naar 78% van de wekelijkse monteur-vragen.

Toen we de monteur-agent bouwden voor de groothandel in Roermond, liepen we steeds tegen hetzelfde aan: "goed genoeg" en "verifieerbaar" zijn verschillende problemen, en de meeste teams leveren alleen het eerste op. Het tweede oplossen is het AI-agent-werk dat wij doen. Als jouw veldmensen nog steeds het kantoor bellen voor procedurecitaten, dan heb je de dataset die je nodig hebt al in huis.

Open vanavond je servicearchief. Trek vijftig willekeurige notities eruit. Kan een monteur in het veld op basis van die notities alleen vaststellen dat de procedure erin de huidige is? Dat is de schaal van het probleem dat je oplost.

Kern

Waar de kosten van een zelfverzekerd fout antwoord hoog zijn, moet het systeem niets mogen zeggen — en elk woord dat het wél zegt, moet het kunnen bewijzen.

FAQ

Waarom dan niet gewoon een model fine-tunen op het corpus?

Fine-tunes bevriezen een momentopname, kunnen geen provenance aantonen voor NEN-compliance, en hallucineren met overtuiging. Met retrieval cap je het antwoord op wat het systeem daadwerkelijk vond, en kan het netjes weigeren.

Wat is die rerank-drempel en waarom is hij belangrijk?

Cohere Rerank 3-score van 0,42 over de top 50 kandidaten, top 6 wordt bewaard. Onder de 0,42 weigert de agent. Een halve punt lager en de citatieprecisie stort in.

Hoe lang duurde de bouw van begin tot eind?

Zo'n 14 weken van kickoff tot productie. De licentiepapierwinkel voor NEN Connect XML was het lange pad, niet het engineering-werk. Reken daar vroeg op.

Wat draait er in productie?

Eén Hetzner AX52 voor ingestion en BM25, Qdrant voor de vectoren, Claude Sonnet 4.5 als antwoordmodel met een self-hosted Llama 3.1 70B als fallback, Cohere Rerank 3, en Telegram als interface voor de monteurs.

Laat je de agent ooit antwoorden zonder citaat?

Nee. Het onderbouwingscontract verbiedt dat. Ondersteunen de passages een stap niet, dan moet het model refuse=true zetten en benoemen welk soort bron ontbrak.

ragai agentsknowledge basecase studyarchitectureoperations

Iets bouwen?

Start een project