Coverage for src/qdrant_loader/core/conversion/options.py: 100%
42 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-20 10:15 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-20 10:15 +0000
1"""Pure translation of a :class:`~.config.ConversionConfig` into docling options.
3:class:`DoclingOptionsBuilder` is deterministic and side-effect free: it reads the
4injected frozen config and constructs docling ``FormatOption`` / pipeline objects.
5This replaces the doc's free ``build_format_options(cfg)`` functions with a single
6cohesive class — same purity, but the config is injected once rather than threaded
7through every call.
9docling is imported lazily inside the methods so this module stays importable when
10only the markitdown engine is present (the package must load without docling until
11the engine is flipped). The overrides below neutralise docling's expensive or
12auto-resolving defaults to keep conversion fast and deterministic on a born-digital
13corpus.
14"""
16from __future__ import annotations
18from typing import TYPE_CHECKING
20from .config import ConversionConfig
22if TYPE_CHECKING:
23 from docling.datamodel.base_models import InputFormat
24 from docling.datamodel.pipeline_options import (
25 PdfPipelineOptions,
26 PictureDescriptionApiOptions,
27 )
28 from docling.document_converter import FormatOption
31class DoclingOptionsBuilder:
32 """Builds docling format/pipeline options from our engine-agnostic config."""
34 def __init__(self, config: ConversionConfig) -> None:
35 self._config = config
37 def build_format_options(self) -> dict[InputFormat, FormatOption]:
38 """Map each configured input format to its docling ``FormatOption``.
40 docx / pptx / html / csv / md are zero-config and omitted, so docling's
41 defaults merge in. Only PDF, image and XLSX carry our overrides.
42 """
43 from docling.backend.docling_parse_backend import (
44 DoclingParseDocumentBackend,
45 )
46 from docling.datamodel.backend_options import MsExcelBackendOptions
47 from docling.datamodel.base_models import InputFormat
48 from docling.document_converter import (
49 ExcelFormatOption,
50 ImageFormatOption,
51 PdfFormatOption,
52 )
54 excel = self._config.excel
55 pdf_pipeline_options = self._build_pdf_pipeline_options()
56 return {
57 # Pin the docling-parse backend so it never auto-resolves.
58 # docling 2.74.0 demoted ``DoclingParseV4DocumentBackend`` to a
59 # deprecation shim that only emits a FutureWarning before delegating to
60 # this same class; pin the parent directly to keep behaviour and silence
61 # the warning (a hard error in a future docling release).
62 InputFormat.PDF: PdfFormatOption(
63 backend=DoclingParseDocumentBackend,
64 pipeline_options=pdf_pipeline_options,
65 ),
66 InputFormat.IMAGE: ImageFormatOption(
67 pipeline_options=pdf_pipeline_options, # images run the PDF pipeline
68 ),
69 InputFormat.XLSX: ExcelFormatOption(
70 backend_options=MsExcelBackendOptions(
71 gap_tolerance=excel.gap_tolerance,
72 treat_singleton_as_text=excel.singleton_as_text,
73 sheet_names=list(excel.sheet_names) if excel.sheet_names else None,
74 ),
75 ),
76 }
78 def _build_pdf_pipeline_options(self) -> PdfPipelineOptions:
79 from docling.datamodel.accelerator_options import AcceleratorOptions
80 from docling.datamodel.pipeline_options import (
81 PdfPipelineOptions,
82 RapidOcrOptions,
83 TableFormerMode,
84 )
86 config = self._config
87 pipeline_options = PdfPipelineOptions()
89 pipeline_options.do_ocr = config.ocr.enabled
90 if config.ocr.enabled: # pin the OCR engine, never let it auto-resolve
91 pipeline_options.ocr_options = RapidOcrOptions(
92 lang=list(config.ocr.languages),
93 backend=config.ocr.backend,
94 force_full_page_ocr=config.ocr.full_page,
95 )
97 pipeline_options.do_table_structure = config.table.enabled
98 pipeline_options.table_structure_options.mode = TableFormerMode(
99 config.table.mode
100 )
101 pipeline_options.table_structure_options.do_cell_matching = (
102 config.table.cell_matching
103 )
105 pipeline_options.do_code_enrichment = config.code_formula_enrichment
106 pipeline_options.do_formula_enrichment = config.code_formula_enrichment
107 pipeline_options.accelerator_options = AcceleratorOptions(
108 device=config.device,
109 num_threads=config.num_threads,
110 )
111 pipeline_options.document_timeout = config.document_timeout # wall-clock cap
112 pipeline_options.artifacts_path = config.artifacts_path
114 if config.picture.enabled: # declarative API captioning
115 pipeline_options.enable_remote_services = True
116 pipeline_options.do_picture_description = True
117 # The enrichment stage only captions pictures whose crops were rendered;
118 # generate_picture_images defaults to False, which silently disables
119 # captioning. Scale 2 is docling's documented floor for legible crops.
120 pipeline_options.generate_picture_images = True
121 pipeline_options.images_scale = max(pipeline_options.images_scale, 2.0)
122 pipeline_options.picture_description_options = self._build_picture_options()
124 return pipeline_options
126 def _build_picture_options(self) -> PictureDescriptionApiOptions:
127 from docling.datamodel.pipeline_options import PictureDescriptionApiOptions
129 picture = self._config.picture
130 headers = (
131 {"Authorization": f"Bearer {picture.api_key}"} if picture.api_key else {}
132 )
133 return PictureDescriptionApiOptions(
134 url=picture.url,
135 headers=headers,
136 params={"model": picture.model},
137 prompt=picture.prompt,
138 timeout=picture.timeout,
139 concurrency=picture.concurrency,
140 scale=picture.scale,
141 )