Splits text into sentence-aware chunks, mirroring upstream's ChunkIterator.
A chunk is a token interval: its text runs from its first token's start to its last token's end. Whitespace between chunks belongs to no chunk, so chunks do not tile the source — matching upstream, whose token stream has no whitespace tokens. Byte offsets always slice the chunk's text back out of the source verbatim.
Sentence boundary rules (upstream's find_sentence_range semantics):
- A
:punctuationtoken ending in a sentence terminator (.,!,?, CJK equivalents —...counts, since symbol runs are one token) ends a sentence, unless the previous token plus the terminator form a known abbreviation ("Dr" <> "." == "Dr."). Whitespace is invisible here, so"Dr ."still reads as the abbreviation. - After sentence-ending punctuation, trailing closing punctuation
(
",',),],},»,”,’) is consumed into the same sentence — across any whitespace, so the quote opening the next line's dialogue attaches to the sentence before it, exactly as upstream. - A token first on its line (its gap from the previous token contains
\nor\r) starts a new sentence unless it begins lowercase — lines opening with quotes, digits, or capitals all break (upstream: "assume break unless lowercase").
Chunk assembly (mirroring upstream's ChunkIterator.__next__):
- A single token longer than the budget forms a chunk by itself.
- An oversized sentence is cut at the most recent newline within budget when one exists, else at the last token that fits; the remainder restarts sentence discovery mid-sentence.
- A chunk that completes a broken sentence never absorbs following sentences.
- Otherwise whole sentences pack into the chunk while they fit.
Budgets count characters (upstream's max_char_buffer unit — see
chunk/2); offsets are bytes.
Departure from upstream's shape: every boundary rule is position-local,
so sentence ends are precomputed for all positions in one backward pass,
where upstream rescans forward from each chunk start — quadratic on
boundary-free text (minified JSON, logs). Behavior is pinned two ways:
the parity fixtures tie the line-comparable port to upstream, and the
differential test ties this rewrite to that port, frozen as
LangExtract.Test.ChunkerBaseline in test support.
Summary
Functions
Splits text into chunks respecting sentence boundaries.
Functions
@spec chunk( String.t(), keyword() ) :: [LangExtract.Chunker.Chunk.t()]
Splits text into chunks respecting sentence boundaries.
Options
:max_chunk_chars— maximum characters per chunk (required). Char-denominated to mirror upstream'smax_char_buffer— chars are code points, Python'slenunit — so chunk boundaries land identically across the two libraries; the cross-library benchmarks depend on that. Output offsets are bytes.