Chunkingstrategieën in RAG-vergelijking: Alternatieven, afwegingen en voorbeelden
Vergelijking van chunkingstrategieën in RAG
Chunking is de meest onderschatte hyperparameter in Retrieval ‑ Augmenteerde Generatie (RAG): het bepaalt stilzwijgend wat je LLM “ziet”, hoe duur de ingesting wordt, en hoeveel van de contextwindow van de LLM je verbruikt per antwoord.
Dit artikel behandelt chunking als een engineering optimalisatieprobleem: doelen definiëren, een strategie kiezen, meten, en dan itereren.
Als je nieuw bent in RAG architectuur, begin dan met de hoofd Retrieval-Augmenteerde Generatie (RAG) Tutorial: Architectuur, Implementatie, en Productiehandleiding.

TL;DR (Executive summary)
RAG-systemen halen chunks op, niet documenten. Chunking bepaalt dus de eenheid van opvragen, de eenheid van embeddingkosten, en de eenheid van bewijs die je kunt tonen of citeren. In de oorspronkelijke RAG-formulering levert opvragen passages op voor generatie; de grenzen van de passage zijn effectief de grenzen van de chunk.
Een goede chunkingstrategie zoekt een Paretofront over: opvragingskwaliteit (herkennen/precisie van bewijs), samenhang (chunks moeten interpreteerbaar zijn), en kosten (embedding, opslag en querylatency). Er is geen globaal optimale chunkgrootte of methode, en productiesystemen mengen vaak strategieën (bijvoorbeeld structuurbewuste chunking voor PDFs + semantisch bewuste splitsen voor proza + AST chunking voor code).
Voor de meeste “documentatie QA” en interne kennisbases is een veilige standaard een structuurrespecterende recursieve splitter met matige overlap (om grensverlies te verminderen), ondersteund door een
vectoropslag
met metadatafiltering en optioneel reranking. LangChain’s RecursiveCharacterTextSplitter is een veelvoorkomende implementatie van deze hiërarchische-scheider-idee; overlap bestaat specifiek om informatieverlies te verminderen wanneer relevante context wordt gesneden aan grenzen.
Wanneer documenten een sterke structuur hebben (PDFs met koppen, tabellen, lijsten, legenden), kan elementgebaseerde / structuurbewuste chunking beter presteren dan token-aantallen snijden terwijl het minder chunks produceert. Een 2024 studie over SEC-aangiften vond dat elementtypegebaseerde chunking RAG-resultaten verbeterde en ook het aantal chunks (en dus vectoren) ongeveer met de helft verminderde in vergelijking met structuur-onafhankelijke methoden—het indexkosten verminderde en mogelijk de querylatency verbeterde.
Als je meer up-front rekenkracht kunt permitteren, kan semantische chunking (splitsen op themaoverschrijdingen met embedding-afstand) aanzienlijk de opvragingsnauwkeurigheid verbeteren voor narratief tekst en gemengde themapagina’s. Oudere themasegmentatiealgoritmen zoals TextTiling tonen het algemene principe: sterke vocabulaire/semantische overschrijdingen zijn goede grenscandidates.
Voor zeer lange, intern kruisverwijzende materialen (beleidsregels, RFCs, standaarden, grote handleidingen), kan hiërarchische chunking + hiërarchische opvragen/vereniging (ouder/kinderknopen) grotere aaneengesloten context op aanvraag herstellen. LlamaIndex’s hiërarchische knoopparser produceert grof-voor-fijn chunkhiërarchieën, en de AutoMergingRetriever kan bladerknopen in ouderknopen verenigen bij opvragen als genoeg gerelateerde kinderknopen zijn opgevraagd.
Chunkingdoelen en trade-offs
Chunking is niet alleen “tekst splitsen zodat het past in een embeddingmodel”. Het beheerst meerdere downstream en operationele gedragingen.
Opvragingsgranulariteit vs opvragingsrui. Kleine chunks verhogen de kans dat de exacte zin met het antwoord opvragbaar is (hogere potentieel herkennen bij vast top-k). Maar ze produceren ook meer vectoren, wat de indexgrootte verhoogt en soms “bijna overeenkomende” resultaten oplevert die semantisch gelijk zijn maar niet feitelijk bewijselijk (lagere precisie). Dense retrievers zoals DPR zijn ontworpen rond het effectief opvragen van passages voor QA, wat aantoont dat passagegrenzen belangrijk zijn voor eind- tot-eind QA-prestaties.
Contextsamenhang vs grensverlies. Samenhangende chunks helpen de LLM correct te redeneren en verminderen hallucinaties door volledige lokale context te bieden (definities, beperkingen, voorwaarden). Overlap verminderd grensverlies maar creëert duplicaattekst, wat kan leiden tot redundante opvragingsresultaten en uitgebreide promptlengte als je niet deduplicate/verenigt.
Embedding- en indexkosten. Embeddingkosten zijn meestal evenredig met het aantal ingebedde tokens, en de ingestingstijd schaalt met het aantal chunks (plus vector DB schrijfoverhead). Voor OpenAI embeddings zijn aanvragen beperkt op per-input maximaal aantal tokens (8192 tokens voor alle embeddingmodellen) en een maximaal totaal aantal tokens per aanvraag (300.000 tokens). Voor grote corpora kan de Batch API de kosten met ongeveer 50% verminderen met een asynchrone, 24-uur omloop—handig voor backfills en periodieke herindexering.
Vectorindexgrootte, RAM en latency. Meer chunks betekent meer vectoren en mogelijk meer geheugen en langzamere queries (afhankelijk van de indexsoort). FAISS stelt expliciet indexontwerp voor als een set trade-offs tussen zoektijd, zoekkwaliteit en geheugen per ingebedde vector; het biedt ook GPU-implementaties voor snelle exacte en benaderde zoekopdrachten.
Downstream LLM promptlengte / contextwindowgebruik. Het opvragingsresultaat wordt promptbudget. Een chunkingstrategie die consistent “net genoeg” context oplevert kan de antwoordkwaliteit verbeteren en kosten verminderen. Aan de andere kant verhoogt overlap en te grote chunks de promptlengte. In de praktijk stel je vaak af: (chunkgrootte, overlap, top-k, reranking/vereniging) samen.
Update/ingestingkosten en deduplicatie. Chunking beïnvloedt hoe duur het is om gegevens bij te werken. Kleine chunks maken gedeeltelijke updates goedkoper (je kunt alleen de gewijzigde sectie herembedden) maar maken ook deduplicatie moeilijker als overlatende of bijna-duplicatieve chunks prolifereren.
Waar chunking zich bevindt in de RAG-werkstroom

