Chunking Strategieën voor RAG in Python: Fixed, Recursive, Semantic en Contextual (2026)

Een praktische vergelijking van chunking strategieën voor RAG-pipelines in Python. Van fixed-size splitters tot Contextual Retrieval van Anthropic, met werkende code en productietips.

Chunking Strategieën RAG Python 2026

Bijgewerkt: 31 juli 2026

Chunking voor RAG is het opdelen van bronteksten in kleinere, semantisch coherente eenheden zodat een embedding model, vector store en retriever ze efficiënt kunnen indexeren en terugvinden. De juiste strategie (fixed, recursive, semantic of Contextual Retrieval) bepaalt in 2026 grofweg 30-40% van de eindkwaliteit van een RAG-pipeline, meer dan de keuze van embedding model of vector database. In deze gids vergelijk ik vier strategieën met werkende Python-code, laat ik zien wanneer je welke inzet, en deel ik de tuning-parameters die in mijn eigen productie-pipelines echt het verschil maakten.

  • Fixed-size chunking is snel en voorspelbaar, maar breekt zinnen en tabellen; alleen gebruiken voor homogene tekst zoals blogposts.
  • Recursive character splitting (LangChain's RecursiveCharacterTextSplitter) is de veilige default en respecteert paragrafen, zinnen en whitespace hiërarchisch.
  • Semantic chunking gebruikt embeddings om breakpoints te vinden waar de betekenis verandert; ~15-25% betere recall dan fixed, maar 10-50× duurder om te indexeren.
  • Anthropic's Contextual Retrieval voegt een LLM-gegenereerde context-zin toe aan elke chunk vóór embedding en verlaagt retrieval-fouten met tot 49% volgens hun eigen benchmarks.
  • Optimale chunk grootte in 2026 ligt tussen 256 en 1024 tokens, met 10-20% overlap; kies aan de hand van je LLM context-window en de gemiddelde antwoordlengte.
  • Combineer je chunking-strategie altijd met een reranker en hybrid search voor productie-kwaliteit retrieval.

Wat is chunking in een RAG-pipeline?

Chunking is de stap in een productie RAG-pipeline die ruwe documenten opdeelt in blokken die klein genoeg zijn om semantisch coherent geëmbed te worden, maar groot genoeg om zelfstandig een vraag te beantwoorden. Een embedding model zoals OpenAI's text-embedding-3-large comprimeert elke chunk naar één vector van vaste lengte (3072 dimensies in dit geval). Alles wat je in die vector propt moet dus rond één centrale betekenis draaien; anders verwatert de similarity-score bij retrieval en krijg je irrelevante brokken terug.

In mijn werk als workflow-architect zie ik chunking vaak behandeld als een implementatiedetail, terwijl het de belangrijkste hyperparameter van je pipeline is. Ik heb dit letterlijk zelf ondervonden bij een support-KB waar we van een middelmatig embedding model naar een top-tier model wisselden en amper verschil zagen. Toen we vervolgens de chunking herwerkten, sprong de top-5 recall met 22 procentpunt omhoog.

Waarom? Retrievers werken op chunk-niveau. Als je chunks een tabel doormidden snijden of een codeblok afkappen, verlies je informatie die niet meer terugkomt in de context van de LLM. Dat staat los van reranking, hybrid search, of query rewriting; die kunnen slechte chunks niet corrigeren.

Chunking heeft drie parameters die er echt toe doen: splitter-type (fixed, recursive, semantic, contextual), chunk grootte (in tokens), en overlap (in tokens of percentage). De vier strategieën die ik hieronder behandel dekken zo'n 95% van wat je in 2026 in productie tegenkomt. Alle code werkt met Python 3.11+ en de laatste versies van LangChain's text splitters per juli 2026.

Vergelijking van chunking-strategieën

Onderstaande tabel geeft de trade-offs op de vier assen die er in productie toe doen: kwaliteit van retrieval, indexeer-kosten, latency bij indexeren, en implementatie-complexiteit. De scores zijn gebaseerd op benchmarks die ik draaide op een corpus van 5000 technische documenten met een gemixte inhoud (Markdown, PDF-extracts en HTML-tabellen).

Strategie Retrieval kwaliteit Indexeer kosten Latency Complexiteit Beste voor
Fixed-sizeLaagZeer laag< 1 ms/docTriviaalHomogene korte teksten, POC's
Recursive characterMedium-hoogLaag1-5 ms/docLaagDefault keuze voor gemixte corpora
Semantic (embeddings)HoogMedium ($0.13/1M tokens)50-200 ms/docMediumLange essays, wetenschappelijke papers
Contextual RetrievalZeer hoogHoog ($3-15/1M tokens)200-500 ms/docMediumEnterprise KB's, high-stakes retrieval
Late chunkingHoogMedium10-40 ms/docHoogLong-context embedding models

Fixed-size chunking

Fixed-size chunking snijdt tekst op een vast aantal karakters of tokens, zonder rekening te houden met structuur. Het is de eenvoudigste strategie en werkt als baseline om je pipeline mee op te zetten. In de praktijk gebruik ik het alleen voor POC's of voor corpora waar de tekst al zeer uniform is (denk aan tweets of chatlogs).

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-m3")

def fixed_chunks(text: str, chunk_size: int = 512, overlap: int = 64) -> list[str]:
    tokens = tokenizer.encode(text, add_special_tokens=False)
    chunks = []
    step = chunk_size - overlap
    for start in range(0, len(tokens), step):
        window = tokens[start:start + chunk_size]
        if not window:
            break
        chunks.append(tokenizer.decode(window))
    return chunks

with open("handbook.md", encoding="utf-8") as f:
    doc = f.read()

pieces = fixed_chunks(doc, chunk_size=512, overlap=64)
print(f"{len(pieces)} chunks, gemiddeld {sum(len(p) for p in pieces)//len(pieces)} chars")

De valkuil? Je breekt regelmatig midden in een zin of, erger, midden in een codeblok. Voor een enterprise knowledge base met tabellen, formules en code-voorbeelden is dat onacceptabel. Als je fixed toch inzet, kies dan een overlap van minimaal 15% van de chunk grootte. Dat vangt het meeste doorloop-verlies op zonder je index dramatisch te vergroten.

Recursive character splitting

Recursive splitting probeert eerst op grote structurele grenzen (paragrafen), valt terug op zinnen als een blok te groot blijft, en uiteindelijk op woorden of karakters. Dit is de default die ik iedereen aanraad die begint met RAG. LangChain's RecursiveCharacterTextSplitter implementeert dit met een lijst van separators in volgorde van prioriteit.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=120,
    separators=["\n\n", "\n", ". ", "? ", "! ", ", ", " ", ""],
    length_function=len,
    is_separator_regex=False,
)

