RAG in Productie: Lessons Learned na 10+ Projecten
Na 10+ RAG projecten deel ik mijn belangrijkste lessen over chunking strategieen, embedding models, retrieval optimalisatie en het omgaan met hallucinations in production.
TTom Builder
15 dagen geledenRAGLangChainProductionAITutorial
# RAG in Productie: Lessons Learned na 10+ Projecten
## Inleiding
Retrieval Augmented Generation (RAG) klinkt simpel in theorie: zoek relevante documenten, geef ze als context mee aan een LLM, en genereer een antwoord. In de praktijk zit de duivel in de details. Na het bouwen van 10+ RAG-systemen voor klanten in juridische, medische en enterprise omgevingen, deel ik mijn belangrijkste lessen.
## Les 1: Chunking strategie bepaalt 80% van je resultaat
De manier waarop je documenten opdeelt in chunks is verreweg de belangrijkste beslissing in je RAG-pipeline. Een slecht ge-chunkte document levert slechte antwoorden, ongeacht hoe goed je LLM is.
### Fixed-size chunking (de baseline)
```python
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(documents)
```
**Wanneer gebruiken:** als startpunt voor prototyping. Werkt redelijk voor gestructureerde tekst.
**Nadelen:** respecteert geen document-structuur, snijdt zinnen en paragrafen door.
### Semantic chunking (aanbevolen)
Splits documenten op basis van semantische grenzen (koppen, secties, paragrafen):
```python
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
chunker = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=90
)
chunks = chunker.create_documents([text])
```
**Wanneer gebruiken:** voor de meeste productie-systemen. Levert betere retrieval en meer coherente context.
### Domein-specifieke chunking
Voor gespecialiseerde documenten (wetgeving, medische protocollen) bouw je een custom parser:
```python
import re
def chunk_legal_document(text):
"""Chunk wetgeving op artikel-niveau."""
articles = re.split(r'(Artikel \d+)', text)
chunks = []
for i in range(1, len(articles), 2):
header = articles[i]
body = articles[i+1] if i+1 < len(articles) else ""
chunks.append(f"{header}\n{body.strip()}")
return chunks
```
### Tips voor chunking
- **Overlap**: gebruik 10-20% overlap tussen chunks om context te bewaren
- **Metadata**: bewaar altijd de bron (document, pagina, sectie) als metadata
- **Chunk size**: 500-1000 tokens voor retrieval, maar geef het LLM grotere context (parent document retriever)
- **Test**: evalueer chunking strategieen systematisch met een golden dataset
## Les 2: Embedding model selectie
Niet alle embedding models zijn gelijk. De keuze heeft directe impact op je retrieval kwaliteit.
### Mijn ranking (begin 2026):
1. **OpenAI text-embedding-3-large** (dim 3072): beste all-round, excellent voor Engels en Nederlands
2. **Cohere embed-v3**: uitstekend voor meertalig gebruik en beschikbaar in verschillende dimensies
3. **BGE-M3** (open source): beste open-source optie voor meertalig
4. **OpenAI text-embedding-3-small** (dim 1536): goede prijs/kwaliteit als kosten belangrijk zijn
### Belangrijk voor Nederlandse teksten
- Test ALTIJD met Nederlandse queries, niet alleen Engelse
- Sommige modellen presteren slecht op Nederlands ondanks goede Engelse scores
- Overweeg een hybrid aanpak: Engelse embeddings + BM25 voor Nederlandse keyword matching
## Les 3: Retrieval optimalisatie
Embeddings alleen zijn niet genoeg. Hier zijn de technieken die het verschil maken:
### Hybrid search
Combineer semantic search (embeddings) met keyword search (BM25):
```python
from langchain.retrievers import EnsembleRetriever
semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
bm25_retriever = BM25Retriever.from_documents(docs, k=10)
ensemble = EnsembleRetriever(
retrievers=[semantic_retriever, bm25_retriever],
weights=[0.6, 0.4]
)
```
Dit vangt zowel semantisch vergelijkbare als exact matchende documenten.
### Re-ranking
Gebruik een cross-encoder om de top-N resultaten te re-ranken:
```python
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
reranker = CohereRerank(model="rerank-multilingual-v3.0", top_n=5)
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=ensemble
)
```
Re-ranking verbetert precision met 15-30% in mijn ervaring.
### Parent document retriever
Haal kleine chunks op voor precision, maar geef grotere context aan het LLM:
- Indexeer chunks van 200 tokens
- Bij een match, haal de parent chunk van 1000 tokens op
- Het LLM krijgt meer context, de retrieval is preciezer
## Les 4: Hallucinations voorkomen
De grootste uitdaging in productie-RAG is ervoor zorgen dat het model alleen antwoordt op basis van de context.
### System prompt engineering
```
Je bent een vraag-en-antwoord assistent. Beantwoord de vraag ALLEEN op basis van de onderstaande context.
Als het antwoord niet in de context staat, zeg dan: "Ik kan dit niet beantwoorden op basis van de beschikbare documenten."
Verwijs altijd naar de bron(nen) waarop je antwoord gebaseerd is.
Verzin GEEN informatie.
```
### Confidence scoring
Implementeer een confidence score om onzekere antwoorden te flaggen:
1. Vraag het LLM om een confidence score (1-10) mee te geven
2. Controleer of de geclaimde bronnen daadwerkelijk de informatie bevatten
3. Filter antwoorden met lage confidence (< 6)
### Bronvermelding
Verplicht bronvermelding bij elk antwoord. Dit heeft twee voordelen:
- De gebruiker kan het antwoord verifieren
- Het model hallucineert minder als het bronnen moet citeren
## Les 5: Evaluatie is niet optioneel
Zonder evaluatie vlieg je blind. Hier is mijn evaluatie-framework:
### Golden dataset
Laat domeinexperts 100-200 vraag-antwoord paren maken. Dit is je ground truth.
### Metrics
1. **Retrieval**: Recall@K (worden de juiste documenten opgehaald?)
2. **Generation**: Accuracy, Faithfulness, Relevance
3. **End-to-end**: Percentage correct beantwoorde vragen
### LLM-as-judge
Gebruik GPT-4 om antwoorden te beoordelen:
```python
eval_prompt = """
Beoordeel of het gegeven antwoord correct is op basis van het referentie-antwoord.
Geef een score van 1-5:
5 = Volledig correct en compleet
4 = Correct maar incompleet
3 = Gedeeltelijk correct
2 = Grotendeels incorrect
1 = Volledig incorrect
Vraag: {question}
Referentie-antwoord: {reference}
Gegenereerd antwoord: {generated}
Score:
"""
```
### Monitoring in productie
- Log elke query, retrieval results en antwoord
- Track user feedback (thumbs up/down)
- Analyseer failed queries wekelijks
- Gebruik LangFuse of Langsmith voor observability
## Conclusie
RAG bouwen is niet moeilijk. RAG bouwen dat betrouwbaar werkt in productie is een ander verhaal. Investeer in chunking, evaluatie en monitoring — het bespaart je weken aan debugging later.