Chunkingstrategieën en alternatieven
Hieronder staan de belangrijkste chunkingfamilies die je tegenkomt in moderne RAG. In de praktijk meng je vaak twee: structuur-georiënteerde chunking (documentgrenzen respecteren) plus token-budgetverplichting (zorg dat chunks passen in je embedding- en promptbudgets).
Vaste grootte chunking
Wat het is. Tekst splitsen in gelijke blokken op basis van karakters of tokens.
Waarom het bestaat. Het is eenvoudig, snel, voorspelbaar en makkelijk te paralleliseren. Het is ook de eenvoudigste strategie voor streaming ingestion waarbij je geen volledige documentcontext hebt.
Waar het mislukt. Het negeert grenzen (zinnen, secties, codeblokken) en kan definities breken of “vraag/antwoordparen” over chunks splitsen, wat het opvragingsfoutpercentage verhoogt.
Operationele profiel. Laagste ingestingscomplexiteit; voorspelbaar aantal chunks; makkelijkste caching. Maar je hebt meestal overlap (onderaan) nodig om grensverlies te vermijden.
Overlap chunking
Wat het is. Elke strategie waarbij opeenvolgende chunks een vaste overlapregio delen (bijvoorbeeld 10–20% van de chunkgrootte). Overlap is standaard in veel frameworks omdat het informatieverlies vermindert wanneer context wordt verdeeld.
Waarom het belangrijk is. Overlap is effectief een “zachte grens”—het laat opvragen een feit dat over een grens ligt.
Kosten en valkuilen. Meer ingebedde tokens; meer duplicatetekst in de index; hoger risico op het opvragen van meerdere bijna identieke chunks tenzij je deduplicate tijdens het opvragen (bijvoorbeeld samenvoegen op bronoffsets of MMR gebruiken).
Zin- en paragraafgebaseerd chunking
Wat het is. Tekst splitsen op zin- of paragraafgrenzen en vervolgens zinnen/paragrafen in chunks pakken tot een tokenbudget.
Waarom ingenieurs het leuk vinden. Het verbetert samenhang voor natuurlijke taal en is robuust voor documenten met conventionele interpunctie en spaties.
Tooling. NLTK’s sent_tokenize() gebruikt standaard Punkt zinbegrenzing, en spaCy biedt regelgebaseerde zinbegrenzingsgereedschappen zoals Sentencizer (handig wanneer je zinsplitsing wilt zonder een volledig afhankelijkheidsmodel).
Mislukkingsmodi. Niet-standaard interpunctie (logboeken, chattranscripten), tabellen, code en lijsten kunnen zinssegmentatieaannames breken.
Glijdend venster chunking
Wat het is. Chunken maken met een vaste venstergrootte en een stap (stapgrootte). Dit is de “systematische overlap” versie van chunking.
Wanneer het goed is. Tijdreeks tekst, transcripten, chatlogboeken, vergaderminuten—alles waar relevante feiten kunnen verschijnen in lokale omgevingen en je robuuste herkennen wilt.
Wanneer het slecht is. Het versterkt redundantie en kan duur zijn op schaal. Het tendingt ook tot het opvragen van redundante vensters tenzij deduped.
Recursief / scheiderhiërarchie chunking
Wat het is. Begin met grote “natuurlijke” scheiders (bijvoorbeeld \n\n voor paragrafen) en splits recursief in kleinere eenheden (zinnen, spaties) alleen als nodig om onder een groottebudget te blijven. LangChain beschrijft dit gedrag expliciet: een recursieve splitter probeert grotere eenheden intact te houden en valt pas terug op kleinere scheiders als een eenheid nog steeds de chunkgrootte overschrijdt.
Waarom het een sterke standaard is. Het respecteert structuur zonder dat complexe documentverwerking vereist is. Het is een pragmatische sweet spot voor Markdown, HTML als tekst en documentatie.
Belangrijke afstemmingsknopen. chunk_size, chunk_overlap, en de length_function (karakters vs tokens), plus aangepaste scheiders voor meertalige codebases.
Semantisch (embeddingbewuste) chunking
Wat het is. Detecteer themaverschuivingen met behulp van semantische representaties (embeddings) en splits waar de gelijkenis daalt. Dit spiegelt klassieke segmentatieideeën zoals TextTiling, die gebruikmaakt van shifts in lexicaal samenhang om subthema grenzen te vinden.
Waarom het kan uitpresteren op basis van grootte. Je stopt met splitsen op willekeurige tokenaantallen en align chunks met themagrenzen—vaak het opvragingsprecisie verbeteren voor meertopic documenten (blogs, designdocumenten, tickets, incidentrapporten).
Kosten. Je hebt mogelijk extra embeddings nodig tijdens chunking (zinsniveau of paragraafniveau embeddings) voor eindchunk embeddings. Dat kan de aantal embeddings verdubbelen of drievoudig maken tenzij je tussenliggende embeddings hergebruikt.
Praktische truc. “Semantisch bewuste verpakking”: bereken zinsembeddings één keer, groepeer zinnen in thema-coherente segmenten, en embed vervolgens elk eindsegment.
Hiërarchisch chunking (ouder/kinder)
Wat het is. Bouw een multigranulariteitsrepresentatie: grove ouderchunks (bijvoorbeeld sectie-omvang) met fijne kinderchunks (bijvoorbeeld paragraaf-omvang). LlamaIndex’s hiërarchische knoopparsing produceert standaard “grof-voor-fijn” hiërarchieën (bijvoorbeeld 2048 → 512 → 128 token schalen), en de AutoMergingRetriever kan kinderknopen in ouderknopen verenigen tijdens opvragen als genoeg gerelateerde kinderknopen zijn opgevraagd.
Waarom het helpt. Het vermijdt het kiezen tussen “kleine chunks voor herkennen” en “grote chunks voor samenhang” door beide op te slaan en te selecteren tijdens het queryen.
Kosten. Meer complexe ingestings- en opvragingslogica, plus potentieel meer opslag (omdat je meerdere granulariteiten opslaat).
Adaptief / LLM-gebaseerd chunking
Wat het is. Gebruik een LLM om te beslissen over chunkgrenzen (en optioneel samenvattingen of contextuele koppen genereren). Weaviate beschrijft LLM-gebaseerd chunking expliciet als het gebruik van een LLM om semantisch samenhangende chunks te maken, in plaats van op vastgestelde regels of embeddinggelijkenis te vertrouwen.
Wanneer het waard is. Hoogwaardige corpora waarin correctheid overheerst boven kosten (recht, naleving, ondersteuningsrunbooks), en waarin documenten ongeordend, heterogeen en slecht gesegmenteerd zijn.
Risico’s. Kosten, latency en niet-determinisme. Je zult caching, deterministische decoding en regressietests willen (zie evaluatiereeks).
Structuur- en elementgebaseerd chunking (documenten zijn niet platte tekst)
Wat het is. Parseer het document in elementen (titels, paragrafen, lijsten, tabellen, legenden) met behulp van een documentverstandlaag, en chunk dan met behulp van die elementen. Unstructured’s chunkingfuncties gebruiken expliciet metadata en documentelementen (die worden gegenereerd door partitionering) om chunks voor RAG te maken. Docling’s HierarchicalChunker maakt chunks per gedetecteerd documentelement en voegt structurele metadata toe zoals koppen/legenden.
Bewijs uit recente werkzaamheden. Een 2024 studie over SEC-aangiften stelt dat paragraaf-only chunking de documentstructuur negeert en voorstelt chunking op structurele elementen; het rapporteert verbeterde RAG-resultaten en minder chunks/vectoren dan structuur-onafhankelijke aanpakken.
Waarom het belangrijk is voor multimodale. Tabellen, figuren en legenden bevatten vaak de grondwaarheid. “Flattenen” ze in platte tekst kan signalen vernietigen die opvraging anders zou gebruiken.
Codebewuste chunking (AST/structuur)
Wat het is. Chunk code op syntactische eenheden (functies, klassen, modules), met optionele inbegrip van docstrings en opmerkingen.
Waarom het belangrijk is. Vaste grootte token splitsen neigen ertoe functies in tweeën te snijden en docstrings van implementaties te scheiden—slecht voor codezoekopdrachten en “leg deze functie uit” RAG-gebruiksgevallen.
Implementatieopties. Voor Python is de ingebouwde ast module vaak voldoende. Voor meertalige repositories zijn tree-sitter-gebaseerde chunkers gebruikelijk.
Evaluatie dimensies en hoe chunkingstrategieën te vergelijken
Chunking moet worden getest als een systeemonderdeel.
Opvragingskwaliteitsmetrieken
Gebruik standaard IR-metrieken voor de opvragingslaag:
- Recall@k / Precision@k: Bevatte de top-k het goudbewijs?
- MRR / nDCG: Stond het goudbewijs hoog in de ranglijst?
BEIR is een algemeen gebruikte heterogene benchmark voor IR-evaluatie over taken/domänen, en benadrukt trade-offs tussen sparza, dicht, late-interactie en rerankingbenaderingen.
Chunking beïnvloedt deze metrieken omdat het bepaalt wat telt als “een relevant opgevraagd item”.
Eind- tot-eind RAG-antwoordkwaliteitsmetrieken
Als je QA of assistenten bouwt, zijn opvragingsmetrieken nodig maar niet voldoende. Je hebt ook nodig:
- Contextherkennen / precisie: of opgevraagde contexten relevante bewijs bevatten en ongegrond ruis vermijden.
- Faithfulness: of het gegenereerde antwoord wordt ondersteund door de opgevraagde context.
RAGAS biedt concrete definities en implementaties voor “faithfulness” en andere RAG-gerichte metrieken.
Systeemkosten en prestatiedimensies
Chunking verandert deze hefbomen:
Latency (p50/p95). Query latency neemt meestal toe met meer vectoren en meer postverwerking. Je vectorindex is ook belangrijk: FAISS-indexsoorten verhouden zich tot zoektijd, kwaliteit, geheugen en training/addingtijd.[^faiss]
Embeddingkosten en doorvoer. OpenAI embeddings worden per token in rekening gebracht; de embeddings API heeft expliciete per-input en per-aanvraaglimieten.[^openai_embed_create] Voor offline ingestion verlaagt de Batch API de kosten en biedt hogere quota in ruil voor niet-realtime omloop.[^openai_batch]
Indexgrootte en geheugen. Ongeveer, opslaan van N float32 vectoren van dimensie d kost ~4 * N * d bytes alleen voor de ruwe vectoren (plus metadata + indexoverhead). Chunking beïnvloedt N. Embeddingdimensie beïnvloedt d, en OpenAI’s embeddings API stelt u in staat om de uitvoerdimensie te bepalen via het dimensions parameter.[^openai_embed_create]
LLM promptbudget. Grotere chunks en overlap vergroten prompttokens. Dit kan latency en kosten verhogen en “verloren in het midden” stijl falenmodes waarbij modellen minder aandacht geven aan sommige context. In de praktijk stel je vaak:
- kleine chunks opvragen,
- samenvoegen/dedupliceren,
- optioneel samenvatten,
- een compact bewijsset naar de LLM sturen.
Update/ingestingkosten. Kleine chunks toestaan gedeeltelijke herembedding maar vergroten administratie. Voor streaming ingestion voorkeur geven aan deterministische, incrementele chunking (vast of glijdend venster) en stabiele ID’s (document_id, offsetbereiken, hash) toevoegen.
Experimenteel ontwerp: een pragmatische benchmarkloop
Een herhaalbare chunkingbenchmark heeft meestal:
- Een vaste corporussnapshot + vaste set queries met goudbewijs (of tenminste verwachte antwoordspannen).
- Een vaste embeddingmodel en vectorindexconfiguratie.
- Een “alleen opvragen” evaluatie (recall@k, nDCG) plus “RAG” evaluatie (faithfulness, antwoordrelevantie).
- Kostenmeting: #chunks, ingebedde tokens, $/maand opslag, p95 querylatency, prompttokens.
Het Unstructured SEC-aangiften paper is een goed voorbeeld van het evalueren van chunkingstrategieën met zowel opvragingsgerichte metrieken als QA-nauwkeurigheidsmaatstaven.
Praktische richtlijnen, beslissingsmatrix en aanbevolen standaarden
Aanbevolen standaarden die verrassend goed werken
Als je een robuuste “dag 1”-strategie nodig hebt voor algemene documentatie QA:
- Licht analyseren: behoud koppen en basismetadata (bron, sectietitel, URL/naam, tijdstip).
- Chunk met een recursieve scheider splitter (paragraaf → zin → woord), met matige overlap.
- Embed met een sterke algemene embeddingmodel.
- Index met metadata (doc id, sectie, ACLs) en deduplicate tijdens het opvragen.
- Voeg reranking of hiërarchische vereniging alleen toe als je evaluatie een gap toont.
Dit correspondeert met hoe veelvoorkomende RAG-frameworks chunkoverlap en structuurrespecterende splitsing beschrijven.
Welke chunkingmethode te gebruiken - Beslissingsmatrix

