localvectordb.chunking module
Position-tracking chunking system for LocalVectorDB v1.0
This module provides chunkers that track exact positions in the original document, enabling perfect reconstruction and precise highlighting.
- class localvectordb.chunking.PositionTrackingChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
ABCBase class for chunkers that track exact positions
- count_tokens(text: str) int
Count tokens in text.
Uses
encode_ordinaryrather thanencode: the latter runs a special-token scan (regex) on every call to enforcedisallowed_special, which is pure overhead here and would also raise on text that merely contains a special-token literal.encode_ordinaryskips that check and yields identical counts for ordinary text.
- class localvectordb.chunking.SentenceChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by sentences while preserving boundaries
- sentence_pattern = re.compile('(?<=[.!?])\\s+|(?<=[.!?]")(?=\\s+[A-Z])|(?<=[.!?])\\n+', re.MULTILINE)
- class localvectordb.chunking.TokenChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by token boundaries with position tracking
- class localvectordb.chunking.WordChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by word boundaries while preserving all whitespace
- class localvectordb.chunking.LineChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by line boundaries
- class localvectordb.chunking.CharChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by character boundaries with exact position tracking
- class localvectordb.chunking.ParagraphChunker(max_tokens: int = 500, overlap: int = 0, **kwargs)
Bases:
PositionTrackingChunkerChunk by paragraph boundaries
- paragraph_pattern = re.compile('\\n\\s*\\n', re.MULTILINE)
- class localvectordb.chunking.DelimiterChunker(max_tokens: int = 500, overlap: int = 0, delimiter: str = '\n\n', **kwargs)
Bases:
PositionTrackingChunkerSplit on a literal delimiter sequence, with a character-level fallback.
The document is cut on every occurrence of
delimiter(a literal string,"\n\n"by default), and the resulting segments are packed into chunks up tomax_tokens. A single segment that is itself larger thanmax_tokensbecomes its own chunk and is then split character-by-character by the shared_ensure_chunks_within_limit()safeguard – so no chunk ever exceeds the limit even when the delimiter leaves an over-long piece.Like
SentenceChunkerandParagraphChunker, each segment owns the delimiter that follows it, so the[start, end)spans are contiguous and cover the whole document andreconstruct_document()is exact.
- class localvectordb.chunking.SectionChunker(max_tokens: int = 500, overlap: int = 0)
Bases:
PositionTrackingChunkerChunk by section headers (markdown-style)
- header_pattern = re.compile('^(#{1,6})\\s+(.+)$', re.MULTILINE)
- class localvectordb.chunking.CodeBlockChunker(max_tokens: int = 500, overlap: int = 0, language: str | None = None, **kwargs)
Bases:
PositionTrackingChunkerChunk code while preserving logical code blocks
- class localvectordb.chunking.StructureChunker(max_tokens: int = 500, overlap: int = 0, min_fill: float = 0.35, heading_finder: Callable[[str], Iterable[int]] | None = None, **kwargs)
Bases:
PositionTrackingChunkerCut at the strongest human-authored boundary inside the size envelope.
Fixed-size chunkers cut at a token count and hope the cut lands somewhere harmless. This one collects the boundaries a human actually wrote – headings, blank-line paragraph breaks, line breaks, sentence ends – ranks them by strength, and cuts at the strongest one still inside the token budget. A chunk therefore ends where the document says a thought ends, and only falls back to an arbitrary cut when the span offers no boundary at all.
Why this shape (measured, see
experiments/span-length-crossover-findings.md):Retrieval quality rises monotonically as chunks shrink, but the return per stored vector collapses after ~500 tokens (Qasper/openai: 1000 -> 500 buys +0.056 nDCG, 500 -> 250 buys +0.017 for 2.4x the vectors).
max_tokensis a budget knob; the boundary choice is what is free.Past roughly 2k tokens a single embedding of a span is beaten by an average of its parts, so oversized chunks are not merely wasteful, they are worse. Keeping chunks inside the envelope is a correctness property, not a preference.
overlapmust be 0, as forSectionChunker: these chunks are logical units, and re-emitting the tail of one inside the next would break the boundary alignment that is the entire point. Reconstruction is exact – chunks tile the source contiguously, separators folded onto the preceding chunk.- Parameters:
max_tokens (int) – Upper bound on chunk size; the cut is chosen at or before this budget.
min_fill (float) – Fraction of
max_tokensa chunk must reach before a boundary is eligible, so a heading two lines in cannot produce a sliver. Set to 0 to always cut at the earliest strongest boundary.heading_finder (Callable[[str], Iterable[int]], optional) –
Supplies extra heading-strength cut positions, for documents whose structure is not markdown. The built-in
_HEADINGrecognises markdown ATX only; a corpus of plain-text contracts, RFCs, or statutes carries no#at all, and on such text this chunker silently degrades to a paragraph splitter (measured: no better than fixed-size, and worse once its under-fill is counted). Positions from this callable are added to the markdown ones, never replace them.Detecting such headings well is not a matter of a wider regex – numbered headings collide with cross-references (“pursuant to Section 2.05”) and with a table of contents that repeats every heading verbatim, so a usable finder needs its own precision filtering. That belongs to the caller who knows the document family, not here.
- STRENGTH_HEADING = 4
- STRENGTH_PARAGRAPH = 3
- STRENGTH_LINE = 2
- STRENGTH_SENTENCE = 1
- class localvectordb.chunking.ChunkerFactory
Bases:
objectFactory for creating chunkers
- CHUNKERS: dict[str, Type[PositionTrackingChunker]] = {'characters': <class 'localvectordb.chunking.CharChunker'>, 'code-blocks': <class 'localvectordb.chunking.CodeBlockChunker'>, 'delimiter': <class 'localvectordb.chunking.DelimiterChunker'>, 'lines': <class 'localvectordb.chunking.LineChunker'>, 'paragraphs': <class 'localvectordb.chunking.ParagraphChunker'>, 'sections': <class 'localvectordb.chunking.SectionChunker'>, 'sentences': <class 'localvectordb.chunking.SentenceChunker'>, 'structure': <class 'localvectordb.chunking.StructureChunker'>, 'tokens': <class 'localvectordb.chunking.TokenChunker'>, 'words': <class 'localvectordb.chunking.WordChunker'>}
- classmethod create_chunker(method: str | Type[PositionTrackingChunker], max_tokens: int = 500, overlap: int = 0, **kwargs) PositionTrackingChunker
Create a chunker instance
- localvectordb.chunking.reconstruct_document(chunks: List[Chunk], original_length: int) str
Reconstruct a document exactly from the chunks a chunker produced.
The general-purpose chunkers (
sentences– the default –paragraphs,words,lines,characters,tokens,sections) emit chunks whose[start, end)spans cover[0, original_length)with no gaps (overlap is fine – an overlapped position is simply filled twice with the same character), so the return value equals the original text character-for-character.sentencesandparagraphsachieve this by folding each inter-unit separator onto the preceding chunk’s span rather than leaving it uncovered.The
code-blockschunker is the exception: it is specialised for splitting source code and its multi-chunk path is line-oriented, so it guarantees exact reconstruction only when the whole input fits a single chunk. A chunk list assembled by hand with gaps between spans will likewise leave those positions blank.