Fix merge_chunks pre-allocating a fixed chunk count that ignores overlap#2076
Open
chuenchen309 wants to merge 1 commit into
Open
Conversation
…lap, letting the last chunk grow unbounded merge_chunks() pre-computes num_chunks = ceil(total_tokens / target_size), then guards the rollover with `curr_chunk < num_chunks - 1`. That estimate never accounts for the `overlap` tokens re-injected at every chunk boundary -- with the repo's own defaults (chunk_token_threshold=2048, overlap_rate=0.1 -> overlap=205), each boundary adds ~205 tokens beyond the naive total_tokens/target_size budget. Once curr_chunk reaches the last pre-allocated slot, the rollover condition can never fire again (the second half of the `and` is always False), so every remaining token gets dumped into that one last chunk with no cap. Verified independently with real (non-mocked) calls to merge_chunks: at 10,000 tokens the last chunk overshoots target_size by 28%; at 40,000 tokens by 143%; at 160,000 tokens by 693% -- the overshoot is unbounded and grows with document length, not a fixed off-by-one. merge_chunks feeds both LLMContentFilter (content_filter_strategy.py:913) and LLMExtractionStrategy (extraction_strategy.py:778), so a long enough document silently produces a "chunk" far larger than the token budget the caller configured -- risking truncation or blown context windows in the downstream LLM call, with no error or warning. Fix: stop pre-allocating a fixed chunk count. Grow the chunk list on demand (`chunks.append(...)` whenever curr_size reaches target_size, unconditionally) instead of capping rollover at a chunk count computed without knowledge of the overlap that will be injected. Added tests/test_merge_chunks.py: no-overlap stays within target_size, overlap stays within target_size at both 10k and 160k tokens (regression guard for the unbounded-growth behavior), overlap tokens are correctly repeated at chunk boundaries, full token content is preserved without overlap, and empty input returns an empty list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
merge_chunks()(crawl4ai/utils.py) pre-computes:num_chunksis derived only fromtotal_tokens // target_sizeand neveraccounts for the
overlaptokens that get re-injected at every chunkboundary. With this repo's own defaults (
chunk_token_threshold=2048,overlap_rate=0.1->overlap=205), each boundary adds ~205 tokens beyondthat naive budget. Once
curr_chunkreaches the last pre-allocated slot,curr_chunk < num_chunks - 1is permanentlyFalse, so the rollover branchcan never fire again — every remaining token gets dumped into that one last
chunk, uncapped.
merge_chunksfeeds bothLLMContentFilter(
content_filter_strategy.py:913) andLLMExtractionStrategy(
extraction_strategy.py:778), so a long document silently produces one"chunk" far larger than the configured token budget — risking truncation or
blown context windows in the downstream LLM call, with no error or warning.
Repro (before fix)
Overshoot is unbounded and grows with document length — not a fixed
off-by-one.
Fix
Stop pre-allocating a fixed chunk count. Grow the chunk list on demand
(append a new chunk whenever
curr_sizereachestarget_size,unconditionally) instead of capping rollover at a count computed without
knowledge of the overlap that will be injected. After the fix, every chunk
in the repro above stays at or under
target_size.Testing
Added
tests/test_merge_chunks.py:target_sizetarget_sizeat 10k and 160k tokens (regressionguard for the unbounded-growth behavior)
overlap=0[]Confirmed red→green: reverting only
crawl4ai/utils.pyreproduces the twoovershoot-size test failures (2628/16246 > 2048); reapplying passes all 6.
Full baseline (
tests/unit/,tests/deep_crawling/,tests/regression/test_reg_extraction.py -m "not network"): 133 passed, 1skipped (unrelated, needs live network).
python -m py_compile crawl4ai/utils.py: clean (repo has nolint/format tooling configured — checked
pyproject.toml, noMakefile/.pre-commit-config.yaml).xref: no open PR touches
merge_chunks.Checklist:
AI-Generated disclosure
Found via an AI-assisted code review pass (Claude Code) over
crawl4ai/utils.py.I personally reproduced the unbounded-overshoot behavior with a standalone
script at three document sizes, verified the fix eliminates it, and ran the
relevant test suites before submitting.