# Chunking-strategieën voor RAG [[TOC]] ## Waarom chunking zo belangrijk is De kwaliteit van je RAG-systeem hangt voor een groot deel af van hoe je documenten opdeelt. Zijn de chunks te groot, dan krijgt het model irrelevante tekst in de context en worden de antwoorden onnauwkeurig. Zijn ze te klein, dan mist de context de benodigde informatie en kan het model geen volledig antwoord geven. :::info title="Vuistregel" Chunks moeten groot genoeg zijn om een complete gedachte te bevatten, maar klein genoeg om specifiek te blijven. Voor informatieve teksten werkt 256 tot 512 tokens meestal goed. ::: ## Strategie 1: Vaste grootte met overlap De eenvoudigste aanpak splitst op een vast aantal tokens of tekens, met een overlappend venster: ```typescript function fixedSizeChunk(text: string, chunkSize: number, overlap: number): string[] { const words = text.split(/\s+/); const chunks: string[] = []; for (let i = 0; i < words.length; i += chunkSize - overlap) { const chunk = words.slice(i, i + chunkSize).join(" "); if (chunk.trim()) chunks.push(chunk); if (i + chunkSize >= words.length) break; } return chunks; } const chunks = fixedSizeChunk(document, 400, 50); ``` Overlap zorgt dat informatie die op een chunk-grens staat niet verloren gaat. Voordelen: eenvoudig, voorspelbaar en snel. Nadelen: het splitst midden in zinnen of paragrafen, waardoor context afgekapt kan raken. ## Strategie 2: Recursief chunken Recursief chunken probeert eerst op grote separatoren te splitsen en gaat pas naar kleinere over als de chunks nog te groot zijn: ```typescript const SEPARATORS = [" ", " ", ". ", "! ", "? ", " ", ""]; function recursiveChunk(text: string, maxChunkSize: number, overlap: number): string[] { for (const separator of SEPARATORS) { if (separator === "") { return fixedSizeChunk(text, maxChunkSize, overlap); } const splits = text.split(separator); const chunks: string[] = []; let current = ""; for (const split of splits) { const candidate = current ? current + separator + split : split; const tokenCount = estimateTokens(candidate); if (tokenCount > maxChunkSize && current) { chunks.push(current); const overlapText = current.split(" ").slice(-overlap).join(" "); current = overlapText + separator + split; } else { current = candidate; } } if (current) chunks.push(current); if (chunks.every(c => estimateTokens(c) <= maxChunkSize)) { return chunks; } } return [text]; } ``` Dit is in de kern de aanpak van de `RecursiveCharacterTextSplitter` uit LangChain en werkt goed voor de meeste platte tekst. In benchmarks levert recursief splitsen rond 400 tot 512 tokens met 10 tot 20 procent overlap een sterke balans tussen retrieval-kwaliteit en kosten, zonder de zware rekentijd van semantisch chunken. :::tip title="Begin hier" Twijfel je welke strategie je moet kiezen, start dan met recursief chunken op 512 tokens en 50 tokens overlap. Optimaliseer pas verder als je metingen daar aanleiding toe geven. ::: ## Strategie 3: Structuur-gebaseerde chunking Voor documenten met een expliciete structuur, zoals Markdown, HTML of Word, kun je op de koppen splitsen: ```typescript function markdownChunk(markdown: string): Chunk[] { const sections = markdown.split(/^#{1,3} /m); const chunks: Chunk[] = []; for (const section of sections) { if (!section.trim()) continue; const lines = section.split(" "); const title = lines[0].trim(); const content = lines.slice(1).join(" ").trim(); if (estimateTokens(content) > 600) { const subChunks = recursiveChunk(content, 400, 50); subChunks.forEach((chunk, i) => { chunks.push({ title: `${title} (${i + 1})`, content: chunk }); }); } else if (content) { chunks.push({ title, content }); } } return chunks; } ``` Structuur-gebaseerde chunking behoudt de semantische eenheid van secties. Dat is bijzonder effectief voor technische documentatie en wiki's, waar de auteur de logische grenzen al heeft aangegeven. ## Strategie 4: Semantisch chunken Semantisch chunken groepeert zinnen die inhoudelijk bij elkaar horen, op basis van embedding-gelijkenis: ```typescript async function semanticChunk(text: string, thresholdPercentile = 95): Promise { const sentences = text.match(/[^.!?]+[.!?]+/g) ?? [text]; const embeddings = await Promise.all(sentences.map(embedText)); const similarities: number[] = []; for (let i = 0; i < embeddings.length - 1; i++) { similarities.push(cosineSimilarity(embeddings[i], embeddings[i + 1])); } const threshold = percentile(similarities, 100 - thresholdPercentile); const chunks: string[] = []; let current = sentences[0]; for (let i = 1; i < sentences.length; i++) { if (similarities[i - 1] < threshold) { chunks.push(current); current = sentences[i]; } else { current += " " + sentences[i]; } } if (current) chunks.push(current); return chunks; } ``` Semantisch chunken klinkt aantrekkelijk, maar het is traag (een embedding-aanroep per zin) en duur voor grote documentsets. Recente benchmarks uit 2026 nuanceren bovendien het idee dat het altijd beter is: op veel praktische documentsets haalt recursief splitsen op 512 tokens een hogere recall dan semantisch chunken. Semantisch chunken wint vooral bij lange lopende tekst zonder duidelijke structuur, zoals onderzoeksartikelen, transcripties en boeken, waar de topic-grenzen belangrijker zijn dan de opmaak. :::warn title="Niet automatisch beter" Kies semantisch chunken niet omdat het de meest geavanceerde methode lijkt. Het kost ongeveer tien keer zoveel rekentijd. Zet het pas in als een meting met RAGAS aantoont dat de winst in precisie of recall de extra kosten waard is. ::: ## Metadata toevoegen aan chunks Chunks met metadata zijn veel waardevoller, omdat je daarmee kunt filteren, citeren en de herkomst terugvindt: ```typescript interface EnrichedChunk { id: string; content: string; metadata: { source_file: string; section_title?: string; page_number?: number; chunk_index: number; total_chunks: number; created_at: string; }; } function enrichChunks(chunks: string[], source: string): EnrichedChunk[] { return chunks.map((content, i) => ({ id: `${source}-chunk-${i}`, content, metadata: { source_file: source, chunk_index: i, total_chunks: chunks.length, created_at: new Date().toISOString(), }, })); } ``` ## Strategie vergelijking | Strategie | Kwaliteit | Snelheid | Kosten | Wanneer | |-----------|-----------|----------|--------|---------| | Vaste grootte | Laag | Snel | Laag | Prototypes | | Recursief | Gemiddeld tot hoog | Snel | Laag | Algemeen gebruik, standaardkeuze | | Structuur-gebaseerd | Hoog | Gemiddeld | Laag | Gestructureerde docs | | Semantisch | Hoog op lange prosatekst | Traag | Hoog | Kwaliteitskritisch, lange lopende tekst | ## In de praktijk: een werkbaar startpunt Wil je niet alle vier de strategieën uitproberen, volg dan deze volgorde: :::howto title="Zo kies je een chunking-aanpak" 1. Begin met **recursief chunken** op 512 tokens en 50 tokens overlap. 2. Heeft je bron duidelijke koppen (Markdown, HTML, handleidingen), schakel dan over op **structuur-gebaseerde chunking** per sectie. 3. Voeg aan elke chunk **metadata** toe, minimaal `source_file`, `section_title` en `chunk_index`. 4. Meet de retrieval-kwaliteit met **RAGAS** (context precision en context recall) op je eigen vragen. 5. Pas alleen semantisch chunken of een kleinere chunk-grootte toe als de meting laat zien dat dit echt helpt. ::: :::faq ### Wat is de optimale chunk-grootte? Dat hangt af van je use case. Voor korte feitelijke vragen werkt 128 tot 256 tokens goed, voor complexere vragen eerder 512 tot 1024 tokens. Test met je eigen data en evalueer de uitkomst met RAGAS. ### Moet ik de titels toevoegen aan de chunk-tekst? Ja, vaak wel. Een chunk als "Het geldt voor alle medewerkers" is zonder context betekenisloos. Door de sectietitel als prefix mee te geven verbeter je de retrieval. ### Hoe ga ik om met tabellen en code? Behandel tabellen en code-blokken als atomaire eenheden en splits ze nooit op een grens. Voeg een korte prefix toe, zoals "Tabel:" of "Code:", zodat het embedding-model de aard van het fragment herkent. ### Wat is parent-child chunking? Je indexeert kleine chunks (de child) voor nauwkeurige retrieval, maar je retourneert de grotere bovenliggende chunk (de parent) als context aan het model. Zo combineer je de precisie van kleine chunks met de context van grote chunks. ### Is semantisch chunken altijd de beste keuze? Nee. Op veel gestructureerde of feitelijke documentsets presteert recursief splitsen op 512 tokens even goed of beter, terwijl het sneller en goedkoper is. Semantisch chunken loont vooral bij lange lopende tekst zonder duidelijke structuur, en alleen als een meting de winst bevestigt. ### Welke overlap moet ik aanhouden? Een overlap van 10 tot 20 procent van de chunk-grootte is een veilige standaard. Bij 512 tokens komt dat neer op ongeveer 50 tot 100 tokens overlap, genoeg om context op de grenzen te behouden zonder onnodig te dupliceren. :::