Skip to content

docs(search-semantic): correct the boundary-aware chunker settings - #478

Merged
marevol merged 2 commits into
masterfrom
feature/chunker-boundary-settings
Aug 11, 2026
Merged

docs(search-semantic): correct the boundary-aware chunker settings#478
marevol merged 2 commits into
masterfrom
feature/chunker-boundary-settings

Conversation

@marevol

@marevol marevol commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Documents the boundary-aware chunker from codelibs/fess#3211. Tracked as codelibs/fess#3231 (filed there because issues are disabled on this repository).

Rebased onto the current master. #475 landed the same three settings while this was open, so this PR is now a correction and extension of that text rather than the original addition. The overlapping notes the two branches each added are merged into one.

Corrections to what #475 landed

All three checked against LengthChunker / ChunkBoundaryFinder / ChunkVectorHelper.

1. The worst case is 840 characters, not "about 841". The bound is chunk_size + max(lookahead, 2 * ChunkBoundaryFinder.MAX_CLUSTER_ADJUST) = 800 + max(40, 32). The further + 1 branch in findChunkEnd requires ideal == start + 1, which MIN_CHUNK_SIZE makes unreachable from LengthChunker. The floor — 800 - 160 — was not stated at all, so the table now gives the full range: 640 to 840 characters. That matters to anyone who sized chunk_size against an embedding model's token budget.

2. A cut does not move to "the nearest suitable break … preferring" one kind over another. Candidates are tiered, and the nearest candidate of the highest tier present wins: a line break or sentence end beats any clause separator or space no matter how much farther back it is, and those beat a writing-system change. This is the part the upstream PR got wrong three times in its own prose.

3. content_chunker.length.overlap snaps its restart point to a boundary too. snapOverlapStart can only move it earlier, and its window is capped at the configured overlap, so the effective overlap is between the configured value and twice it. Not documented at all before.

Additions

content_chunker.max_chunks_per_document — a document that crosses the cap is marked skipped and receives no embeddings at all: it stays keyword-searchable but drops out of semantic search. Boundary-aware splitting also makes chunks shorter, so a document yields roughly 3% to 25% more chunks than a fixed-length split (measured: +2.7% on Japanese prose, +9.3% on English prose, +24.8% worst case) — the cap is easier to reach than it was. The row now says both.

Re-chunking requires a recrawl — changing any of these settings only affects documents chunked afterwards; a document already stored as a chunk array keeps its boundaries until it is re-crawled.

Kept from #475

The guarantee that only the cut point moves, so concatenating a document's chunks still reproduces its content exactly, and the description of the independent grapheme-cluster overshoot that ignores lookahead_percent.

Scope

All seven languages: en, ja, de, es, fr, ko, zh-cn.

Verification

A full Sphinx build is not reproducible from this repository alone (conf/conf.py is templated by the builder), so the checks are structural:

  • every content_chunker.* list-table still has the same shape as master (23 rows / 69 cells) in each of the seven languages;
  • the number of .. note:: blocks per file is unchanged from master (the two overlapping notes were merged, not stacked);
  • all three boundary keys are present in each of the seven;
  • docutils parses all seven files with a diagnostic set identical to master (zero non-Sphinx diagnostics);
  • no line exceeds the 123-column display width each file already had (East-Asian-Width aware).

codelibs/fess#3211 makes the length chunker move each cut to a sensible text
boundary and adds three settings for it. The 15.8 semantic search page did not
mention them, and one existing row became wrong.

content_chunker.length.chunk_size was described as "Number of characters per
chunk". It is now a target rather than a hard limit: a chunk may be up to
lookback_percent of it shorter and up to max(lookahead, 32) characters longer,
so 640 to 840 characters with the shipped defaults. That matters to anyone who
sized it against an embedding model's token budget.

content_chunker.length.overlap now also snaps its restart point to a boundary,
and snapping can only move it earlier, so the effective overlap is between the
configured value and twice it.

content_chunker.max_chunks_per_document now notes that boundary-aware splitting
yields roughly 3% to 25% more chunks per document, and that a document crossing
the cap is marked skipped and receives no embeddings at all.

Added rows for content_chunker.length.boundary.enabled, .lookback_percent and
.lookahead_percent, plus a note that disabling the feature -- or setting both
percentages to 0 -- reproduces the previous fixed-length behaviour exactly, and
that a change only affects documents chunked afterwards.

All seven languages. Verified: every list-table still has 22 rows and 44 cells
in each language, all three keys are present in each, and docutils reports no
non-Sphinx diagnostics for any of the seven files.
The boundary settings landed on master in the meantime, so both sides edited
the same content_chunker table and added a note about it in all seven
languages. Resolved by keeping master's text and folding this branch's
corrections into it, then merging the two overlapping notes into one.

Corrections kept from this branch, all checked against LengthChunker and
ChunkVectorHelper:

- The worst case is 840 characters, not "about 841". The bound is
  chunk_size + max(lookahead, 2 * ChunkBoundaryFinder.MAX_CLUSTER_ADJUST),
  i.e. 800 + max(40, 32); the further "+ 1" branch in findChunkEnd requires
  ideal to equal start + 1, which MIN_CHUNK_SIZE makes unreachable from
  LengthChunker. The floor, 800 - 160, was not stated at all, so the table now
  gives the full range of 640 to 840 characters.

- A cut does not move to "the nearest suitable break, preferring" one kind over
  another. Candidates are tiered and the nearest candidate of the highest tier
  present wins: a line break or sentence end beats any clause separator or
  space no matter how much farther back it is, and those beat a writing-system
  change.

- content_chunker.length.overlap snaps its restart point to a boundary as well.
  snapOverlapStart only ever moves it earlier and its window is capped at the
  configured overlap, so the effective overlap is between the configured value
  and twice that value.

- content_chunker.max_chunks_per_document: a document over the cap is marked
  skipped and receives no embeddings, so it stays keyword-searchable but drops
  out of semantic search. Boundary-aware splitting also yields roughly 3% to
  25% more chunks per document, which makes the cap easier to reach.

- Changing any of these settings only affects documents chunked afterwards: a
  document already stored as a chunk array keeps its boundaries until it is
  re-crawled.

Kept from master: the guarantee that only the cut point moves so concatenating
a document's chunks still reproduces its content exactly, and the description
of the grapheme-cluster overshoot.

Verified: table shape (23 rows / 69 cells) and note count unchanged from master
in every language, all three keys present in each, every line within each
file's pre-existing 123-column display width, and docutils reports a diagnostic
set identical to master for all seven files.
@marevol marevol changed the title docs(search-semantic): document the boundary-aware chunker settings docs(search-semantic): correct the boundary-aware chunker settings Aug 11, 2026
@marevol
marevol merged commit 14b1140 into master Aug 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant