Tekst opdelen in chunks voor embeddings: waar de knip valt, bepaalt wat je terugvindt
Retrievalsystemen falen zonder geluid te maken. Je embedt een corpus, zet er een zoekfunctie op, stelt een vraag en krijgt iets terug dat er qua onderwerp naast ligt maar de vraag niet beantwoordt. De reflex is om het embeddingmodel of de gelijkenismaat de schuld te geven. Veel vaker ging het daarvóór al mis: de tekst is in stukken geknipt die niet elk een volledig idee bevatten, en geen enkele slimme ranking herstelt betekenis die bij het splitsen is vernietigd.
Dit gereedschap splitst tekst in chunks met een tokenlimiet en overlap, en knipt op de meest betekenisvolle grens die beschikbaar is in plaats van op een vaste offset. Het laat je elke grens zien, want een slechte knip zie je alleen door te lezen waar de ene chunk eindigt en de volgende begint. Alles draait in dit browsertabblad.
Recursief splitsen op grenzen
De naïeve aanpak is om elke N tokens te snijden. Dat is snel en het is fout, want een tokengrens heeft geen enkele relatie met een betekenisgrens — je knipt midden in een zin, soms midden in een woord, en de resulterende vector beschrijft een fragment dat niemand heeft geschreven.
De methode hier werkt een ladder van scheidingstekens af en gebruikt het eerste dat stukken oplevert die klein genoeg zijn. In volgorde: vóór een Markdown-kop, dan lege regels, dan elke regelafbreking, dan zinsgrenzen, dan zinsdeelgrenzen bij komma's en puntkomma's, dan witruimte tussen woorden. Alleen als niets daarvan bestaat — een geminificeerde bundle, één enorm woord — valt het terug op knippen per tokenindex.
Het praktische effect is dat structuur behouden blijft zolang de tekst die heeft. Een document met koppen splitst bij secties. Proza splitst bij alinea's, of bij zinnen als een alinea te lang is. Een muur ononderbroken tekst splitst bij woorden. Elke chunk eindigt waar een mens hem ook geëindigd zou hebben, tenzij de tekst die gelegenheid niet bood.
Waar overlap eigenlijk voor is
Overlap herhaalt de staart van de ene chunk aan het begin van de volgende. Het bestaat omdat een feit en datgene waar het naar verwijst vaak aan weerszijden van een grens staan. “De migratie liep 's nachts. Hij liep vast op de derde tabel.” Splits tussen die twee zinnen en de tweede chunk bevat een storing zonder onderwerp, terwijl de eerste een migratie zonder afloop bevat. Geen van beide laat zich nuttig terugvinden. Met overlap draagt de tweede chunk het antecedent mee.
De prijs is duplicatie: overlappende tokens worden meer dan één keer opgeslagen, geëmbed en doorzocht. Tien tot vijftien procent van de chunkgrootte is het gebruikelijke compromis, en daar zitten de presets ook. Het statistiekenpaneel meldt precies hoeveel tokens de overlap heeft toegevoegd, zodat de afweging een getal is en geen gevoel.
Eén beveiliging is het vermelden waard: een overlap die gelijk is aan of groter dan de chunkgrootte zou betekenen dat elke chunk begint met alles waarmee de vorige eindigde, en dan komt het proces nooit vooruit. Dat wordt afgekapt in plaats van te blijven hangen.
Een grootte kiezen
Chunkgrootte is een afweging tussen precisie en context, en er is geen universeel juist antwoord:
- Kleine chunks, rond 256 tokens, geven scherpe matches. Een vector over twee of drie zinnen wordt gedomineerd door wat die zinnen zeggen. Goed voor vraag-antwoord over dicht referentiemateriaal. Het risico zijn chunks die goed matchen maar de omringende context missen die nodig is om echt antwoord te geven.
- Middelgrote chunks, rond 512, zijn de gangbare standaard en het formaat waar de meeste embeddingmodellen het gelukkigst mee zijn. Ongeveer een stevige alinea.
- Grote chunks, 1024 en hoger, dragen meer context per treffer maar verdunnen de vector. Een chunk die vier onderwerpen bestrijkt ligt in het gemiddelde van alle vier en matcht op geen van alle sterk.
- Zeer grote chunks, enkele duizenden tokens, houden op een retrieval-eenheid te zijn en worden een pagineringseenheid — om een lang document in opeenvolgende passages door een model te halen in plaats van erin te zoeken.
Vier splitsstrategieën
Slim splitsen is de recursieve ladder hierboven en is voor de meeste proza de juiste keuze. Alinea's pakt hele alinea's bij elkaar en deelt er nooit één op, wat past bij tekst waarin de alinea al de eenheid van denken is en je liever ongelijke chunkgroottes hebt dan een kapotte. Markdown-koppen geeft één chunk per sectie, de natuurlijke keuze voor documentatie waarin een kop precies benoemt wat eronder staat. Regels pakt hele regels bij elkaar, voor logs, CSV-uittreksels en lijsten waarin elke rij op zichzelf staat.
In elke modus wordt elke eenheid die de limiet nog steeds overschrijdt eerst door de recursieve splitser gehaald voordat er wordt ingepakt, zodat een strategiekeuze nooit een te grote chunk oplevert.
Tellen, en wat het getal betekent
Tokens worden geteld met dezelfde tokenizer waarmee deze site repository's meet, uit de familie die de modellen van OpenAI gebruiken. De tellingen voor Claude en Gemini wijken wat af — andere vocabulaires knippen dezelfde tekst anders op — maar ze lopen dicht genoeg gelijk om een chunk tegen een limiet af te meten. Zit je budget krap, houd dan marge aan in plaats van het getal als exact te behandelen.
Let er ook op dat de opgegeven limiet van een embeddingmodel een hard afkappunt is en geen streefwaarde. Ga eroverheen en de staart wordt zonder melding weggegooid — een faalwijze die geen fout oplevert en de retrieval verslechtert op een manier die oprecht moeilijk te diagnosticeren is.
Uitvoer die je ergens in kunt stoppen
Drie downloads. Platte tekst met optionele --- Chunk n/N ----koppen, om te lezen en te controleren. JSON als een array van objecten met index, tokenaantal en tekst, voor een script dat de hele partij inleest. JSONL met één object per regel, wat streaming-ingestie en de meeste batch-embeddingendpoints verwachten en wat je wilt zodra het corpus te groot is om als één geparste array in het geheugen te houden.
Dit past natuurlijk bij de rest van de site: zet eerst een repository, een PDF, een webpagina of een transcript om naar tekst en chunk het resultaat hier. Op geen van beide stappen wordt er iets geüpload.