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

1"""Pure translation of a :class:`~.config.ConversionConfig` into docling options. 

2 

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. 

8 

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""" 

15 

16from __future__ import annotations 

17 

18from typing import TYPE_CHECKING 

19 

20from .config import ConversionConfig 

21 

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 

29 

30 

31class DoclingOptionsBuilder: 

32 """Builds docling format/pipeline options from our engine-agnostic config.""" 

33 

34 def __init__(self, config: ConversionConfig) -> None: 

35 self._config = config 

36 

37 def build_format_options(self) -> dict[InputFormat, FormatOption]: 

38 """Map each configured input format to its docling ``FormatOption``. 

39 

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 ) 

53 

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 } 

77 

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 ) 

85 

86 config = self._config 

87 pipeline_options = PdfPipelineOptions() 

88 

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 ) 

96 

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 ) 

104 

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 

113 

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() 

123 

124 return pipeline_options 

125 

126 def _build_picture_options(self) -> PictureDescriptionApiOptions: 

127 from docling.datamodel.pipeline_options import PictureDescriptionApiOptions 

128 

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 )