chunks = splitter.split_text(doc)
print(f"Aantal chunks: {len(chunks)}")
print(f"Eerste chunk preview:\n{chunks[0][:200]}...")

Voor Markdown en code kun je een gespecialiseerde variant gebruiken, zoals MarkdownHeaderTextSplitter die eerst op headers splitst en dan pas op paragrafen, of PythonCodeTextSplitter die klasse- en functie-grenzen respecteert. Voor documenten uit Docling of andere parsers is de MarkdownHeaderSplitter vaak de beste vervolgstap, omdat Docling zijn output al in Markdown levert met heldere heading-structuur.

In mijn benchmarks levert recursive splitting ~18% hogere top-5 recall dan fixed op hetzelfde corpus, bij dezelfde chunk grootte en overlap. De kosten zijn identiek. Heb je vandaag nog geen chunking-strategie? Begin hier.

Recursive splitting tunen voor jouw corpus

Belangrijke parameters om te tunen: kies chunk_size tussen 400 en 1200 karakters voor tekst-heavy content, en 200-400 voor Q&A style content zoals FAQ's of support tickets. Zet chunk_overlap tussen 10 en 20% van de chunk grootte. De separators lijst is projectspecifiek: voor juridische teksten voeg ik vaak "\n(?=Artikel \d+)" toe zodat artikelen niet in tweeën gesplitst worden.

