GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||
GeezerVison is showing github.com — Split text blocks into segments that join back to the block, without langchain-text-splitters by edwinyyyu · Pull Request #1855 · MemMachine/MemMachine Conversation
|
| Before | After |
|---|---|
'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'.
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_length raises ValueError, as LangChain's splitter did.Why in-house rather than strip_whitespace=False.
_join_docs calls text.strip() unconditionally at langchain v0.0.100 and v0.0.200, and strip_whitespace=True has 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 @override decorator.langchain-text-splitters>=1.1.1 with 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_splitters took 1.3 s and loaded 2,451 modules on macOS. 1.1.3 makes those imports lazy again.langchain-aws keeps langchain-core in 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.
_cuts finds 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._split recurses over the separators like LangChain's splitter, merging adjacent pieces up to max_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 every max_chunk_length code 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_length 20, 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-splitters leaves the server's dependencies. uv.lock was 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's revision from 3 to 5.
No issue filed.
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/server and uv 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).N/A
agentic_expansion branch 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=False and moves whitespace after the split. It cannot keep indentation with its line or keep chunks within the limit.🤖 Generated with Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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) <[email protected]>
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) <[email protected]>
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) <[email protected]>
The user chose to port the seven inactive datasets onto EventMemory rather than delete them. partitions.py gives every query the EventMemory partition it searches, by its dataset's scope: its LongMemEval history (turns as events, histories.py), its own document, its candidate documents (SWE-bench), or the whole corpus. Documents are events with no producer (NullContext), embedded and rendered with no date or time (FormatOptions with both styles off), so the embedding text, the reranker's input and the reader's context are the segment's JSON text alone. Gold is character spans in event texts (LongMemEval's gold turns whole); each segment's offsets are found in its event's text, so spans survive TextSegmenter's stripping. On every query of the seven datasets, at seg500 and cut500, every gold span maps to segments, none empty. simulation.py replaces simulate.py and simulate_histories.py: one simulator, grouped by partition so a shared corpus is opened once. Removed with the flat path: retrieval.py, units.py, expansion.py, chunking.py, the MemMachine#1855 segmenter copy, token depths, pool_tokens and min_depth, simulate.EXPANSIONS, explain.BUDGETS, metrics.BUDGETS and KS (stored count columns follow config), and the invented dev/test splits (samples take questions in one hash order; a preparer's split field is source provenance only). The estimate, explain, examples, profile, fidelity and embed-fidelity tools run on partitions. data/chunks and data/pools are left in place (other scripts read them). Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.