Repository navigation
feat(binary): any-binary normalization stage with distroless-friendly Office conversion - #6
Merged
Merged
Conversation
added 2 commits
May 15, 2026 00:36
… Office conversion Turns every caller-supplied binary into one or more LLM-renderable rows before the rest of the pipeline runs. PDFs and provider-native rasters (PNG/JPEG/GIF/WebP) pass straight through; everything else is normalised on our side so callers can submit DOCX, XLSX, PPTX, RTF, ODT, HTML, HEIC/HEIF/AVIF iPhone photos, multi-frame TIFF fax scans, SVG, ZIP/7z/TAR bundles, and EML/MSG email -- including all the multilingual content their text contains. Architecture: * New ``core/services/binary`` package with one adapter per format class (PdfGuard, ImageNormalizer, OfficeConverter protocol, ArchiveUnpacker, EmailUnpacker), orchestrated by ``BinaryNormalizer``. * Office conversion is pluggable behind ``OfficeConverter``: the default ``GotenbergConverter`` (HTTP) keeps the runtime container distroless- friendly by delegating to a Gotenberg sidecar; ``LibreOfficeConverter`` shells out to local ``soffice`` for slim/dev images that bundle it. Selected by ``FLYDESK_IDP_OFFICE_CONVERTER`` (default ``gotenberg``). * All adapters run through pyfly DI -- ``@service`` autoscan for the pure-Python ones, factory ``@bean`` in ``IDPCoreConfiguration`` for the OfficeConverter pick + the BinaryNormalizer assembly. * Typed ``BinaryNormalizationError`` subclasses (encrypted PDF, archive extraction failed, office conversion failed, image conversion failed, unsupported binary) map to RFC 7807 422s via ``ExceptionAdvice``. * Multi-doc bundles (ZIP, EML+attachments) fan out into the existing ``documents[]`` shape -- no API surface change. ``derived_from`` carries the unpack chain for traceability. Recursion depth + total fan-out are bounded by IDPSettings to guard against zip bombs. Plumbing: * Orchestrator ``_step_load`` calls the normalizer per inbound file before building ``_FileSlot`` rows. * docker-compose grows a ``gotenberg`` service that the api + worker depend on; a single ``FLYDESK_IDP_OFFICE_CONVERTER=libreoffice`` env flip switches both back to the in-container subprocess path. * Dockerfile runtime stage adds libheif1 + libcairo2 + libpango + libgdk-pixbuf so HEIC/SVG conversion works without LibreOffice bloating the image. Tests: 53 new unit tests across sniffer, pdf guard, image, archive, email, normalizer, gotenberg (HTTP mocked via respx). Full suite stays green; pyright + ruff clean.
4 tasks done
ancongui
added a commit
that referenced
this pull request
May 31, 2026
… Office conversion (#6) * feat(binary): any-binary normalization stage with distroless-friendly Office conversion Turns every caller-supplied binary into one or more LLM-renderable rows before the rest of the pipeline runs. PDFs and provider-native rasters (PNG/JPEG/GIF/WebP) pass straight through; everything else is normalised on our side so callers can submit DOCX, XLSX, PPTX, RTF, ODT, HTML, HEIC/HEIF/AVIF iPhone photos, multi-frame TIFF fax scans, SVG, ZIP/7z/TAR bundles, and EML/MSG email -- including all the multilingual content their text contains. Architecture: * New ``core/services/binary`` package with one adapter per format class (PdfGuard, ImageNormalizer, OfficeConverter protocol, ArchiveUnpacker, EmailUnpacker), orchestrated by ``BinaryNormalizer``. * Office conversion is pluggable behind ``OfficeConverter``: the default ``GotenbergConverter`` (HTTP) keeps the runtime container distroless- friendly by delegating to a Gotenberg sidecar; ``LibreOfficeConverter`` shells out to local ``soffice`` for slim/dev images that bundle it. Selected by ``FLYDESK_IDP_OFFICE_CONVERTER`` (default ``gotenberg``). * All adapters run through pyfly DI -- ``@service`` autoscan for the pure-Python ones, factory ``@bean`` in ``IDPCoreConfiguration`` for the OfficeConverter pick + the BinaryNormalizer assembly. * Typed ``BinaryNormalizationError`` subclasses (encrypted PDF, archive extraction failed, office conversion failed, image conversion failed, unsupported binary) map to RFC 7807 422s via ``ExceptionAdvice``. * Multi-doc bundles (ZIP, EML+attachments) fan out into the existing ``documents[]`` shape -- no API surface change. ``derived_from`` carries the unpack chain for traceability. Recursion depth + total fan-out are bounded by IDPSettings to guard against zip bombs. Plumbing: * Orchestrator ``_step_load`` calls the normalizer per inbound file before building ``_FileSlot`` rows. * docker-compose grows a ``gotenberg`` service that the api + worker depend on; a single ``FLYDESK_IDP_OFFICE_CONVERTER=libreoffice`` env flip switches both back to the in-container subprocess path. * Dockerfile runtime stage adds libheif1 + libcairo2 + libpango + libgdk-pixbuf so HEIC/SVG conversion works without LibreOffice bloating the image. Tests: 53 new unit tests across sniffer, pdf guard, image, archive, email, normalizer, gotenberg (HTTP mocked via respx). Full suite stays green; pyright + ruff clean. * style: ruff format pass on Phase 0 binary normalizer files --------- Co-authored-by: ancongui <andres.contreras@soon.es>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 0 of the bbox refinement initiative. The IDP service now accepts any binary callers can throw at it — DOCX, XLSX, PPTX, RTF, ODT, HTML, HEIC/HEIF/AVIF, multi-frame TIFF, SVG, ZIP/7z/TAR bundles, EML/MSG email with attachments — and turns each one into LLM-renderable bytes before extraction. Multilingual by construction: Office docs preserve their original script through Gotenberg/LibreOffice; archives + email pass content through unchanged; the multimodal LLM handles the rest.
Today the loader silently passes unknown formats through to the provider, which fails. This change makes the contract real.
Architecture
core/services/binary/— new package, one adapter per format class:PdfGuard— encrypted/corrupt PDF detectionImageNormalizer— Pillow + pillow-heif (HEIC/HEIF/AVIF), multi-frame TIFF → multi-page PDF, SVG via cairosvg, BMP → PNGOfficeConverterprotocol with two adapters:GotenbergConverter(HTTP) — default, distroless-friendly: API + worker containers carry nosoffice, just POST bytes to a Gotenberg sidecarLibreOfficeConverter(subprocess) — fallback for slim/dev images that bundle LibreOfficeArchiveUnpacker— ZIP + 7z + TAR + GZIP with zip-bomb guardsEmailUnpacker— EML (stdlib) + MSG (extract-msg) → attachments + inline bodyBinaryNormalizer— orchestrates them all, recurses into archives/emails up to a configurable depth, fans out into the existingdocuments[]shapeFLYDESK_IDP_OFFICE_CONVERTER=gotenberg|libreoffice. Adding a new adapter (OnlyOfficeConverter,CollaboraConverter, ...) is one new bean + one new enum value.EncryptedPdfError,OfficeConversionError,ArchiveExtractionError,ImageConversionError,UnsupportedBinaryError) all map to RFC 7807 422 problem-details viaExceptionAdvicewith stablecodestrings.derived_fromcarries the unpack chain for traceability.Plumbing
PipelineOrchestrator._step_loadcalls the normalizer per inbound file before building_FileSlotrows.docker-compose.ymlgrows agotenbergservice the api + worker depend on. FlipFLYDESK_IDP_OFFICE_CONVERTER=libreofficeto switch back to the in-container subprocess.Dockerfileruntime stage addslibheif1 + libcairo2 + libpango + libgdk-pixbufso HEIC/SVG conversion works without bloating the image with LibreOffice.IDPSettingsknobs:office_converter,gotenberg_url,gotenberg_timeout_s,binary_normalize_enabled(kill switch),binary_max_recursion_depth,binary_max_expanded_files,binary_libreoffice_path,binary_libreoffice_timeout_s.Why distroless
Office conversion is the only place subprocess + filesystem writes are forced —
sofficewon't take stdin/stdout for Office formats and needs a writable user-profile dir. Pure-Python alternatives (python-docx,openpyxl) lose layout fidelity, which would break the Phase 1 bbox refiner downstream. The Gotenberg sidecar is the canonical pattern: bytes-in, PDF-out over HTTP, withsoffice+ headless Chromium isolated in its own container. The API + worker containers stay distroless-eligible.Test plan
pytest tests/unit→ 147 passed, 1 skipped (the SVG test skips on macOS dev machines without libcairo; runs in the Dockerised image where libcairo2 is installed)ruff check .cleanpyright src/flydesk_idp0 errorsdocker compose upwith real Gotenberg (covered by Phase 0 follow-up)Phase plan
This is Phase 0 of the three-phase bbox-refinement initiative:
PARTIAL_SUCCEEDEDjob state + new EDA destination.Multilingual + any-binary are hard requirements across all three phases.