Tidy PDF attachment extension fix
This commit is contained in:
parent
bce24e84a0
commit
8ac670714d
3 changed files with 12 additions and 34 deletions
|
|
@ -1,6 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Embedded PDF attachment extension is derived from the attachment filename, not the parent's synthetic `...#attachment=<name>` 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
|
## [0.57.0] - 2026-06-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -338,15 +338,10 @@ async def _ingest_fetch_result(
|
||||||
existing document if one is supplied. ``depth`` tracks position in an
|
existing document if one is supplied. ``depth`` tracks position in an
|
||||||
attachment chain so the reconciliation step can bound recursion.
|
attachment chain so the reconciliation step can bound recursion.
|
||||||
|
|
||||||
``filename`` makes that name's suffix the authoritative source of the
|
``filename``, when given, makes its suffix authoritative for the file
|
||||||
file extension (hence the docling format). Callers pass it when the
|
extension (and thus the docling format), overriding the URI/content-type
|
||||||
result's ``uri`` cannot yield the right extension -- notably embedded
|
fallback. Callers pass it when ``result.uri`` cannot yield the right
|
||||||
PDF attachments, whose synthetic ``...#attachment=<name>`` URI carries
|
extension, e.g. embedded attachments whose name lives in a URI fragment."""
|
||||||
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."""
|
|
||||||
from haiku.rag.embeddings import embed_chunks
|
from haiku.rag.embeddings import embed_chunks
|
||||||
|
|
||||||
converter = get_converter(client._config)
|
converter = get_converter(client._config)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
async def test_joboptions_attachment_skipped_not_routed_as_pdf(temp_db_path, caplog):
|
||||||
"""Regression: an Adobe ``.joboptions`` preset is ASCII text, embedded by
|
"""An attachment's own name drives its extension, not the synthetic
|
||||||
name only. Its synthetic ``...#attachment=Press%20Quality.joboptions`` URI
|
``...#attachment=<name>`` URI (whose fragment the URL-suffix fallback drops,
|
||||||
carries the name in a *fragment*, so the URL-suffix fallback used to inherit
|
inheriting the parent's ``.pdf``). ``.joboptions`` is unsupported, so the
|
||||||
the PARENT PDF's ``.pdf`` and hand the ASCII bytes to docling's PDF backend.
|
child is skipped before any converter/embedder call (real ingest, no fake)."""
|
||||||
|
|
||||||
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."""
|
|
||||||
import logging
|
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")])
|
pdf_bytes = build_pdf([("Press Quality.joboptions", b"/CompressObjects /Tags\n")])
|
||||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||||
parent_uri = "file:///fixtures/brochure.pdf"
|
parent_uri = "file:///fixtures/brochure.pdf"
|
||||||
parent = await _make_parent(client, parent_uri, pdf_bytes)
|
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"):
|
with caplog.at_level(logging.WARNING, logger="haiku.rag.client.documents"):
|
||||||
await _reconcile_pdf_attachments(client, parent, pdf_bytes, depth=0)
|
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 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):
|
async def test_cascade_delete_removes_reconciled_children(temp_db_path, monkeypatch):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue