Skip to content

Commit 7ded47f

Browse files
fix: apply split_overlap in RecursiveDocumentSplitter fallback path (#11768)
1 parent 2e8f968 commit 7ded47f

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

haystack/components/preprocessors/recursive_splitter.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,10 @@ def _chunk_text(self, text: str) -> list[str]:
353353
return chunks
354354

355355
# if no separator worked, fall back to word- or character-level chunking
356-
return self._fall_back_to_fixed_chunking(text, self.split_units)
356+
chunks = self._fall_back_to_fixed_chunking(text, self.split_units)
357+
if self.split_overlap > 0:
358+
chunks = self._apply_overlap(chunks)
359+
return chunks
357360

358361
def _fall_back_to_fixed_chunking(self, text: str, split_units: Literal["word", "char", "token"]) -> list[str]:
359362
"""
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed ``RecursiveDocumentSplitter`` silently
5+
ignoring ``split_overlap`` when none of the configured ``separators``
6+
match the input text. In that code path the component fell back to
7+
fixed-size chunking but returned the result directly without calling
8+
``_apply_overlap``, so every chunk after the first was missing the
9+
expected overlap prefix. The fix applies ``_apply_overlap`` to the
10+
fallback chunks before returning them, making behaviour consistent
11+
with the separator-matched code path. All three split units
12+
(``"char"``, ``"word"``, and ``"token"``) are affected and are now
13+
covered by regression tests.

test/components/preprocessors/test_recursive_splitter.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,3 +998,50 @@ def test_recursive_splitter_generates_unique_ids_and_correct_meta():
998998
for idx, chunk in enumerate(chunks):
999999
assert chunk.meta["parent_id"] == source_doc.id
10001000
assert chunk.meta["split_id"] == idx
1001+
1002+
1003+
def test_fallback_overlap_char_unit():
1004+
"""split_overlap must be applied even when no separator matches (char unit)."""
1005+
splitter = RecursiveDocumentSplitter(split_length=5, split_overlap=2, separators=["\n\n"], split_unit="char")
1006+
# No \n\n in text → all separators fail → final fixed-chunking fallback
1007+
text = "abcdefghij"
1008+
result = splitter.run([Document(content=text)])["documents"]
1009+
1010+
# With overlap=2 and length=5: "abcde", "defgh", "ghij"
1011+
assert len(result) == 3
1012+
assert result[0].content == "abcde"
1013+
assert result[1].content == "defgh"
1014+
assert result[2].content == "ghij"
1015+
1016+
1017+
def test_fallback_overlap_word_unit():
1018+
"""split_overlap must be applied even when no separator matches (word unit)."""
1019+
splitter = RecursiveDocumentSplitter(split_length=3, split_overlap=1, separators=["\n\n"], split_unit="word")
1020+
# No \n\n → final fixed-chunking fallback
1021+
text = "one two three four five six seven"
1022+
result = splitter.run([Document(content=text)])["documents"]
1023+
1024+
contents = [d.content for d in result]
1025+
# Each chunk must share 1 word with its neighbour
1026+
assert len(result) > 1
1027+
for i in range(len(result) - 1):
1028+
prev_words = result[i].content.split()
1029+
next_words = result[i + 1].content.split()
1030+
# The last word of chunk i must appear at the start of chunk i+1
1031+
assert prev_words[-1] == next_words[0], (
1032+
f"No overlap between chunk {i} ({contents[i]!r}) and chunk {i + 1} ({contents[i + 1]!r})"
1033+
)
1034+
1035+
1036+
@pytest.mark.integration
1037+
def test_fallback_overlap_token_unit():
1038+
"""split_overlap must be applied even when no separator matches (token unit)."""
1039+
splitter = RecursiveDocumentSplitter(split_length=4, split_overlap=2, separators=["\n\n"], split_unit="token")
1040+
# No \n\n → final fixed-chunking fallback
1041+
text = "one two three four five six seven eight"
1042+
result = splitter.run([Document(content=text)])["documents"]
1043+
1044+
# Each chunk should be at most 4 tokens; overlap means more than 1 chunk
1045+
assert len(result) > 1
1046+
for chunk in result:
1047+
assert splitter._chunk_length(chunk.content) <= 4

0 commit comments

Comments
 (0)