Chunking¶
A chunk is the unit of every later decision, and no later stage can undo it.
What gets embedded, scored, reranked, quoted in a citation and pushed into the window is a chunk. A passage split through the middle becomes two hits that are each wrong, and the reranker cannot recover the half that was left behind. So chunking is configured per collection, sized in the tokens of the model that will read it, and it refuses rather than emitting a piece that will not fit.
The shape¶
from tesserix_adk.rag import ChunkerRegistry, ChunkingSpec, Document, tokens_via
chunkers = ChunkerRegistry(
count=tokens_via(provider),
collections={
"handbook": ChunkingSpec(strategy="structural", max_tokens=512),
"search": ChunkingSpec(strategy="fixed", max_tokens=256, overlap_tokens=32),
"repo": ChunkingSpec(strategy="code", max_tokens=400),
},
)
for chunk in chunkers.chunker_for("handbook").chunk(Document(id="handbook-2026", text=text)):
... # chunk.text, chunk.start, chunk.end, chunk.section, chunk.tokens
A collection with no settings and no default is a ConfigurationError: chunking it by
guesswork indexes it differently from everything already in it, and the symptom is
retrieval quality that drifts with no deploy to blame.
Sized by the tokeniser that will read it¶
tokens_via(provider) counts with the provider's own tokeniser. A character heuristic is
wrong by a factor of three the first time a document is not English — under the window on
one corpus, over it on another, and nothing finds out until retrieval is in production.
Any Callable[[str], int] works, so a deployment with a local tokeniser passes it
directly.
The strategies¶
| Name | Class | Splits on |
|---|---|---|
fixed |
FixedTokens |
Word boundaries, filling to the limit, with an optional overlap |
structural |
Structural |
Headings, then paragraphs, then sentences, then words |
sentence-window |
SentenceWindow |
Overlapping windows of whole sentences |
code |
CodeAware |
Top-level definitions, then lines |
register(name, build) adds a deployment's own, and a registered name replaces a built-in
of the same name.
Structural falls to a finer boundary only where the coarser one does not fit, so a
document with no headings, no paragraphs and no sentence ends still chunks rather than
failing. SentenceWindow overlaps by construction: its chunks cover the document, they do
not partition it.
A chunk knows where it came from¶
chunk.text is exactly document.text[start:end], and the model refuses any chunk whose
span does not describe its text. Offsets are code points, so a document in mixed scripts
cites correctly rather than resolving to a half-character. Everything here segments by
producing cut offsets rather than pieces, so no strategy can quietly drop the whitespace
between two chunks and leave every subsequent offset wrong by one.
Ids are content-addressed from the document id and the chunk text, so re-indexing an unchanged document writes the same ids and an edit rewrites only the chunks it touched.
What will not divide¶
A run of text that cannot be cut under the limit — a base64 blob, a minified bundle — is a
ChunkingError naming the document and the offset. Emitting it anyway moves the failure to
a model call, where it reads as a context window error about a document nobody can name.
overflow=Overflow.SPLIT cuts it mid-word at the limit instead. That is right for a
document that genuinely has no boundaries and lossy for one whose boundaries the strategy
did not know about, which is why it is asked for rather than assumed.
Known limitations¶
-
A heading whose section overflows becomes its own chunk. Structural splitting cuts at the heading, so where the section beneath it does not fit, the heading line is the first piece. Its text is carried on every following chunk's
sectionregardless, so nothing is lost — but a collection where this is common wants a largermax_tokens. -
CodeAwarehas no parser. Column zero is where a top-level definition starts in every language that indents its bodies, which keeps whole functions together without a grammar per language. A language that does not indent is chunked by lines. -
Sentence segmentation is regex-based. It ends a sentence at terminal punctuation, so
Dr.ande.g.end one. The cost is a slightly early boundary, not a lost span. -
The counter is called several times per chunk. Fitting is an exponential-then-binary search rather than one unit at a time, so a large document costs a logarithmic number of counts per chunk. A counter that makes a network call per invocation is still the wrong counter.
-
Loading is somebody else's job. A
Documentis text that has already been extracted. PDF, HTML and office loaders are not in the kit.
A runnable walkthrough: examples/chunking.py.