Repository navigation
Conversation
TextSegmenter split with langchain_text_splitters'
RecursiveCharacterTextSplitter, whose strip_whitespace defaults to
True. Every chunk lost the whitespace at its ends, so a block's
segments did not join back to its text, and a rendered run of segments
ran its sentences together ("is here.Second sentence").
Split in-house instead, with the same separators and the same chunk
boundaries, keeping every character:
- The whitespace between two segments ends the earlier one, so only a
block's first segment begins with whitespace.
- A block of whitespace alone is one segment; an empty block has none.
- max_chunk_length bounds a chunk without the whitespace at its ends.
- A non-positive max_chunk_length still raises ValueError.
The embedded text changes only where the text segmenter feeds the
whole-text deriver: a derivative now carries its segment's edge
whitespace inside the quoted text, as the passthrough segmenter's
derivatives already do. The sentence deriver strips each sentence, so
its derivatives are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nothing imports it after the text segmenter's in-house split. langchain-core stays, through langchain-aws. uv.lock was regenerated with uv 0.13.5, the release CI installs. It removes only the package's entries; uv 0.13.5 also bumps the lock's revision from 3 to 5. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5 of 16 tasks
The split moved all whitespace at the start of a chunk to the end of the chunk before it. That took a line's indentation away from its line, and the whitespace it moved could push a chunk past max_chunk_length. Choose cuts by a whitespace rule instead of moving whitespace after the split: - A whitespace run between two pieces of content is cut after its last newline: the earlier segment takes it through that newline, and the later one begins with the rest, the line's indentation. A run without a newline goes wholly to the earlier segment. - The text's leading whitespace begins its first segment, and its trailing whitespace ends its last. - No segment is longer than max_chunk_length, whitespace included. The content cuts are chosen so the rule's whitespace fits; a whitespace run that no segment can hold with the character it attaches to is cut to fit instead. Every separator's cut that touches whitespace moves to the run's cut before pieces merge, so merging counts whitespace and never exceeds the limit. max_chunk_length bounds whole chunks again, so its description returns to main's wording. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6 of 16 tasks
This branch has not been deployed
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.
Purpose of the change
TextSegmenter's segments do not join back to the text they came from. It splits with LangChain'sRecursiveCharacterTextSplitter, whosestrip_whitespacedefaults toTrue, so every chunk loses the whitespace at its ends. Event memory renders a run of segments by concatenating them, so a split block renders with its sentences run together. This change splits text in-house with the same separators, keeping every character, and places the whitespace between segments by a rule that LangChain's splitter cannot express. It dropslangchain-text-splittersfrom the server's dependencies.With
max_chunk_length=50,"First sentence is here. Second sentence is right here. Third one ends it.\n\nNew paragraph starts here and goes on."segments as:'First sentence is here.''First sentence is here. ''Second sentence is right here. Third one ends it.''Second sentence is right here. ''Third one ends it.\n\n''New paragraph starts here and goes on.''New paragraph starts here and goes on.'Rendered before:
"First sentence is here.Second sentence is right here. Third one ends it.New paragraph starts here and goes on.". After: the text exactly. The second chunk splits because whitespace now counts toward the limit: with its trailing"\n\n", the old second chunk would be 51 code points.Indentation stays with its line. With
max_chunk_length=20,"def f():\n return [\n 1,\n 2,\n ]\n"segments as'def f():\n return ','[\n 1,\n',' 2,\n ]\n'.Description
Segments.
max_chunk_length, whitespace included. The content cuts are chosen so the rule's whitespace fits. A whitespace run that no segment can hold together with the character it attaches to is cut to fit instead; only then can a segment be whitespace alone.max_chunk_lengthraisesValueError, as LangChain's splitter did.Why in-house rather than
strip_whitespace=False._join_docscallstext.strip()unconditionally at langchain v0.0.100 and v0.0.200, andstrip_whitespace=Truehas been the default since stripping became optional. The functions on this segmenter's path (__init__,split_text,_split_text,_split_text_with_regex,_merge_splits,_join_docs) are identical in 1.1.1, 1.1.2, 1.1.3, and master, apart from an added@overridedecorator.langchain-text-splitters>=1.1.1with no cap, so an install takes whatever release is current, and the chunk boundaries decide what is stored and what is embedded.import langchain_text_splitterstook 1.3 s and loaded 2,451 modules on macOS. 1.1.3 makes those imports lazy again.langchain-awskeepslangchain-corein the base install.Implementation. Three private functions in
text_segmenter.py, covering only the configuration the segmenter uses: literal separators kept at the end of the text they follow, no overlap._cutsfinds the cut after each occurrence of a separator and moves any cut that touches whitespace to that run's cut, from_whitespace_cut, which scans only the run._splitrecurses over the separators like LangChain's splitter, merging adjacent pieces up tomax_chunk_length. Pieces already carry their whitespace when they merge, so no chunk exceeds the limit. At the last separator, the empty string, a piece still over the limit is cut everymax_chunk_lengthcode points.Measured. 2,106 real documents (55.9 million characters from QASPER, COVID-QA, ConditionalQA, NQ, NarrativeQA, and LegalBench-RAG) and about 3,000 synthetic texts heavy in whitespace, blank-line runs, indentation, CJK punctuation, and code, at
max_chunk_length20, 128, and 500:max_chunk_length, in every case;Embedded text. Unchanged for the default configuration (passthrough segmenter, whole-text deriver). Where the text segmenter feeds the whole-text deriver, a derivative now carries its segment's edge whitespace inside the quoted text, as the passthrough segmenter's derivatives already do. The sentence deriver strips each sentence, so its derivatives are unchanged.
Dependencies.
langchain-text-splittersleaves the server's dependencies.uv.lockwas regenerated with uv 0.13.5, the release CI installs. It removes only the package's entries, and uv 0.13.5 also bumps the lock'srevisionfrom 3 to 5.Fixes/Closes
No issue filed.
Type of change
How Has This Been Tested?
New tests in
segmenter/test_text_segmenter.py:max_chunk_length, and every boundary that touches whitespace cuts it where the rule does;max_chunk_length.In
test_event_memory.py, a split event's queried context renders its text exactly. Against main's segmenter, 17 of the new tests fail; with this change all pass.Test Results:
uv lock --check(uv 0.13.5): passes.uv run --frozen ruff check packages/serveranduv run --frozen ruff format --check packages/server: clean.uv run --frozen --all-extras ty check --project packages/server: clean.uv run --frozen --all-extras pytest packages/server/server_tests -q -p no:cacheprovider: 2,147 passed, 3 skipped, 1,725 deselected (Python 3.14, macOS).Checklist
Maintainer Checklist
Screenshots/Gifs
N/A
Further comments
agentic_expansionbranch holds an earlier, verbatim port of the whole splitter (10ed25a6), which keeps LangChain's stripping and its unused options. This change replaces that approach.strip_whitespace=Falseand moves whitespace after the split. It cannot keep indentation with its line or keep chunks within the limit.🤖 Generated with Claude Code