From e55e3d13c881ba163bd02a6ae13a9a9feef4b9a8 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 6 May 2026 12:57:41 +0300 Subject: [PATCH] fix _patch_picture_descriptions silently dropping docling_pages --- haiku_rag_slim/haiku/rag/client/rebuild.py | 12 ++++++++++- tests/test_rebuild.py | 24 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/client/rebuild.py b/haiku_rag_slim/haiku/rag/client/rebuild.py index 0d51d5dc..dddb0c64 100644 --- a/haiku_rag_slim/haiku/rag/client/rebuild.py +++ b/haiku_rag_slim/haiku/rag/client/rebuild.py @@ -358,7 +358,17 @@ async def _patch_picture_descriptions(client: "HaikuRAG", doc: Document) -> int: pic.meta = PictureMeta() pic.meta.description = DescriptionMetaField(text=text) - doc.set_docling(docling_doc) + # Update only docling_document — set_docling would also overwrite + # docling_pages by routing through compress_docling_split, which + # extracts pages from the in-memory JSON and finds none (the pages + # blob is stored separately and is not loaded by get_docling_document). + # That would silently destroy page rasters for every doc with at + # least one undescribed picture. + from haiku.rag.store.compression import compress_docling_split + + structure_bytes, _ = compress_docling_split(docling_doc.model_dump_json()) + doc.docling_document = structure_bytes + doc.docling_version = docling_doc.version return len(descriptions) diff --git a/tests/test_rebuild.py b/tests/test_rebuild.py index 1ecc00a3..51d54be5 100644 --- a/tests/test_rebuild.py +++ b/tests/test_rebuild.py @@ -440,8 +440,17 @@ async def test_rebuild_descriptions_requires_enabled(temp_db_path): @pytest.mark.vcr() async def test_rebuild_descriptions_patches_blob_and_chunks(temp_db_path, monkeypatch): """End-to-end: ingest a doc with a picture (no VLM at ingest), then run - rebuild --descriptions with the VLM mocked. The docling blob should gain - the description in meta, and the chunk text should pick it up.""" + rebuild --descriptions with the VLM mocked. After the rebuild: + + - the docling blob has the description in meta; + - the chunk text picks it up; + - the docling_pages blob is preserved untouched. + + The pages assertion guards against a foot-gun in compress_docling_split: + the docling document loaded via get_docling_document() never carries + pages (they live in a separate column), so calling set_docling() after + patching would write pages_bytes=None and silently destroy page rasters + on disk — breaking visualize_chunk for the affected docs.""" from haiku.rag.client.documents import _store_document_with_chunks from haiku.rag.config import AppConfig from haiku.rag.store.models.document import Document @@ -458,6 +467,14 @@ async def test_rebuild_descriptions_patches_blob_and_chunks(temp_db_path, monkey created = await _store_document_with_chunks(rag, document, [], docling_doc) assert created.id is not None + # _docling_doc_with_picture has no PageItems, so set_docling leaves + # docling_pages as None. Inject sentinel bytes to stand in for what + # a real ingest with generate_page_images=True would store. + sentinel_pages = b"\x80SENTINEL_PAGE_BYTES" + await rag.store.documents_table.update( + {"docling_pages": sentinel_pages}, where=f"id = '{created.id}'" + ) + from_blob = ( await rag.document_repository.get_by_id(created.id) ).get_docling_document() # type: ignore[union-attr] @@ -503,6 +520,9 @@ async def test_rebuild_descriptions_patches_blob_and_chunks(temp_db_path, monkey chunks = await rag.chunk_repository.get_by_document_id(created.id) assert any("A red square (mocked)." in (c.content or "") for c in chunks) + # docling_pages must survive untouched — see docstring. + assert after.docling_pages == sentinel_pages + @pytest.mark.vcr() async def test_rebuild_descriptions_skips_already_described(temp_db_path, monkeypatch):