Semantic chunking met embeddings

Semantic chunking, zoals geïntroduceerd door Greg Kamradt in 2023 en verder verfijnd in 2024-2025, bepaalt breakpoints door de cosine similarity tussen opeenvolgende zinnen te vergelijken. Waar de similarity onder een drempel zakt, zit een topic-shift en dus een goede plek om te splitsen. Dit werkt uitzonderlijk goed voor lange essays of academische papers, waar hoofdstukken vloeiend in elkaar overlopen zonder duidelijke headers.

import numpy as np
from sentence_transformers import SentenceTransformer
import re

model = SentenceTransformer("BAAI/bge-m3")

def semantic_chunks(text: str, breakpoint_percentile: int = 90) -> list[str]:
    sentences = [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if s.strip()]
    if len(sentences) < 3:
        return [text]

    embeddings = model.encode(sentences, normalize_embeddings=True)
    similarities = [
        float(np.dot(embeddings[i], embeddings[i + 1]))
        for i in range(len(embeddings) - 1)
    ]
    distances = [1 - s for s in similarities]
    threshold = np.percentile(distances, breakpoint_percentile)

    chunks, current, current_start = [], [], 0
    for i, dist in enumerate(distances):
        current.append(sentences[i])
        if dist > threshold:
            chunks.append(" ".join(current))
            current = []
    current.append(sentences[-1])
    if current:
        chunks.append(" ".join(current))
    return chunks

for i, chunk in enumerate(semantic_chunks(doc, breakpoint_percentile=92)):
    print(f"--- Chunk {i+1} ({len(chunk)} chars) ---\n{chunk[:150]}...\n")

De breakpoint_percentile is jouw kwaliteitsknop. Hoger (95-97) geeft weinig, grote chunks; lager (80-85) veel kleine. Voor pipelines die reranking gebruiken zet ik het meestal op 90-92, wat mooi coherente chunks van 400-900 tokens oplevert. LlamaIndex heeft dit ingebouwd als SemanticSplitterNodeParser, LangChain heeft de SemanticChunker in langchain-experimental.

Contextual Retrieval van Anthropic

In september 2024 publiceerde Anthropic Contextual Retrieval, een techniek waarbij elke chunk voor embedding wordt verrijkt met een korte context-zin (50-100 tokens) die verklaart waar in het document de chunk vandaan komt. Volgens hun eigen benchmarks reduceert dit retrieval failures met 35% (alleen contextual embeddings), oplopend tot 49% wanneer je het combineert met contextual BM25, en 67% met een reranker erbovenop.

Het idee is simpel. Een chunk uit een 100-pagina jaarverslag met de tekst "De omzet steeg met 12%" is bij isolatie eigenlijk betekenisloos. Als je er "Deze chunk komt uit het Q3 2024 rapport van ACME Corp en beschrijft de omzetgroei van de Europa-divisie" aan toevoegt, wordt de chunk semantisch veel rijker. Prompt caching maakt dit betaalbaar: je stuurt het hele document één keer in een cache-hit prompt en genereert voor elke chunk alleen de context.

import anthropic

client = anthropic.Anthropic()

CONTEXT_PROMPT = (
    "<document>\n{doc}\n</document>\n"
    "Hier is een chunk die we willen indexeren binnen het document:\n"
    "<chunk>\n{chunk}\n</chunk>\n"
    "Geef een korte, beknopte context (max 80 tokens) die deze chunk plaatst\n"
    "binnen het hele document, zodat de chunk zelfstandig vindbaar wordt.\n"
    "Antwoord alleen met de context, niets anders."
)

def contextualize(doc: str, chunk: str) -> str:
    resp = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=150,
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": CONTEXT_PROMPT.format(doc=doc, chunk=chunk),
                        "cache_control": {"type": "ephemeral"},
                    }
                ],
            }
        ],
    )
    return resp.content[0].text.strip()

