Als je in 2026 met Large Language Models werkt, ontkom je eigenlijk niet meer aan RAG. Retrieval-Augmented Generation — of zoals ik het graag noem: het slimmer maken van je LLM door het toegang te geven tot jouw eigen data — is uitgegroeid van een handige truc tot dé standaard architectuur voor enterprise AI-toepassingen. En eerlijk gezegd, terecht. Want hoe krachtig GPT-4, Claude of Gemini ook zijn, ze weten simpelweg niets van jouw interne documenten, je productcatalogus of die ene Confluence-pagina die niemand meer kan vinden.
De belofte van RAG is eigenlijk best simpel: in plaats van een model alles te laten onthouden (wat sowieso onmogelijk is en hallucinaties veroorzaakt), geef je het op het juiste moment de juiste context mee. Het model hoeft niet meer te gokken — het kan lezen en dan antwoorden.
Klinkt logisch, toch? Maar de praktijk is een stuk weerbarstiger dan de theorie.
In de afgelopen twee jaar is RAG geëvolueerd van een simpele "zoek-en-plak" aanpak naar geavanceerde architecturen met hybride zoekstrategieën, kennisgrafen, agentic workflows en zelfcorrigerende systemen. Behoorlijk wat is er veranderd, zeg maar.
In deze gids neem ik je mee door alles wat je moet weten om een productiewaardige RAG-pipeline te bouwen. Van chunkingstrategieën tot GraphRAG, van embeddings tot evaluatiemetrieken. Met echte code, echte cijfers en eerlijke meningen. Laten we erin duiken.
De Architectuur van een RAG-Pipeline
Voordat we in de details duiken, is het handig om eerst het grote plaatje te snappen. Een RAG-pipeline bestaat uit twee hoofdfasen: de indexeringsfase (offline) en de retrievalfase (online, per query). Laten we beide even ontleden.
De indexeringsfase
In de indexeringsfase bereid je je data voor op snelle retrieval. Dit gaat in een aantal stappen:
- Document ingestion — Je laadt documenten in vanuit diverse bronnen: PDF's, webpagina's, databases, API's, Markdown-bestanden, Confluence, Notion, noem maar op. Dit is vaak verrassend lastig, want elk formaat heeft z'n eigen eigenaardigheden (en geloof me, PDF-parsing kan je grijze haren bezorgen).
- Preprocessing — Je schoont de tekst op: headers, footers, navigatie-elementen, duplicaten en andere ruis eruit. Garbage in, garbage out geldt hier meer dan waar dan ook.
- Chunking — Je splitst documenten op in kleinere stukken (chunks) die elk een betekenisvolle eenheid van informatie bevatten. Dit is eerlijk gezegd een van de meest onderschatte stappen in het hele proces.
- Embedding — Elk chunk wordt omgezet naar een numerieke vector die de semantische betekenis vastlegt. Deze vectoren leven in een hoogdimensionale ruimte waarin vergelijkbare teksten dicht bij elkaar staan.
- Opslag — De vectoren worden opgeslagen in een vectordatabase, samen met de originele tekst en metadata.
De retrievalfase
Wanneer een gebruiker een vraag stelt, gebeurt het volgende:
- Query processing — De vraag wordt geanalyseerd en eventueel herschreven of uitgebreid.
- Retrieval — De meest relevante chunks worden opgezocht via vectorsimilarity, keyword search, of een combinatie daarvan.
- Reranking — De opgehaalde chunks worden opnieuw gerangschikt op relevantie.
- Generation — De relevante chunks worden als context meegegeven aan het LLM, dat vervolgens een antwoord genereert.
Elke stap in dit proces biedt mogelijkheden voor optimalisatie — en elke stap kan je pipeline maken of breken. Dus laten we ze een voor een onder de loep nemen.
Chunkingstrategieën die het Verschil Maken
Chunking klinkt triviaal — je knipt tekst in stukjes, toch? Maar de manier waarop je dat doet, heeft een enorme impact op de kwaliteit van je retrieval. Ik heb pipelines gezien die van "mwah" naar "uitstekend" gingen, puur door de chunkingstrategie aan te passen. Geen grap.
Fixed-size chunking
De simpelste aanpak: knip de tekst op in stukken van een vast aantal tokens of karakters. Snel, voorspelbaar, en vaak goed genoeg als startpunt.
Het probleem? Je knipt midden in zinnen, alinea's en gedachten. Een chunk die begint met "...daarom is het belangrijk om" zonder de voorafgaande context is voor een embedder lastig te interpreteren. En dat merk je terug in je retrieval-resultaten.
Recursive chunking
Een slimmere variant die probeert te splitsen op natuurlijke grenzen: eerst op paragrafen, dan op zinnen, en pas als laatste resort op een vast aantal tokens. LangChain's RecursiveCharacterTextSplitter is hier het standaardvoorbeeld. Dit respecteert de structuur van je tekst beter, maar het is nog steeds puur op tekststructuur gebaseerd — niet op betekenis.
Semantic chunking
En dan de echte gamechanger: semantische chunking. Hierbij gebruik je embeddings om te bepalen waar de inhoudelijke breuken in je tekst liggen. Je berekent de cosine similarity tussen opeenvolgende zinnen, en waar die similarity onder een drempel zakt, maak je een nieuw chunk.
Het resultaat? Chunks die daadwerkelijk coherente informatieve eenheden bevatten.
De cijfers spreken eerlijk gezegd voor zich. In benchmarks laat semantische chunking een faithfulness-score van 0.79 tot 0.82 zien, vergeleken met slechts 0.47 tot 0.51 voor naïeve chunking. Dat is niet marginaal — dat is het verschil tussen een systeem dat bruikbaar is en een systeem dat hallucinaties produceert.
De sweet spot voor chunk-grootte
Ongeacht je chunkingstrategie is er een optimale grootte. Te kleine chunks verliezen context; te grote chunks verdunnen de relevante informatie met ruis. De consensus in 2026 ligt bij 256 tot 512 tokens als sweet spot, met een overlap van 10 tot 20% tussen opeenvolgende chunks. Die overlap zorgt ervoor dat informatie die op een grens valt niet verloren gaat.
Een overlap van rond de 20% wordt in de meeste benchmarks als optimaal beschouwd. Meer overlap leidt tot onnodige duplicatie en hogere opslagkosten; minder overlap tot informatieverlies op de grenzen.
Mijn advies? Begin met recursive chunking van 512 tokens en 20% overlap. Meet je faithfulness-scores. Stap dan over op semantische chunking en vergelijk. In de meeste gevallen zie je een significante verbetering die de extra complexiteit meer dan rechtvaardigt.
Embedding-modellen en Vectordatabases
Je chunks zijn klaar. Nu moeten ze omgezet worden naar vectoren en ergens opgeslagen worden. Beide keuzes hebben directe gevolgen voor de kwaliteit en schaalbaarheid van je pipeline.
Embedding-modellen kiezen
De keuze voor een embedding-model hangt af van een paar factoren: de taal van je documenten, de gewenste nauwkeurigheid, de kosten en de latency. In 2026 zijn dit de belangrijkste opties:
- OpenAI text-embedding-3-large — Uitstekende kwaliteit, makkelijk te integreren, maar je data gaat wel naar een externe API. Dimensies zijn instelbaar (256 tot 3072), wat handig is voor kosten-kwaliteitsafwegingen.
- Cohere Embed v4 — Sterk in meertalige use cases en biedt native ondersteuning voor diverse documenttypen.
- Open-source modellen — BGE, E5, GTE en de Nomic-modellen bieden uitstekende kwaliteit zonder vendor lock-in. Je kunt ze lokaal draaien, wat privacy- en latencyvoordelen oplevert.
- Domeinspecifieke modellen — Voor medische, juridische of technische teksten kunnen finegetunede modellen significant beter presteren dan generieke embedders.
Een tip die ik niet genoeg kan benadrukken: test altijd meerdere modellen op jouw specifieke data. De MTEB-leaderboard is een prima startpunt, maar de prestaties op benchmarks vertalen zich lang niet altijd naar jouw domein. Wat voor Engelstalige Wikipedia-artikelen werkt, hoeft absoluut niet optimaal te zijn voor Nederlandstalige juridische documenten.
Vectordatabases: het speelveld in 2026
De vectordatabase-markt heeft zich enorm ontwikkeld. Dit zijn de belangrijkste spelers op dit moment:
- Pinecone — Fully managed, schaalbaar, en met goede filtering-mogelijkheden. Ideaal als je niet zelf infra wilt beheren, maar het is een betaalde dienst.
- Chroma — Lichtgewicht, open-source, en perfect voor prototyping en kleinere projecten. Draait lokaal of in-memory.
- Weaviate — Open-source met sterke hybride zoekfunctionaliteit (vector + keyword). Heeft ingebouwde modules voor embedding-generatie.
- Qdrant — Performant, open-source, geschreven in Rust. Sterke filtering en payload-support. Eerlijk gezegd mijn persoonlijke favoriet voor middelgrote projecten.
- pgvector — PostgreSQL-extensie die vectoropslag toevoegt aan je bestaande database. Fantastisch als je al op PostgreSQL draait en geen aparte dienst wilt opzetten.
Een interessante trend in 2026 is dat vectoren steeds meer een standaard datatype worden in multimodel databases. PostgreSQL met pgvector, MongoDB met Atlas Vector Search, Redis met de Vector Similarity Search-module — je hoeft niet per se een dedicated vectordatabase te draaien. Voor veel toepassingen is het toevoegen van vectorfunctionaliteit aan je bestaande database gewoon de meest pragmatische en kosteneffectieve keuze.
Hybrid Search en Reranking
Pure vectorsearch heeft een fundamentele beperking: het is geweldig in het vinden van semantisch vergelijkbare tekst, maar het mist soms exacte matches. Als een gebruiker zoekt naar "foutcode XR-4072", dan wil je een exacte keyword match — niet een chunk dat semantisch over foutcodes gaat maar toevallig een ander foutnummer noemt.
Hybride zoekstrategieën
De oplossing? Hybride search: combineer vectorsearch (voor semantische relevantie) met BM25 of andere keyword-gebaseerde methoden (voor exacte matches). De resultaten van beide worden dan gefuseerd met een algoritme als Reciprocal Rank Fusion (RRF).
In de praktijk werkt dit verrassend goed. Je vangt het beste van twee werelden: de semantische flexibiliteit van vectorsearch ("wat bedoelt de gebruiker?") gecombineerd met de precisie van keyword search ("welke exacte term wordt genoemd?").
Reranking: de stille held
Reranking is misschien wel de meest ondergewaardeerde optimalisatie in RAG. Serieus. Het idee is simpel: je haalt eerst een ruime set kandidaat-chunks op (zeg 20-50), en laat vervolgens een cross-encoder model deze herschikken op basis van de daadwerkelijke relevantie voor de specifieke query.
Waarom werkt dit zo goed? Bi-encoder embeddings (die in je vectordatabase zitten) berekenen de relevantie van query en document onafhankelijk van elkaar. Een cross-encoder daarentegen bekijkt query en document samen, waardoor het veel fijnmazigere relevantie-oordelen kan maken.
Onderzoek van Databricks toonde aan dat reranking kan leiden tot een verbetering van tot 48% in retrievalkwaliteit. De NDCG-scores stijgen aanzienlijk, wat zich direct vertaalt naar betere antwoorden. Cohere Rerank, BGE Reranker en de Jina Reranker zijn populaire opties in 2026.
De kosten? Een extra API-call of inferentiestap per query, die doorgaans 50-200ms toevoegt aan de latency. Voor de meeste toepassingen is dat een prima acceptabele trade-off voor significant betere resultaten.
Een RAG-Pipeline Bouwen met Python
Oké, genoeg theorie — laten we code schrijven. We beginnen met een basis RAG-pipeline en bouwen die vervolgens uit naar een geavanceerde variant met query rewriting en reranking.
Basis RAG-pipeline
Deze pipeline laadt documenten, maakt chunks, slaat embeddings op en beantwoordt vragen. Simpel, maar functioneel genoeg om mee te starten.
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# Stap 1: Documenten laden
loader = DirectoryLoader(
"./documenten",
glob="**/*.md",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"}
)
documenten = loader.load()
print(f"Aantal documenten geladen: {len(documenten)}")
# Stap 2: Chunking met overlap
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=100, # ~20% overlap
length_function=len,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = text_splitter.split_documents(documenten)
print(f"Aantal chunks: {len(chunks)}")
# Stap 3: Embeddings genereren en opslaan
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db",
collection_name="mijn_documenten"
)
# Stap 4: Retrieval chain opzetten
prompt_template = PromptTemplate(
input_variables=["context", "question"],
template="""Je bent een behulpzame assistent. Gebruik de volgende context
om de vraag te beantwoorden. Als je het antwoord niet kunt vinden in de
context, zeg dat dan eerlijk.
Context:
{context}
Vraag: {question}
Antwoord:"""
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
chain_type_kwargs={"prompt": prompt_template},
return_source_documents=True
)
# Stap 5: Een vraag stellen
resultaat = qa_chain.invoke({"query": "Wat zijn de voordelen van RAG?"})
print(resultaat["result"])
for doc in resultaat["source_documents"]:
print(f" Bron: {doc.metadata.get('source', 'onbekend')}")
Dit is een werkende basis. Maar voor productie heb je echt meer nodig. Laten we de pipeline flink uitbreiden.
Geavanceerde RAG-pipeline met query rewriting en reranking
In deze versie voegen we twee krachtige optimalisaties toe: query rewriting (het herschrijven van de gebruikersvraag voor betere retrieval) en reranking (het herschikken van resultaten met een cross-encoder). Dit is waar het echt interessant wordt.
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from sentence_transformers import CrossEncoder
# --- Configuratie ---
llm = ChatOpenAI(model="gpt-4o", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# Vectorstore laden (eerder aangemaakt)
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="mijn_documenten"
)
# --- Query Rewriting ---
rewrite_prompt = ChatPromptTemplate.from_template(
"""Herschrijf de volgende gebruikersvraag naar 3 varianten die
beter geschikt zijn voor documentretrieval. Geef elke variant op
een nieuwe regel, zonder nummering.
Originele vraag: {question}
Herschreven varianten:"""
)
def generate_query_variants(question: str) -> list[str]:
"""Genereer meerdere zoekquery's voor betere retrieval."""
chain = rewrite_prompt | llm | StrOutputParser()
varianten = chain.invoke({"question": question})
queries = [q.strip() for q in varianten.strip().split("\n") if q.strip()]
queries.insert(0, question) # Originele query ook meenemen
return queries
# --- Hybride Retrieval ---
def hybrid_retrieve(question: str, k: int = 20) -> list:
"""Combineer vector search met meerdere query-varianten."""
queries = generate_query_variants(question)
alle_docs = []
gezien = set()
for query in queries:
vector_docs = vectorstore.similarity_search(query, k=k)
for doc in vector_docs:
doc_id = hash(doc.page_content)
if doc_id not in gezien:
alle_docs.append(doc)
gezien.add(doc_id)
return alle_docs
# --- Reranking met Cross-Encoder ---
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3", max_length=512)
def rerank_documents(question: str, documents: list, top_k: int = 5) -> list:
"""Herrangschik documenten met een cross-encoder reranker."""
if not documents:
return []
paren = [(question, doc.page_content) for doc in documents]
scores = reranker.predict(paren)
scored_docs = list(zip(documents, scores))
scored_docs.sort(key=lambda x: x[1], reverse=True)
return [doc for doc, score in scored_docs[:top_k]]
# --- Generatie ---
answer_prompt = ChatPromptTemplate.from_template(
"""Je bent een deskundige assistent. Beantwoord de vraag op basis
van de onderstaande context. Wees nauwkeurig en verwijs naar
specifieke details uit de context.
Context:
{context}
Vraag: {question}
Gedetailleerd antwoord:"""
)
def format_docs(docs: list) -> str:
return "\n\n---\n\n".join(doc.page_content for doc in docs)
# --- Volledige pipeline ---
def advanced_rag_query(question: str) -> dict:
"""Voer een geavanceerde RAG-query uit met rewriting en reranking."""
# Stap 1: Hybride retrieval met query rewriting
kandidaten = hybrid_retrieve(question, k=20)
print(f"Kandidaat-documenten opgehaald: {len(kandidaten)}")
# Stap 2: Reranking
top_docs = rerank_documents(question, kandidaten, top_k=5)
print(f"Top documenten na reranking: {len(top_docs)}")
# Stap 3: Generatie
context = format_docs(top_docs)
chain = answer_prompt | llm | StrOutputParser()
antwoord = chain.invoke({"context": context, "question": question})
return {
"antwoord": antwoord,
"bronnen": top_docs,
"aantal_kandidaten": len(kandidaten)
}
# Gebruik
resultaat = advanced_rag_query("Hoe optimaliseer ik een RAG-pipeline?")
print(resultaat["antwoord"])
Dit is een serieuze upgrade ten opzichte van de basispipeline. De query rewriting zorgt ervoor dat je niet afhankelijk bent van hoe de gebruiker z'n vraag formuleert, en de reranking garandeert dat de meest relevante documenten bovenaan komen te staan. In mijn ervaring levert deze combinatie een merkbaar betere gebruikerservaring op — en dat is uiteindelijk waar het om draait.
Agentic RAG: De Volgende Evolutie
De pipelines die we tot nu toe hebben besproken zijn lineair: vraag erin, antwoord eruit. Maar wat als het antwoord niet goed genoeg is? Wat als de retrieval niets oplevert? Wat als de vraag eigenlijk uit meerdere deelvragen bestaat?
Hier komt agentic RAG om de hoek kijken.
Van pipeline naar loop
Het fundamentele inzicht achter agentic RAG is dat RAG geen pipeline zou moeten zijn, maar een loop. Een agent die kan:
- Plannen — De vraag ontleden in deelvragen en een strategie bepalen.
- Zoeken — Informatie ophalen uit verschillende bronnen met verschillende strategieën.
- Reflecteren — Evalueren of de opgehaalde informatie voldoende en relevant is.
- Bijsturen — Als de info onvoldoende is, de zoekopdracht aanpassen en opnieuw proberen.
- Tools gebruiken — Externe API's aanroepen, berekeningen uitvoeren of databases bevragen.
Stel je voor: een gebruiker vraagt "Hoe presteert product X vergeleken met product Y op criteria A, B en C?" Een lineaire pipeline zoekt één keer en hoopt het beste. Een agentic RAG-systeem daarentegen splitst dit op in deelvragen, zoekt informatie over elk product afzonderlijk, controleert of alle criteria gedekt zijn, en stelt pas dan een vergelijkend antwoord samen. Best een verschil, toch?
Self-RAG: zelfcorrigerend retrieval
Een bijzonder elegante variant is Self-RAG (Self-Reflective RAG). Het idee: het LLM beoordeelt zelf of retrieval nodig is, of de opgehaalde documenten relevant zijn, en of het gegenereerde antwoord wordt ondersteund door de bronnen. Dit gebeurt via speciale "reflectie-tokens" die het model leert genereren.
De flow ziet er zo uit:
- Het model ontvangt een vraag en besluit of retrieval nodig is.
- Als ja, worden documenten opgehaald en beoordeeld op relevantie.
- Het model genereert een antwoord en beoordeelt of dit antwoord faithful is ten opzichte van de bronnen.
- Als het antwoord onvoldoende is, wordt het proces herhaald met aangepaste queries.
Dit is een fundamenteel andere benadering dan "zoek altijd, ongeacht de vraag". Soms weet het model het antwoord gewoon al — in dat geval is retrieval onnodige overhead die alleen maar latency toevoegt zonder echte waarde. Self-RAG leert wanneer retrieval echt nodig is en wanneer niet.
Multi-agent RAG
Voor complexe kennissystemen kun je meerdere gespecialiseerde agents inzetten. Een "router-agent" bepaalt welke specialist nodig is: een technische documentatie-agent, een beleids-agent, een data-analyse-agent. Elk met eigen vectorstores, prompts en tools. De router coördineert en aggregeert de resultaten. Het klinkt misschien overengineered, maar voor grote organisaties met diverse kennisbronnen kan dit echt het verschil maken.
GraphRAG: Kennisgrafen voor Betere Retrieval
Vectorsearch is fantastisch voor het vinden van semantisch vergelijkbare tekst, maar het mist iets fundamenteels: relaties. Als document A zegt "Jan is CEO van Bedrijf X" en document B zegt "Bedrijf X heeft een partnership met Bedrijf Y", dan kan vectorsearch deze documenten allebei wel vinden — maar het begrijpt niet dat Jan indirect verbonden is met Bedrijf Y.
Dat is precies waar GraphRAG om de hoek komt kijken.
GraphRAG combineert kennisgrafen met RAG om dit probleem op te lossen. De aanpak, sterk gepopulariseerd door Microsoft's onderzoek, werkt als volgt:
- Entity extraction — Een LLM extraheert entiteiten (personen, organisaties, concepten) en hun relaties uit je documenten.
- Grafopbouw — Deze entiteiten en relaties worden opgeslagen in een kennisgraaf.
- Community detection — Clusters van sterk verbonden entiteiten worden geïdentificeerd.
- Samenvattingen — Per community worden samenvattingen gegenereerd die de sleutelinformatie bevatten.
- Retrieval — Bij een query wordt zowel vectorsearch als graftraversal gebruikt om relevante informatie te vinden.
De resultaten zijn eerlijk gezegd indrukwekkend. Voor vragen die redeneren over relaties en verbanden rapporteren implementaties een zoekprecisie van tot 99%. Dat is significant beter dan pure vectorsearch, vooral voor vragen als "Welke bedrijven zijn indirect verbonden met persoon X?" of "Wat zijn alle factoren die bijdragen aan probleem Y?"
GraphRAG in de praktijk met Neo4j en LangChain
Hier is een vereenvoudigd voorbeeld van hoe je GraphRAG kunt implementeren met Neo4j als grafendatabase:
from langchain_community.graphs import Neo4jGraph
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import json
# Verbinding met Neo4j
graph = Neo4jGraph(
url="bolt://localhost:7687",
username="neo4j",
password="jouw_wachtwoord"
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# Stap 1: Entiteiten extraheren uit een document
extraction_prompt = ChatPromptTemplate.from_template(
"""Extraheer alle entiteiten en hun relaties uit de volgende tekst.
Geef het resultaat als JSON met de structuur:
{{
"entiteiten": [
{{"naam": "...", "type": "Persoon|Organisatie|Concept|Locatie"}}
],
"relaties": [
{{"bron": "...", "type": "...", "doel": "..."}}
]
}}
Tekst: {tekst}
JSON-resultaat:"""
)
def extraheer_en_sla_op(tekst: str):
"""Extraheer entiteiten uit tekst en sla op in Neo4j."""
chain = extraction_prompt | llm | StrOutputParser()
resultaat = chain.invoke({"tekst": tekst})
data = json.loads(resultaat)
# Entiteiten aanmaken in Neo4j
for entiteit in data["entiteiten"]:
graph.query(
"MERGE (e:{type} {{naam: $naam}}) RETURN e".format(
type=entiteit["type"]
),
params={"naam": entiteit["naam"]}
)
# Relaties aanmaken
for relatie in data["relaties"]:
graph.query(
"""MATCH (a {naam: $bron})
MATCH (b {naam: $doel})
MERGE (a)-[r:RELATIE {type: $type}]->(b)
RETURN r""",
params={
"bron": relatie["bron"],
"doel": relatie["doel"],
"type": relatie["type"]
}
)
# Stap 2: Grafgebaseerde retrieval
def graph_retrieval(vraag: str) -> str:
"""Zoek relevante informatie via de kennisgraaf."""
entity_prompt = ChatPromptTemplate.from_template(
"""Welke entiteiten worden genoemd of bedoeld in deze vraag?
Geef alleen de namen, gescheiden door komma's.
Vraag: {vraag}
Entiteiten:"""
)
chain = entity_prompt | llm | StrOutputParser()
entiteiten = chain.invoke({"vraag": vraag}).split(",")
entiteiten = [e.strip() for e in entiteiten]
context_delen = []
for entiteit in entiteiten:
resultaat = graph.query(
"""MATCH (e {naam: $naam})-[r]-(verbonden)
RETURN e.naam AS bron, type(r) AS relatie_type,
r.type AS relatie_detail,
verbonden.naam AS verbonden_met
LIMIT 20""",
params={"naam": entiteit}
)
if resultaat:
for r in resultaat:
context_delen.append(
f"{r['bron']} --[{r.get('relatie_detail', '')}]--> "
f"{r['verbonden_met']}"
)
return "\n".join(context_delen) if context_delen else "Geen grafdata gevonden."
# Voorbeeld
extraheer_en_sla_op(
"Acme Corp, opgericht door Jan de Vries in Amsterdam, "
"is een partner van TechStart BV. TechStart is gespecialiseerd "
"in machine learning en wordt geleid door Maria Jansen."
)
antwoord = graph_retrieval("Wat is de relatie tussen Jan de Vries en TechStart?")
print(antwoord)
In de praktijk combineer je GraphRAG met reguliere vectorsearch. De graaf geeft je de relationele context ("wie is verbonden met wie"), terwijl vectorsearch de inhoudelijke details levert ("wat staat er precies in het document over deze relatie"). De combinatie van beide is krachtiger dan elk afzonderlijk — dat is iets wat ik keer op keer zie in productie-implementaties.
Een kanttekening: GraphRAG is niet voor elk project geschikt. De indexeringskosten zijn behoorlijk (veel LLM-calls voor entiteitsextractie), en het onderhouden van een kennisgraaf voegt complexiteit toe. Gebruik het wanneer relaties en verbanden echt centraal staan in je use case — denk aan compliance, due diligence, supply chain analyse of medische dossiers.
Evaluatie en Monitoring
Je hebt een mooie RAG-pipeline gebouwd. Maar hoe weet je eigenlijk of die goed werkt? En hoe monitor je de kwaliteit over tijd?
Dit is waar veel teams de bal laten vallen. Ze bouwen een pipeline, doen wat handmatige tests, en deployen. Tot een gebruiker klaagt dat het systeem onzin uitkraamt. Geloof me, dat wil je voorkomen.
Evaluatiemetrieken
Er zijn drie dimensies waarop je je RAG-systeem moet evalueren:
Retrievalkwaliteit:
- Recall@k — Welk percentage van de relevante documenten zit in je top-k resultaten?
- Precision@k — Welk percentage van je top-k resultaten is daadwerkelijk relevant?
- NDCG (Normalized Discounted Cumulative Gain) — Staan de meest relevante documenten bovenaan? Dit is cruciaal, want het verschil tussen het relevantste document op positie 1 versus positie 5 is enorm.
- MRR (Mean Reciprocal Rank) — Op welke positie staat het eerste relevante document gemiddeld?
Generatiekwaliteit:
- Faithfulness — Is het antwoord gebaseerd op de opgehaalde bronnen, of hallucineert het model? Dit is veruit de belangrijkste metriek voor productiesystemen.
- Answer relevance — Beantwoordt het antwoord daadwerkelijk de gestelde vraag?
- Completeness — Zijn alle relevante aspecten van de vraag afgedekt?
End-to-end kwaliteit:
- Correctness — Is het antwoord feitelijk juist? Dit vereist een ground truth dataset.
- Latency — Hoe snel krijgt de gebruiker een antwoord?
- Kosten per query — Wat kost elke interactie aan API-calls, compute en opslag?
LLM-as-Judge
Een van de krachtigste evaluatietechnieken in 2026 is "LLM-as-Judge": je gebruikt een (ander) LLM om de kwaliteit van je RAG-output te beoordelen. Frameworks als RAGAS, DeepEval en LangSmith bieden kant-en-klare implementaties hiervoor.
Het werkt zo: je geeft het evaluatie-LLM de originele vraag, de opgehaalde context, en het gegenereerde antwoord. Het model beoordeelt dan op dimensies als faithfulness en relevantie, met scores en motivatie. Het is niet perfect — LLMs hebben hun eigen biases — maar het is significant beter dan handmatige evaluatie als je met grote volumes werkt.
Productie-monitoring
In productie wil je continu monitoren. Hier zijn de dingen waar je op moet letten:
- Retrieval-metrics per query — Track de similarity scores van opgehaalde documenten. Als die plotseling dalen, is er mogelijk iets veranderd in je data of embeddings.
- Gebruikersfeedback — Thumbs up/down, follow-up vragen, het herschrijven van queries — alles is signaal.
- Drift-detectie — Verandert het type vragen dat gebruikers stellen over tijd? Past je documentatie zich daarop aan?
- Error rates — Hoe vaak faalt de pipeline (timeouts, lege resultaten, LLM-weigering)?
- Cost tracking — Hoeveel tokens verbruik je per dag? Zijn er uitschieters?
Mijn tip: bouw monitoring niet achteraf in. Dat is een fout die ik zelf heb gemaakt en die ik niet nog een keer maak. Ontwerp je pipeline van begin af aan met observability in gedachten. Log elke stap — de query, de herschreven queries, de opgehaalde documenten met hun scores, het uiteindelijke antwoord. Je zult jezelf dankbaar zijn bij de eerste productie-incidenten.
Best Practices voor Productie
Het verschil tussen een demo en een productiesysteem is enorm. Hier zijn de lessen die ik heb geleerd — soms op de harde manier.
Data preprocessing: het fundament
Besteed meer tijd aan data preprocessing dan je denkt nodig te hebben. Serieus, dit meen ik. Een RAG-systeem is zo goed als de data die erin gaat.
- Ontdubbeling — Duplicaten in je brondata leiden tot duplicaten in je retrieval, wat kostbare contextruimte verspilt.
- Metadata verrijking — Voeg structurele metadata toe aan elk chunk: bron-document, sectietitel, aanmaakdatum, auteur, documenttype. Deze metadata is goud waard voor filtering en debugging.
- Kwaliteitsfiltering — Niet elk document is het waard om geïndexeerd te worden. Verouderde documenten, drafts en auto-gegenereerde content kunnen de kwaliteit van je retrieval flink ondermijnen.
- Tabel- en afbeeldingsverwerking — Tabellen in PDF's zijn berucht moeilijk te verwerken. Overweeg gespecialiseerde tools of converteer tabellen naar gestructureerde tekst voordat je ze chunked.
Caching: snelheid en kosten
Caching is niet sexy maar buitengewoon effectief. Implementeer caching op meerdere niveaus:
- Embedding cache — Cache de embeddings van veelvoorkomende queries.
- Retrieval cache — Cache de top-k resultaten voor identieke of zeer vergelijkbare queries.
- Antwoord cache — Cache volledige antwoorden voor exacte query-matches. Maar wees voorzichtig: als je brondata verandert, moeten caches geïnvalideerd worden.
- Semantic cache — Cache antwoorden niet alleen voor exacte matches, maar ook voor semantisch vergelijkbare queries. Dit is geavanceerder maar levert de meeste besparingen op.
De 70/30-regel
Dit is misschien wel het belangrijkste architectuuradvies dat ik kan geven: hardcode de stabiele 70% van je pipeline en gebruik een framework als LangChain voor de flexibele 30%.
Wat bedoel ik daarmee? De documentlading, preprocessing, chunking, embedding en opslag — dat zijn relatief stabiele processen die je prima kunt implementeren met standaard Python-code en directe API-calls. Hier heb je geen framework overhead nodig.
Maar de retrieval-strategie, de prompt engineering, de agent-logica, de evaluatie — dat zijn componenten die je vaak wilt aanpassen, A/B-testen en itereren. Daar biedt LangChain of LlamaIndex echte waarde door de flexibiliteit en het abstractieniveau.
Het resultaat: een pipeline die stabiel en performant is in de kern, maar flexibel en experimenteerbaar aan de randen. Win-win.
Kosten-optimalisatie
RAG kan duur worden, vooral bij hoog volume. Een paar strategieën die helpen:
- Embedding-dimensiereductie — OpenAI's text-embedding-3-large ondersteunt lagere dimensies. 1024 dimensies in plaats van 3072 scheelt significant in opslag en zoektijd, met minimaal kwaliteitsverlies.
- Tiered retrieval — Gebruik een goedkope eerste fase (BM25 of lage-dimensie embeddings) om kandidaten te selecteren, en een duurdere tweede fase (cross-encoder reranking) alleen voor de top-kandidaten.
- Kleinere modellen voor routine-queries — Niet elke query heeft GPT-4o nodig. Gebruik een router die simpele vragen naar een goedkoper model stuurt.
- Batch processing — Als je documenten niet real-time hoeft te indexeren, verwerk ze dan in batches om API-kosten te beperken.
Conclusie
RAG in 2026 is een wereld van verschil vergeleken met de eerste naive implementaties van een paar jaar geleden. De tooling is volwassener, de architecturen geavanceerder, en de best practices beter gedocumenteerd. Maar de kern blijft hetzelfde: geef je LLM de juiste context, op het juiste moment, in het juiste formaat.
Laten we even samenvatten wat we allemaal hebben behandeld:
- Chunking maakt of breekt je pipeline. Semantische chunking met 256-512 tokens en 10-20% overlap is de huidige state of the art.
- Hybride search combineert het beste van vectorsearch en keyword search.
- Reranking is een relatief eenvoudige toevoeging die tot 48% verbetering in retrievalkwaliteit kan opleveren.
- Agentic RAG transformeert je pipeline van een lineair proces naar een intelligent zoek-en-redeneer-systeem.
- GraphRAG voegt relationeel begrip toe en bereikt zoekprecisie tot 99% voor relatievragen.
- Evaluatie en monitoring zijn geen nice-to-haves maar essentieel voor productiesystemen.
- De 70/30-regel helpt je de juiste balans te vinden tussen stabiliteit en flexibiliteit.
Mijn advies? Begin simpel. Bouw een basis RAG-pipeline, meet de kwaliteit, en itereer. Voeg complexiteit alleen toe wanneer je data laat zien dat het nodig is. Semantische chunking voor betere faithfulness. Reranking als je precision te laag is. GraphRAG als relaties centraal staan. Agentic RAG als simpele queries niet meer volstaan.
De mooiste RAG-pipeline is uiteindelijk niet de meest geavanceerde — het is de pipeline die het probleem van je gebruikers oplost, betrouwbaar draait in productie en binnen budget blijft.
De toekomst van RAG belooft trouwens nog spannender te worden. Multimodale RAG (zoeken over tekst, afbeeldingen en video), real-time RAG met streaming updates, en steeds slimmere agentic architecturen staan aan de horizon. Maar de fundamenten die je in deze gids hebt geleerd, blijven relevant — ongeacht welke richting de technologie op gaat.
Veel succes met het bouwen van je eigen RAG-pipeline. En onthoud: de beste manier om RAG te leren, is door het te doen. Open je editor, start een Jupyter notebook, en begin gewoon met experimenteren. Je data wacht op je.