From ada1c095ab1913c4ccd0838245f775d7e0991f88 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Fri, 21 Aug 2026 16:57:29 +0100 Subject: [PATCH] fix(llm): bisect PDF slices on extracted text; reconcile splittable-PDF tests (#2906) Three fixes landing the PDF-slicing set coherently: - bisect_slice read raw container bytes, so the adaptive-retry path (a lone oversized slice that still overflows, #2880) searched for the newline cut in binary coordinates and could cut mid-line for a compressed PDF. Index unit_source_text instead, matching read_slice_text and expand_oversized_files; any converter failure falls back to None. - #2900's test asserted a PDF is not splittable, which #2906 overturns; rewrite it to guard the real invariant (an image has no addressable text and is never sliced). The unreadable-PDF passthrough case is covered by test_pdf_slicing. - add a bisect_slice-on-PDF regression test: the cut lands on an extracted-text line boundary and the halves tile the slice exactly. --- graphify/file_slice.py | 10 ++++++++-- tests/test_oversized_document_slicing.py | 12 +++++++----- tests/test_pdf_slicing.py | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/graphify/file_slice.py b/graphify/file_slice.py index 906fff6c..c43d4a1e 100644 --- a/graphify/file_slice.py +++ b/graphify/file_slice.py @@ -226,9 +226,15 @@ def bisect_slice(fs: FileSlice) -> tuple[FileSlice, FileSlice] | None: """ if fs.end - fs.start <= 1: return None + # Index the SAME string the slice offsets were computed against and the + # prompt carries: for a PDF that is the extracted text via unit_source_text, + # not the raw container bytes. Reading the container here (the old behavior) + # searched for the newline cut in binary coordinates, so a compressed PDF + # slice could cut mid-line or past the text end (#2906). Any converter/read + # failure means we cannot split, so fall back to None (treated as atomic). try: - text = fs.path.read_text(encoding="utf-8", errors="replace") - except OSError: + text = unit_source_text(fs.path) + except Exception: return None mid = (fs.start + fs.end) // 2 nl = text.find("\n", mid, fs.end) diff --git a/tests/test_oversized_document_slicing.py b/tests/test_oversized_document_slicing.py index 04e151bb..e8ad54a6 100644 --- a/tests/test_oversized_document_slicing.py +++ b/tests/test_oversized_document_slicing.py @@ -77,11 +77,13 @@ def test_a_small_document_is_still_passed_through_whole(tmp_path): assert units == [f], "a file under the cap must pass through as a plain Path" -def test_binary_documents_are_still_not_sliced(tmp_path): - """A PDF's bytes are not what the model is shown, so slicing the raw file - would be wrong. It must keep passing through untouched.""" - f = tmp_path / "paper.pdf" - f.write_bytes(b"%PDF-1.4\n" + b"x" * 40_000) +def test_image_documents_are_not_sliced(tmp_path): + """An image has no addressable text, so it can never be sliced and must pass + through untouched. (A PDF, by contrast, is now sliced through its converter — + #2906 — so the old assertion that PDFs are unsplittable no longer holds; the + unreadable-PDF passthrough case is covered in test_pdf_slicing.py.)""" + f = tmp_path / "diagram.png" + f.write_bytes(b"\x89PNG\r\n\x1a\n" + b"x" * 40_000) assert not is_splittable_text(f) assert expand_oversized_files([f], _FILE_CHAR_CAP) == [f] diff --git a/tests/test_pdf_slicing.py b/tests/test_pdf_slicing.py index 3eb45bb1..30008c78 100644 --- a/tests/test_pdf_slicing.py +++ b/tests/test_pdf_slicing.py @@ -23,6 +23,7 @@ import pytest from graphify.file_slice import ( FileSlice, + bisect_slice, expand_oversized_files, is_splittable_text, read_slice_text, @@ -183,3 +184,24 @@ def test_a_rewritten_pdf_is_re_read(tmp_path): def test_repeated_reads_agree(big_pdf): _, p = big_pdf assert len({unit_source_text(p) for _ in range(3)}) == 1 + + +def test_bisect_slice_of_a_pdf_indexes_extracted_text_not_bytes(big_pdf): + """The adaptive-retry path (a lone oversized slice that still overflows) calls + bisect_slice. For a PDF it must split the EXTRACTED text, not the raw + container: the cut has to land on a newline boundary in the extracted text and + the two halves must tile the original slice exactly. Reading container bytes + (the pre-fix behavior) searched for the newline in binary coordinates, so the + cut would not align to a text line.""" + _, p = big_pdf + text = unit_source_text(p) + whole = FileSlice(p, 0, len(text), 0, 1) + halves = bisect_slice(whole) + assert halves is not None, "a multi-line PDF slice must be splittable" + left, right = halves + # cut lands just after a newline in the EXTRACTED text (byte-coordinate search + # on the compressed container could not produce a text-aligned cut here) + assert text[left.end - 1] == "\n", "cut did not land on an extracted-text line boundary" + # halves tile the original slice exactly, in text coordinates + assert read_slice_text(left) + read_slice_text(right) == text + assert left.start == 0 and right.end == len(text) and left.end == right.start