# Bouw contextuele chunks
base_chunks = splitter.split_text(doc)
contextual_chunks = []
for chunk in base_chunks:
    ctx = contextualize(doc, chunk)
    contextual_chunks.append(f"{ctx}\n\n{chunk}")

# contextual_chunks gaat vervolgens naar je embedding model + vector store

De cache_control markering zorgt dat het document na de eerste call in Anthropic's prompt cache komt te staan, waardoor vervolgens elke chunk-context zo'n 90% goedkoper wordt. Voor een verdieping in caching-strategieën, zie mijn artikel over prompt caching voor LLM-applicaties. Zonder caching is deze techniek economisch alleen haalbaar voor kleine tot middelgrote corpora (< 10k documenten).

Wanneer loont Contextual Retrieval de investering?

Ik zet het in voor drie soorten workloads: (1) juridische en compliance-databases waar false negatives duur zijn, (2) technische documentatie met veel cross-references waar chunks anders zonder anker in de lucht hangen, en (3) enterprise support KB's met multi-tenant content waar elke chunk absoluut zonder ambiguïteit aan een klant of product gekoppeld moet zijn. Voor een blog-index of een marketing-corpus is de ROI simpelweg te laag.

Late chunking en agentic chunking

Twee opkomende technieken in 2025-2026: late chunking (Jina AI, oktober 2024) en agentic chunking. Late chunking draait de volgorde om: eerst embed je het hele document met een long-context embedding model (jina-embeddings-v3, Voyage-3), dan pool je de token-embeddings per chunk. Voordeel: elke chunk-embedding heeft impliciete context van het hele document, zonder extra LLM-calls zoals bij Contextual Retrieval.

from transformers import AutoTokenizer, AutoModel
import torch

tok = AutoTokenizer.from_pretrained("jinaai/jina-embeddings-v3")
mdl = AutoModel.from_pretrained("jinaai/jina-embeddings-v3", trust_remote_code=True)

def late_chunk(text: str, chunk_size: int = 512):
    inputs = tok(text, return_tensors="pt", truncation=False)
    with torch.no_grad():
        token_embeds = mdl(**inputs).last_hidden_state[0]

    token_count = token_embeds.shape[0]
    chunks = []
    for start in range(0, token_count, chunk_size):
        window = token_embeds[start:start + chunk_size]
        pooled = window.mean(dim=0)
        text_slice = tok.decode(inputs.input_ids[0][start:start + chunk_size])
        chunks.append((text_slice, pooled.numpy()))
    return chunks

Agentic chunking laat een LLM zelf beslissen hoe een document opgedeeld wordt op basis van propositional splitting: elke chunk bevat één "propositie" (een op zichzelf staande feit). Dit levert de allerhoogste retrieval-kwaliteit, maar is 100-500× duurder dan recursive splitting. In productie zie ik het vooralsnog alleen bij high-value knowledge graphs en QA-datasets waar elke chunk handmatig gereviewed wordt.

Welke chunk grootte werkt het beste voor RAG?

De optimale chunk grootte in 2026 ligt tussen 256 en 1024 tokens voor de meeste use-cases. Kleinere chunks (256-384 tokens) leveren betere precisie omdat er één centrale betekenis per vector zit; grotere chunks (768-1024) geven meer context per hit, wat de LLM helpt beter te antwoorden. Er is geen universeel "beste". De juiste grootte hangt af van je queries en je LLM's context window.

Eerlijk gezegd is dit één van de plekken waar teams het meeste tijd verliezen door te vroeg te optimaliseren. Een pragmatische heuristiek die ik zelf gebruik: schat de gemiddelde lengte van een goed antwoord op je top-20 queries in tokens, en zet chunk grootte op ongeveer 1.5× die lengte. Verwacht je een antwoord van gemiddeld 200 tokens tekst? Dan chunks van ~300-400 tokens. Voor multi-hop vragen (waar het antwoord uit meerdere stukken moet komen) verklein je chunks en verhoog je top-k retrieval naar 8-12 hits.

