From 8ac670714d2719caa3c609c97299fdb576d45383 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Fri, 12 Jun 2026 09:31:42 +0300 Subject: [PATCH] Tidy PDF attachment extension fix --- CHANGELOG.md | 4 +++ haiku_rag_slim/haiku/rag/client/documents.py | 13 +++------ tests/test_pdf_attachments.py | 29 +++----------------- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca167fd2..753f3b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Fixed + +- Embedded PDF attachment extension is derived from the attachment filename, not the parent's synthetic `...#attachment=` URI; non-PDF attachments (e.g. `.joboptions`) are no longer misrouted to docling's PDF backend, and unsupported extensions are skipped. + ## [0.57.0] - 2026-06-11 ### Added diff --git a/haiku_rag_slim/haiku/rag/client/documents.py b/haiku_rag_slim/haiku/rag/client/documents.py index 0faf2ceb..02175f7e 100644 --- a/haiku_rag_slim/haiku/rag/client/documents.py +++ b/haiku_rag_slim/haiku/rag/client/documents.py @@ -338,15 +338,10 @@ async def _ingest_fetch_result( existing document if one is supplied. ``depth`` tracks position in an attachment chain so the reconciliation step can bound recursion. - ``filename`` makes that name's suffix the authoritative source of the - file extension (hence the docling format). Callers pass it when the - result's ``uri`` cannot yield the right extension -- notably embedded - PDF attachments, whose synthetic ``...#attachment=`` URI carries - the attachment name in the fragment, so the URL-suffix fallback would - otherwise inherit the *parent* PDF's ``.pdf`` and feed ASCII payloads - (e.g. ``Press Quality.joboptions``) to docling's PDF backend. An empty - or unsupported suffix then fails the guard below and is rejected, - instead of being silently misrouted.""" + ``filename``, when given, makes its suffix authoritative for the file + extension (and thus the docling format), overriding the URI/content-type + fallback. Callers pass it when ``result.uri`` cannot yield the right + extension, e.g. embedded attachments whose name lives in a URI fragment.""" from haiku.rag.embeddings import embed_chunks converter = get_converter(client._config) diff --git a/tests/test_pdf_attachments.py b/tests/test_pdf_attachments.py index 2e4cfaf4..0cf52175 100644 --- a/tests/test_pdf_attachments.py +++ b/tests/test_pdf_attachments.py @@ -332,42 +332,21 @@ async def test_unsupported_attachment_continues_loop(temp_db_path, monkeypatch): async def test_joboptions_attachment_skipped_not_routed_as_pdf(temp_db_path, caplog): - """Regression: an Adobe ``.joboptions`` preset is ASCII text, embedded by - name only. Its synthetic ``...#attachment=Press%20Quality.joboptions`` URI - carries the name in a *fragment*, so the URL-suffix fallback used to inherit - the PARENT PDF's ``.pdf`` and hand the ASCII bytes to docling's PDF backend. - - The attachment's own name must now drive the extension: ``.joboptions`` is - unsupported, so the child is skipped -- never ingested, never sent to - docling. This runs the REAL ``_ingest_fetch_result`` (no monkeypatch); its - unsupported-extension guard short-circuits before any converter/embedder - call, so the sole attachment needs no LLM.""" + """An attachment's own name drives its extension, not the synthetic + ``...#attachment=`` URI (whose fragment the URL-suffix fallback drops, + inheriting the parent's ``.pdf``). ``.joboptions`` is unsupported, so the + child is skipped before any converter/embedder call (real ingest, no fake).""" import logging - from haiku.rag.client.processing import get_extension_from_content_type_or_url - pdf_bytes = build_pdf([("Press Quality.joboptions", b"/CompressObjects /Tags\n")]) async with HaikuRAG(temp_db_path, create=True) as client: parent_uri = "file:///fixtures/brochure.pdf" parent = await _make_parent(client, parent_uri, pdf_bytes) - # The latent trap: octet-stream + the fragment URI still resolves to the - # parent's ``.pdf`` -- which is exactly what the fix must no longer use. - child_uri = f"{parent_uri}#attachment=Press%20Quality.joboptions" - assert ( - get_extension_from_content_type_or_url( - child_uri, "application/octet-stream" - ) - == ".pdf" - ) - with caplog.at_level(logging.WARNING, logger="haiku.rag.client.documents"): await _reconcile_pdf_attachments(client, parent, pdf_bytes, depth=0) - # No child created: the ASCII preset was skipped, not parsed as a PDF. assert await client.list_documents(filter=parent_uri_filter(parent_uri)) == [] - assert "unsupported extension" in caplog.text - assert ".joboptions" in caplog.text async def test_cascade_delete_removes_reconciled_children(temp_db_path, monkeypatch):