| Gebruiksaanwijzing | Aanbevolen chunkingstandaard | Belangrijke parameters om af te stemmen | Gewone mislukking | Upgradepad |
|---|---|---|---|---|
| Korte vorm QA over documenten (FAQs, interne wiki) | Recursief/scheider chunking + overlap | chunk_size, overlap, top-k |
Mist kruiszinnenbewijs aan grens | Voeg semantisch chunking of reranker toe |
| Langere vorm QA (beleidsregels, standaarden, handleidingen) | Hiërarchisch chunking + verenigingsopvraging | ouder/kinder grootte, verenigingsschakel | Opgaat kleine fragmenten; LLM mist volledige context | Automatisch verenigen/hiërarchische opvragen |
| Samenvatten (per document / per sectie) | Structuurbewuste chunks (secties) | sectie detectie, max tokens | Samenvattingen missen kruissectielinks | Hiërarchische samenvatten + sectiegrafiek |
| Codezoekopdrachten & “leg deze functie uit” | AST/functieniveau chunks | include docstring/opmerkingen, max tokens | Functie gesplitst; verliest signature/gebruik | Repo-bewuste hiërarchie (module→klasse→functie) |
| Multimodale PDFs (tabellen/figuren) | Elementgebaseerd chunking (titel/tabel/legenda bewust) | tabelserialisatie, legenda samenvoegen | Tabelinhoud verloren of verkeerd | Gebruik Docling/Unstructured + gestructureerde serializers |
| Streaming ingestion (logboeken, chats, tickets) | Glijdend venster of vaste grootte tokens | venster, stap, dedup | Over-opvragen van redundante vensters | Voeg semantische grensdetectie toe op batches |
chunking - Kwalitatieve prestatievergelijking
Behandel dit als “verwachte richting van verandering” (valideer met jouw eigen data).
| Strategie | Potentieel opvragingsnauwkeurigheid | Samenhang van opgevraagde context | Ingestingscomplexiteit | Aantal vectoren / indexgrootte | Embeddingkosten | Querylatencyimpact | Beste voor |
|---|---|---|---|---|---|---|---|
| Vaste grootte (geen overlap) | Gemiddeld | Laag | Laag | Gemiddeld | Laag | Gemiddeld | snelle prototypen, homogene tekst |
| Vaste grootte + overlap | Gemiddeld–Hoog | Gemiddeld | Laag | Hoog | Gemiddeld–Hoog | Gemiddeld–Hoog | QA waarbij grensverlies schade doet |
| Zin/paragraafverpakking | Hoog (proza) | Hoog | Gemiddeld | Gemiddeld | Gemiddeld | Gemiddeld | documenten, artikelen, nette proza |
| Glijdend venster | Hoog herkennen | Gemiddeld | Gemiddeld | Zeer hoog | Zeer hoog | Hoog | transcripten, logboeken, chat |
| Recursief/scheider | Hoog | Hoog | Gemiddeld | Gemiddeld | Gemiddeld | Gemiddeld | “standaard” documenten RAG |
| Semantisch chunking | Hoog–Zeer hoog | Hoog | Hoog | Gemiddeld | Hoog | Gemiddeld | meertopic pagina’s, narratief tekst |
| Hiërarchisch (ouder/kinder) | Zeer hoog | Zeer hoog | Hoog | Hoog | Hoog | Gemiddeld | lange handleidingen / standaarden |
| LLM-gebaseerd/adapter | Zeer hoog | Zeer hoog | Zeer hoog | Gemiddeld | Zeer hoog | Gemiddeld–Hoog | hoogwaardige corpora |
| Element-/structuurgebaseerd | Hoog–Zeer hoog | Hoog | Hoog | Laag–Gemiddeld | Gemiddeld | Gemiddeld | PDFs, rapporten, tabellen, gemengde lay-outs |
| Codebewust (AST) | Hoog (code) | Hoog | Gemiddeld | Gemiddeld | Gemiddeld | Gemiddeld | codezoekopdrachten, repo-assistenten |
DevOps en hardware notities (vaak over het hoofd gezien)
Chunkingkeuzes beïnvloeden hoeveel infrastructuur je nodig hebt:
- Kleine chunks → meer vectoren → grotere indexen en meer RAM/disk. Voor zelfgehoste FAISS kan dit sharding of disk-gebaseerde indexen dwingen.
- Als je lokaal embed, wordt embeddingdoorvoer een GPU-schedulingsprobleem; als je via API embed, wordt tokenvolume een FinOps probleem (Batch API is je vriend voor backfills).
- Sommige engines (FAISS) bieden GPU-geaccelereerde zoekopdrachten; dit kan kosten verplaatsen van RAM-begrensde CPU naar GPU-geheugen en PCIe doorvoer.
- Structuurbewuste parsing (PDF lay-out, OCR, tabeluitvoer) is vaak CPU-begrens en kan embeddingkosten overschrijden voor gescande documenten; budgetteer het apart.
Chunking - Python-voorbeelden
Alle voorbeelden zijn ontworpen om leesbaar en uitvoerbaar te zijn. Als u een API-sleutel of een draaiende database nodig heeft, is dat duidelijk uit de code.
Gedeelde hulpmiddelen: token tellen en stabiele chunk-IDs
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Any, Iterable, Optional
from transformers import AutoTokenizer # pip install transformers
@dataclass(frozen=True)
class Chunk:
text: str
meta: dict[str, Any]
def sha1_id(*parts: str) -> str:
h = hashlib.sha1()
for p in parts:
h.update(p.encode("utf-8"))
h.update(b"\x1e")
return h.hexdigest()
# Gebruik een tokenizer die ongeveer overeenkomt met de tokenisatie van uw LLM/embedding.
TOKENIZER = AutoTokenizer.from_pretrained("bert-base-uncased")
def token_len(text: str) -> int:
return len(TOKENIZER.encode(text, add_special_tokens=False))
Vaste grootte token chunking
def chunk_fixed_tokens(
text: str,
*,
chunk_size: int = 512,
) -> list[Chunk]:
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
for i in range(0, len(token_ids), chunk_size):
window = token_ids[i : i + chunk_size]
chunk_text = TOKENIZER.decode(window)
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "fixed_tokens", "start_token": i, "end_token": i + len(window)},
)
)
return out
Vaste grootte met overlap + schuivende venster
def chunk_sliding_window(
text: str,
*,
window_tokens: int = 512,
stride_tokens: int = 384, # kleinere stap = meer overlap
) -> list[Chunk]:
assert 1 <= stride_tokens <= window_tokens, "stap moet binnen (0, venster]"
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
start = 0
while start < len(token_ids):
end = min(start + window_tokens, len(token_ids))
window = token_ids[start:end]
out.append(
Chunk(
text=TOKENIZER.decode(window),
meta={"strategy": "sliding_window", "start_token": start, "end_token": end},
)
)
if end == len(token_ids):
break
start += stride_tokens
return out
Zin gebaseerd chunking (NLTK) met token-budget verpakking
# pip install nltk
import nltk
from nltk.tokenize import sent_tokenize
nltk.download("punkt", quiet=True)
def chunk_by_sentences_nltk(
text: str,
*,
max_tokens: int = 512,
overlap_sentences: int = 1,
) -> list[Chunk]:
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
def flush():
nonlocal buf, buf_tokens
if not buf:
return
chunk_text = " ".join(buf).strip()
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "sentences_nltk", "sent_count": len(buf)},
)
)
# Overlap door laatste N zinnen te behouden
if overlap_sentences > 0:
buf = buf[-overlap_sentences:]
buf_tokens = token_len(" ".join(buf))
else:
buf, buf_tokens = [], 0
for s in sents:
s_tokens = token_len(s)
# Als een enkele zin het budget overschrijdt, valt terug op vaste grootte chunking voor dat fragment
if s_tokens > max_tokens:
flush()
out.extend(chunk_fixed_tokens(s, chunk_size=max_tokens))
continue
if buf_tokens + s_tokens > max_tokens and buf:
flush()
buf.append(s)
buf_tokens += s_tokens
flush()
return out
Zin gebaseerd chunking (spaCy) wanneer u regelgebaseerde of modelgebaseerde SBD wilt
# pip install spacy
# python -m spacy download en_core_web_sm
import spacy
def chunk_by_sentences_spacy(
text: str,
*,
max_tokens: int = 512,
) -> list[Chunk]:
# Voor lichte regelgebaseerde splitsingen (geen afhankelijkheidsanalyse), gebruik sentencizer.
nlp = spacy.blank("en")
nlp.add_pipe("sentencizer") # regelgebaseerde zinsgrensdetectie
doc = nlp(text)
sents = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
return chunk_by_sentences_nltk(" ".join(sents), max_tokens=max_tokens, overlap_sentences=1)
Rekursief scheiding chunking (LangChain)
# pip install langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter
def chunk_recursive_langchain(
text: str,
*,
chunk_size: int = 1200,
chunk_overlap: int = 150,
) -> list[Chunk]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=token_len, # token-aware budgeting
separators=["\n\n", "\n", ". ", " ", ""], # aanpassen aan uw inhoud (bijv. code)
)
pieces = splitter.split_text(text)
return [
Chunk(text=p, meta={"strategy": "recursive_langchain", "chunk_size": chunk_size, "overlap": chunk_overlap})
for p in pieces
]
Semantisch chunking met embedding gelijkenis (OpenAI embeddings)
Deze aanpak berekent embeddings voor kandidaat-eenheden (zinnen/paragrafen), en vindt dan semantische “brekingspunten”.
# pip install openai numpy
import os
import numpy as np
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY"))
def embed_texts_openai(texts: list[str], *, model: str = "text-embedding-3-small") -> np.ndarray:
# OPMERKING: voor grote batches, respecteer aanvraag tokenlimieten en batchgrootte limieten.
resp = client.embeddings.create(model=model, input=texts)
embs = np.array([d.embedding for d in resp.data], dtype=np.float32)
return embs
def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
denom = (np.linalg.norm(a) * np.linalg.norm(b)) + 1e-8
return float(np.dot(a, b) / denom)
def chunk_semantic(
text: str,
*,
max_tokens: int = 800,
breakpoint_threshold: float = 0.70,
) -> list[Chunk]:
# 1) Start vanaf zin kandidaten
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
if len(sents) <= 1:
return [Chunk(text=text, meta={"strategy": "semantic", "note": "no_split"})]
# 2) Embed elke zin
embs = embed_texts_openai(sents)
# 3) Bereken gelijkenisverlagingen
sims = [cosine_sim(embs[i], embs[i + 1]) for i in range(len(sents) - 1)]
# 4) Creëer segmenten aan brekingspunten
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
for i, s in enumerate(sents):
# Voeg zin toe
s_tok = token_len(s)
if buf and buf_tokens + s_tok > max_tokens:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "max_tokens"}))
buf, buf_tokens = [], 0
buf.append(s)
buf_tokens += s_tok
# Bepaal brekingspunt na zin i (op basis van gelijkenis met volgende)
if i < len(sims) and sims[i] < breakpoint_threshold:
out.append(
Chunk(
text=" ".join(buf),
meta={"strategy": "semantic", "reason": "sim_drop", "sim_to_next": sims[i]},
)
)
buf, buf_tokens = [], 0
if buf:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "final"}))
return out
Hiërarchisch chunking + samenvoegen van ophalen (LlamaIndex)
# pip install llama-index llama-index-llms-openai
from llama_index.core import Document, StorageContext, VectorStoreIndex
from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes
from llama_index.core.retrievers import AutoMergingRetriever
from llama_index.core.storage.docstore import SimpleDocumentStore
def build_hierarchical_index(text: str):
docs = [Document(text=text)]
node_parser = HierarchicalNodeParser.from_defaults() # standaardwaarden zijn van grof naar fijn
nodes = node_parser.get_nodes_from_documents(docs)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)
leaf_nodes = get_leaf_nodes(nodes)
base_index = VectorStoreIndex(leaf_nodes, storage_context=storage_context)
base_retriever = base_index.as_retriever(similarity_top_k=6)
retriever = AutoMergingRetriever(base_retriever, storage_context, verbose=True)
return retriever
Element gebaseerd chunking voor PDFs (Docling)
# pip install docling
# OPMERKING: kwaliteit van PDF-parsen hangt af van uw omgeving (lettertypen, OCR, enz.)
from docling.document_converter import DocumentConverter
from docling.transforms.chunker.hierarchical_chunker import HierarchicalChunker
def chunk_pdf_docling(pdf_path: str) -> list[Chunk]:
converter = DocumentConverter()
doc = converter.convert(pdf_path).document # DoclingDocument
chunker = HierarchicalChunker()
doc_chunks = list(chunker.chunk(doc))
out: list[Chunk] = []
for c in doc_chunks:
# c.text bevat serialiseerde chunk inhoud; c.meta draagt structuurinformatie
out.append(Chunk(text=c.text, meta={"strategy": "docling_hierarchical", **dict(c.meta)}))
return out
Code-bewuste chunking voor Python (AST)
import ast
def chunk_python_by_ast(code: str, *, filepath: str = "<memory>") -> list[Chunk]:
tree = ast.parse(code)
out: list[Chunk] = []
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
start = getattr(node, "lineno", None)
end = getattr(node, "end_lineno", None)
if start is None or end is None:
continue
lines = code.splitlines()
snippet = "\n".join(lines[start - 1 : end])
kind = "class" if isinstance(node, ast.ClassDef) else "function"
name = getattr(node, "name", "<anon>")
out.append(
Chunk(
text=snippet,
meta={
"strategy": "python_ast",
"kind": kind,
"name": name,
"filepath": filepath,
"start_line": start,
"end_line": end,
},
)
)
return out
Indexvoorbeelden
FAISS (lokale) — minimale, snelle basislijn
# pip install faiss-cpu numpy
import numpy as np
import faiss
def build_faiss_index(vectors: np.ndarray) -> faiss.Index:
# vectors: vorm (N, d), type float32
d = vectors.shape[1]
index = faiss.IndexFlatIP(d) # inwendig product; cosinus gelijkenis als vectors genormaliseerd zijn
faiss.normalize_L2(vectors)
index.add(vectors)
return index
def faiss_search(index: faiss.Index, query_vec: np.ndarray, k: int = 5):
q = query_vec.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(q)
scores, ids = index.search(q, k)
return ids[0].tolist(), scores[0].tolist()
Chroma (lokale) — eenvoudige persistentie voor RAG prototyping
# pip install chromadb
import chromadb
def build_chroma_collection(chunks: list[Chunk], embeddings: np.ndarray, *, path: str = "./chroma_store"):
client = chromadb.PersistentClient(path=path)
col = client.get_or_create_collection(name="docs")
ids = [sha1_id(c.meta.get("strategy", "chunk"), str(i), c.text[:50]) for i, c in enumerate(chunks)]
col.upsert(
ids=ids,
documents=[c.text for c in chunks],
metadatas=[c.meta for c in chunks],
embeddings=embeddings.tolist(),
)
return col
Weaviate (zelfgehost / cloud) — eigen vectors meenemen
# pip install weaviate-client
import weaviate
from weaviate.classes.config import Configure
def weaviate_upsert_self_provided(
chunks: list[Chunk],
embeddings: np.ndarray,
):
client = weaviate.connect_to_local() # of connect_to_weaviate_cloud(...)
try:
collection = client.collections.create(
name="Chunk",
vector_config=Configure.Vectors.self_provided(), # u levert eigen vectors
)
with collection.batch.dynamic() as batch:
for c, v in zip(chunks, embeddings):
batch.add_object(
properties={"text": c.text, **{f"m_{k}": str(vv) for k, vv in c.meta.items()}},
vector=v.tolist(),
)
# Zoek op near-vector
q = embeddings[0]
res = collection.query.near_vector(near_vector=q.tolist(), limit=5)
for obj in res.objects:
print(obj.properties.get("text")[:200])
finally:
client.close()
Sommige brondocumenten
- Patrick Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, NeurIPS 2020; arXiv:2005.11401.
- Vladimir Karpukhin et al., “Dense Passage Retrieval for Open-Domain Question Answering”, EMNLP 2020; arXiv:2004.04906.
- OpenAI API Referentie: Embedding aanmaken (
/v1/embeddings) — tokenlimieten (8192 per invoer; 300k totaal per aanvraag) endimensionsparameter. - OpenAI Batch API gids + OpenAI API prijspagina (Batch bespaart ongeveer 50% met 24-uurs doorlooptijd).
- LangChain documentatie: RecursiveCharacterTextSplitter en splitters integratiegids (chunkgrootte/overlap, rekursieve scheiding hiërarchie).
- LlamaIndex documentatie: HierarchicalNodeParser en AutoMergingRetriever (grof naar fijn knopen; ophalen tijdens ophalen).
- Weaviate blog: “Chunking Strategies to Improve LLM RAG Pipeline Performance” (LLM-gebaseerde chunking beschrijving en trade-offs).
- Docling documentatie: HierarchicalChunker maakt chunks van documentelementstructuur en voegt hoofdingen/legenda metadata toe.
- Jimeno Yepes et al., “Financial Report Chunking for Effective Retrieval Augmented Generation” (arXiv:2402.05131v3, 2024).
- Marti A. Hearst, “TextTiling: Segmenting Text into Multi-Paragraph Subtopic Passages”, Computational Linguistics, 1997 (ACL Anthology: J97-1003).
- FAISS documentatie / GitHub repository: trade-offs tussen zoektijd, kwaliteit, geheugen; optionele GPU-ondersteuning.
- Nandan Thakur et al., “BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models”, NeurIPS 2021; arXiv:2104.08663.
- RAGAS documentatie: “Faithfulness” metriek en gerelateerde RAG evaluatiemetrieken.
- NLTK documentatie:
nltk.tokenize.sent_tokenize- Punkt-based aanbevolen zintokkizer. - spaCy API documentatie:
Sentencizer- regelgebaseerde zinsgrensdetectie zonder afhankelijkheidsanalyse.
Andere nuttige links
- Geavanceerd RAG: LongRAG, Self-RAG en GraphRAG uitgelegd
- Vector Stores voor RAG vergelijking
- Herordenen met embedding modellen
- Herordenen van tekstdocumenten met Ollama en Qwen3 Embedding model - in Go
- Herordenen van tekstdocumenten met Ollama en Qwen3 Reranker model - in Go
- Self-Hosting Cognee (LLM geheugen): LLM Prestatietests
- LLM hosting: Lokale, zelfgehoste & cloud infrastructuur vergeleken
- LLM prestaties: Benchmarks, bottlenecks & optimalisatie
- Retrieval-Augmented Generation (RAG) tutorial: Architectuur, implementatie en productiegids