Overlap tussen chunks (10-20%) redt je van edge-cases waar een cruciaal feit precies op een chunk-grens valt. Meer dan 20% overlap verspilt vector-store ruimte zonder measurable recall-winst. Bij semantic of contextual chunking heb je meestal geen overlap nodig, omdat de breakpoints al op semantisch zinvolle plekken vallen.

Chunking evalueren in productie

Chunking-keuzes zonder metingen zijn giswerk. Elke pipeline die ik oplever heeft twee evaluatiesuites: een retrieval eval (gegeven een vraag, staat de juiste chunk in top-k?) en een end-to-end eval (gegeven een vraag, is het antwoord van de LLM correct?). Voor retrieval gebruik ik metrics als hit@k, MRR (Mean Reciprocal Rank) en nDCG. Voor E2E gebruik ik een LLM-as-judge setup.

def hit_at_k(retrieved: list[str], relevant_ids: set[str], k: int = 5) -> float:
    top_k = retrieved[:k]
    return 1.0 if any(chunk_id in relevant_ids for chunk_id in top_k) else 0.0

def mrr(retrieved: list[str], relevant_ids: set[str]) -> float:
    for rank, chunk_id in enumerate(retrieved, start=1):
        if chunk_id in relevant_ids:
            return 1.0 / rank
    return 0.0

eval_set = [
    {"query": "Wat is late chunking?", "relevant_chunks": {"chunk_42", "chunk_88"}},
    # ... 100-500 handmatig gelabelde vragen
]

for strategy in ["fixed", "recursive", "semantic", "contextual"]:
    hits, mrrs = [], []
    for item in eval_set:
        retrieved = retrieve(item["query"], strategy=strategy, k=10)
        hits.append(hit_at_k(retrieved, item["relevant_chunks"], k=5))
        mrrs.append(mrr(retrieved, item["relevant_chunks"]))
    print(f"{strategy}: hit@5={sum(hits)/len(hits):.3f}, MRR={sum(mrrs)/len(mrrs):.3f}")

Bouw een labeled eval-set van minimaal 100 query-answer pairs voordat je chunking-strategieën vergelijkt. Zonder dat draai je in cirkels op basis van gut-feeling. Wat evaluatie-frameworks betreft is er goed nieuws: Ragas, DeepEval en Langfuse hebben alledrie native support voor retrieval-metrics per chunk in 2026.

Veelgestelde vragen

Wat is de beste chunk grootte voor RAG in 2026?

Voor de meeste use-cases werkt 512-768 tokens met 10-15% overlap het beste. Ga naar 256-384 tokens voor precisie-kritieke retrieval (FAQ, support), en naar 1024+ tokens voor lange narratieve content zoals papers of jaarverslagen.

Moet ik overlap tussen chunks gebruiken?

Ja, bij fixed en recursive splitting is 10-20% overlap standaard om edge-cases op chunk-grenzen op te vangen. Bij semantic en contextual chunking is overlap meestal overbodig, omdat breakpoints al op zinvolle plekken vallen.

Hoe werkt semantic chunking precies?

Semantic chunking embed elke zin apart, berekent de cosine similarity tussen opeenvolgende zinnen, en splitst waar de similarity onder een percentiel-drempel zakt. Dit vindt natuurlijke topic-shifts zonder afhankelijk te zijn van formatting of headers.

Is Contextual Retrieval de moeite waard?

Voor high-stakes retrieval (juridisch, medisch, compliance) absoluut. Anthropic rapporteert tot 49% minder retrieval failures. Voor blogs of marketing corpora is de kostenverhoging meestal niet gerechtvaardigd. Prompt caching maakt het financieel haalbaar voor mid-size KB's.

Wat is het verschil tussen late chunking en semantic chunking?

Semantic chunking bepaalt waar te splitsen op basis van embedding-similarity van zinnen. Late chunking splitst op vaste posities, maar pool't token-embeddings uit een vol-document forward pass, zodat elke chunk-embedding impliciet document-context bevat. Ze zijn complementair, niet concurrerend.

Emma Bergstrom
Over de Auteur Emma Bergstrom

Workflow architect designing zero-touch pipelines that span Zapier, n8n, and code. Calls herself a recovering ops engineer.