RAG
RAG-factcheck: hoe een Leidse uitgever het bureau omgooide
Een Leidse vakuitgever had zes redacteuren die feiten checkten tegen een archief van 41.000 artikelen. Wij vervingen de retrieval-stap door een RAG-agent. Dit hebben wij geleerd.

Het probleem op woensdagmiddag
Een senior redacteur bij een Leidse vakuitgever schuift een manuscript van 6.000 woorden over haar bureau. Het is een essay over vroegmoderne handelsroutes, geschreven door een academicus die negentien jaar bij haar uitgeverij publiceert. De auteur is betrouwbaar. De voetnoten niet. Drie eerdere manuscripten spraken data tegen die al in het eigen archief stonden, en de correcties gingen te laat de deur uit. Pijnlijk voor beide kanten.
In het grootste deel van 2024 werd dit opgevangen door een factcheck-bureau van zes mensen die elk geaccepteerd manuscript naast de backcatalogus legden. Ze waren goed. Ze waren ook traag, duur, en structureel onmogelijk op te schalen voorbij de circa tachtig titels die het huis per jaar uitbrengt. In het najaar stelde de uitgever ons een vraag die simpel klinkt. Kan een machine de eerste lezing doen?
Dit is een verhaal over voorzichtig ja zeggen. Een deel werkte. Een deel verraste ons. Het bureau bestaat nog, alleen niet meer zoals vroeger.
Factchecken als retrieval-probleem
Factchecken bij een vakuitgever is niet hetzelfde als factchecken in de journalistiek. Een journalist toetst beweringen aan de wereld. Een redacteur bij een vakuitgeverij toetst beweringen aan het huis zelf. De wereld zegt misschien dat de Vrede van Utrecht in 1713 werd getekend. De eigen monografie uit 2019 over de Nederlandse handel zei hetzelfde, maar koppelde een specifieke clausule aan een specifieke brief uit april. Als een nieuw manuscript dat verdraait, staat de tegenstrijdigheid in je eigen voetnoten, in druk, op planken die je zelf beheert.
Dat maakt het probleem smal op een manier waar LLMs goed mee overweg kunnen. Je vraagt het model niet om dingen te weten. Je vraagt het te vinden wat het huis al heeft gepubliceerd, de nieuwe bewering daarmee te vergelijken, en te melden wanneer ze niet matchen. Dat is een retrieval-taak met een vergelijkingslaag erbovenop. Klassieke RAG, plus die vergelijking.
Het archief, in cijfers
41.000 artikelen, hoofdstukken en losse essays, teruggaand tot 1971. Ongeveer 70% stond in XML (JATS-smaak, grotendeels schoon). De andere 30% zat in twee oudere formaten: een eigen SGML-dialect uit de jaren tachtig, en gescande PDF's met OCR die het redactiebureau al een decennium stil aan het corrigeren was. Gemiddelde artikellengte: 4.800 woorden. Totaal corpus rond 197 miljoen woorden, oftewel ruwweg 270 miljoen tokens na onze tokenizer.
Niets hiervan is groot naar maatstaven van 2026. Klein genoeg om in een middag te embedden en comfortabel in één Postgres-instantie te passen. Het lastige zat niet in volume. Het lastige zat erin dat het corpus zichzelf tegenspreekt. Het huis heeft over vijfenveertig jaar concurrerende posities gepubliceerd, en ground truth is contextueel: een bewering is waar afhankelijk van welk decennium aan wetenschap je vertrouwt.
Architectuur, bewust saai
We weerstonden de verleiding om iets slims te doen. De stack:
- Postgres 16 met pgvector voor het artikelcorpus
- Hybride retrieval: BM25 naast dense embeddings
- Reciprocal rank fusion om de twee resultaatsets samen te voegen
- Een kleine reranker (bge-reranker-base, self-hosted op één GPU) voor de top 50 kandidaten
- Een frontier-LLM voor de contradictie-detectiestap, op basis van de top 8 opgehaalde passages en de kandidaat-paragraaf uit het manuscript
Embeddings: we kwamen uit op een meertalig model, omdat ruwweg 22% van het archief in het Nederlands, Frans of Duits is, en redacteuren cross-language retrieval verwachten. Een Nederlands artikel uit 1994 over textielhandel moet boven komen bij een Engelstalig concept over dezelfde periode. We testten vier embedding-modellen op een handmatig gelabelde set van 200 manuscriptparagrafen met bekende archiefmatches. Het meertalige model won met genoeg marge dat we stopten met zoeken.
Chunking was minder interessant dan we verwachtten. We probeerden paragraaf-niveau, sectie-niveau en sliding-window chunks van 512 en 1.024 tokens. Sectie-niveau van 512 tokens met een overlap van 64 tokens won, maar nipt. De reranker vangt veel chunking-zonden op.
Het contradictieprobleem
Retrieval levert je de passages die relevant lijken. Het levert je geen tegenstrijdigheden. Een naïeve pipeline haalt de top-K passages op, geeft ze aan een LLM met “vind tegenstrijdigheden”, en levert af wat terugkomt. We deden precies dat in de eerste pilot. De false-positive rate was 38%.
Het model markeerde alles dat in toon afweek. Een artikel uit 1987 dat zei “de heersende opvatting is X” en een concept uit 2025 dat zei “X staat vast” werden gemarkeerd als tegenstrijdig, terwijl de tweede een strikte implicatie is van de eerste. Redacteuren haatten het. Binnen drie dagen lazen ze de meldingen niet meer.
Wat werkte: een prompt in twee stappen. De eerste call haalt atomaire beweringen uit het nieuwe manuscript, in genormaliseerde vorm (claim, onderwerp, datum, bron). De tweede call stelt per opgehaalde passage een smallere vraag. Geeft deze passage voor dezelfde bewering een waarde die afwijkt van het nieuwe manuscript? Citeer beide, of geef null terug. Het opsplitsen van het werk bracht false positives terug naar 6%. Redacteuren begonnen de meldingen weer te lezen.
def detect_contradictions(manuscript_paragraph, retrieved_passages):
claims = llm.extract_claims(
text=manuscript_paragraph,
schema=AtomicClaim,
)
findings = []
for claim in claims:
for passage in retrieved_passages:
result = llm.compare(
prompt=CONTRADICTION_PROMPT,
claim=claim,
passage=passage,
response_format=ContradictionVerdict,
)
if result.contradicts:
findings.append({
"claim": claim,
"archive_passage": passage,
"new_quote": result.new_quote,
"archive_quote": result.archive_quote,
"confidence": result.confidence,
})
return findings
De CONTRADICTION_PROMPT is het dragende deel. Hij loopt zo'n 600 woorden lang en bevat negen uitgewerkte voorbeelden uit het eigen correctielogboek van het bureau. We hebben hem elf keer herschreven voordat redacteuren stopten met klagen.
Binnen het budget van 8 seconden blijven
De pilot deed er 47 seconden over per manuscriptparagraaf. De tolerantie van het bureau lag ergens rond 10 seconden. Redacteuren wachten als wachten voelt als werk. Ze wachten niet als het voelt als naar een spinner kijken. We moesten de check van één paragraaf passen in de tijd die het kost om de volgende te markeren en door te scrollen.
De winst kwam uit drie hoeken. Het cachen van de claim-extractiestap per paragraaf (de tweede ronde over een herzien concept raakt de cache voor alles wat niet veranderde) verlaagde de mediane latency met zo'n 60%. Het parallel draaien van de contradictie-vergelijkingscalls tegen de top 8 opgehaalde passages, met een kleine concurrency-cap om de rate limiter tevreden te houden, haalde er nog een flink stuk af. En het overzetten van de reranker naar een quantised on-prem deployment versloeg de round-trip naar een hosted reranker keer op keer.
Eindstand: mediaan 8,2 seconden per paragraaf, 95e percentiel 14 seconden. Goed genoeg.
Bij domeinspecifieke RAG doet het embedding-model er minder toe dan je denkt, en de vergelijkingsprompt meer. Het dure denkwerk gebeurt na retrieval, niet tijdens.
Wat er met de zes mensen gebeurde
Twee vertrokken tijdens de uitrol, om niet-gerelateerde redenen. De andere vier zitten nog op het bureau. Hun werk veranderde.
De agent doet de eerste lezing. Hij leest het manuscript, markeert kandidaat-contradicties, hangt het archiefcitaat eraan vast, en schrijft een onderbouwing van één regel. Een mens bekijkt elke melding voordat die de auteur bereikt. De agent schrijft niet in het manuscript, hij schrijft in een wachtrij. Redacteuren triageren de wachtrij. Ze wijzen ruwweg 6% van de meldingen af als onjuist, accepteren 71% als de moeite waard om bij de auteur aan te kaarten, en markeren de rest als “interessant maar geen tegenstrijdigheid”. Dat is een categorie die we niet voorzagen en waar we een UI voor moesten bouwen.
Het bureau verwerkt nu ruwweg drie keer zoveel volume per persoon, maar het werk is anders. Minder alles-lezen, meer oordeel op randgevallen. Twee van de vier zijn begonnen institutionele kennisdocumenten te schrijven op basis van de patronen die de agent boven brengt. Dat stond niet in de opdracht. Het is het mooiste dat we uit het project hebben zien komen.
Wat we verkeerd deden
Drie dingen, op volgorde van wat het kostte.
We onderschatten de OCR-laag. Ruwweg 11.000 van de 41.000 artikelen kwamen uit scans. Het bureau corrigeerde al jaren OCR-fouten, maar die correcties zaten in een aparte tekstlaag die we bij de eerste ingestion misten. De agent bleef contradicties markeren die in werkelijkheid OCR-rommel waren. We hebben opnieuw ingelezen met de correctielaag erin gemerged, en de false-positive rate zakte van de ene op de andere dag met een derde. Les: vraag twee keer hoe het corpus echt in zijn huidige vorm is beland, en vraag dan een derde keer naar de correcties die niemand noemt.
We vertrouwden de reranker te veel. De reranker is goed. Niet onfeilbaar. De eerste maand toonden we redacteuren alleen de top 5 passages per query. Ze bleven vragen naar “die overduidelijke” die de reranker op positie 12 had gezet. We tonen nu standaard de top 8 en laten redacteuren uitklappen naar de top 20. Latency-kosten: klein. Vertrouwenskosten van het niet doen: groot.
We gingen live zonder audit log. Drie weken later vroeg een senior redacteur welke versie van de agent een specifieke tegenstrijdigheid had gemarkeerd in een manuscript dat sindsdien twee keer was herzien. We wisten het niet. We hebben in week vier volledige provenance bijgebouwd (modelversie, promptversie, opgehaalde passages, scores). Als jouw RAG-agent redactionele beslissingen neemt, zijn versie-vastgepinde audit logs een eis voor dag één, niet een latere refactor.
Waar deze aanpak vastloopt
Het werkt voor de Leidse uitgever omdat het archief afgebakend is, goed gevormd en vertrouwd. Dezelfde aanpak zou stuklopen op drie plekken die we kunnen benoemen.
Factchecken op het open web, waar het corpus de wereld is: retrieval wordt een zoekmachineprobleem, en contradictiedetectie moet kunnen omgaan met onwelwillende bronnen. Een compleet ander project.
Juridisch of medisch, waar een gemiste tegenstrijdigheid een depositie betekent: de drempel voor human-in-the-loop ligt veel strakker, en de auditvereisten duwen de architectuur richting volledig traceerbare regelgebaseerde systemen met RAG als één input naast vele.
Elk domein waar het corpus sneller groeit dan jouw embedding-pipeline kan bijhouden: het archief van de uitgever groeit met 80 titels per jaar. Een nieuwsorganisatie haalt dat voor de lunch. Het kostenmodel verandert van vorm.
Overweeg je RAG voor redactioneel werk, of voor een ander domein waar een afgebakend archief zichzelf tegenspreekt, dan is een experiment van één middag de goedkoopste eerste stap. Label met de hand dertig paragrafen uit een recent manuscript met hun bekende archief-precedenten, en kijk dan of een willekeurig kant-en-klaar embedding-model de juiste passage in de top 10 ophaalt. Dat is de enige test die je vertelt of retrieval überhaupt de juiste vorm is voor jouw probleem. Toen wij de contradictie-agent voor de Leidse uitgever bouwden, liepen we ertegenaan dat de vergelijkingsprompt zwaarder woog dan elk ander stuk samen. We hebben hem uiteindelijk vaker herschreven dan de rest van het systeem. Wil je dezelfde vorm voor je eigen corpus, dan is dat het soort RAG-werk dat wij doen.
Kern
Bij domein-RAG doet de vergelijkingsprompt meer werk dan het embedding-model. Herschrijf hem vaker dan de rest van het systeem samen.
FAQ
Kan een RAG-agent factcheckers vervangen?
Nee, en de vraag mist wat factcheckers echt doen. Een RAG-agent doet de eerste retrieval-ronde tegen je eigen archief. Mensen oordelen of een gemarkeerde tegenstrijdigheid ertoe doet en hoe je die bij de auteur aankaart.
Hoe groot moet een archief zijn voordat RAG de moeite waard is?
Het gaat niet om omvang, het gaat om herhaling. Als jouw domeinexperts elke week dezelfde retrieval-queries draaien, verdient zelfs een corpus van 1.000 documenten de bouwkosten binnen een kwartaal terug.
Wat is de latency-ondergrens voor in-line redactionele RAG?
Ruwweg 8 tot 10 seconden per paragraaf voordat redacteuren het geduld verliezen. Cache de claim-extractiestap, draai de vergelijkingscalls parallel, en host de reranker zelf om binnen dat venster te blijven.
Waarom niet gewoon een LLM fine-tunen op het archief?
Fine-tunen bakt feiten in gewichten die je niet kunt auditen of per document kunt versioneren. RAG houdt het archief apart, zodat je het dagelijks kunt bijwerken en elke melding terug kunt herleiden naar een specifieke bronpassage.