Coverage for src/qdrant_loader/core/conversion/settings.py: 91%
34 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"""The loader-config surface for the docling engine (pydantic) and its translation
2into the engine-internal frozen :class:`~.config.ConversionConfig`.
4The rest of qdrant-loader configures conversion through YAML, which is parsed into
5pydantic models (validation, env interpolation, the existing settings tree). The
6engine, by contrast, takes a *frozen* dataclass that knows nothing about pydantic.
7This module is the one-way bridge between the two — the anti-corruption boundary that
8keeps the engine swappable: pydantic stays in the config layer, the frozen config
9stays in the engine, and ``to_config`` is the only crossing.
11Only a curated subset of knobs is surfaced here; the rest come from the chosen
12:class:`~.config.ConversionProfile`. Surfacing more is a mechanical extension the TDD
13pass can drive as real configs demand it.
14"""
16from __future__ import annotations
18from pydantic import BaseModel, Field
20from .config import (
21 ConversionConfig,
22 ConversionProfile,
23 PictureDescriptionConfig,
24)
27class DoclingPictureSettings(BaseModel):
28 """YAML surface for API image-captioning, mapped to :class:`PictureDescriptionConfig`.
30 Off by default. When enabled it drives docling's remote picture-description
31 pipeline; ``api_key`` is typically supplied via ``${ENV_VAR}`` interpolation.
32 """
34 enabled: bool = Field(default=False)
35 url: str = Field(default="https://api.openai.com/v1/chat/completions")
36 model: str = Field(default="gpt-4o-mini")
37 api_key: str | None = Field(default=None)
38 prompt: str = Field(default="Describe this image in a few sentences.")
39 timeout: float = Field(default=60.0, gt=0)
40 concurrency: int = Field(default=1, gt=0)
42 def to_config(self) -> PictureDescriptionConfig:
43 """Project these settings into the engine's frozen picture config."""
44 return PictureDescriptionConfig(
45 enabled=self.enabled,
46 url=self.url,
47 model=self.model,
48 api_key=self.api_key,
49 prompt=self.prompt,
50 timeout=self.timeout,
51 concurrency=self.concurrency,
52 )
55class DoclingConversionSettings(BaseModel):
56 """YAML surface for the docling conversion engine.
58 A :class:`~.config.ConversionProfile` selects the baseline (cost vs. fidelity);
59 the optional fields below override individual top-level knobs on top of it. Only
60 fields the user *explicitly sets* override the profile — unset fields keep the
61 profile's value, so ``profile: accurate`` is not silently reset to the schema
62 defaults.
63 """
65 profile: ConversionProfile = Field(default=ConversionProfile.FAST)
67 # Optional top-level overrides (unset => inherit from the profile baseline).
68 max_file_size: int | None = Field(default=None, gt=0)
69 document_timeout: float | None = Field(default=None)
70 enabled_formats: tuple[str, ...] | None = Field(default=None)
71 device: str | None = Field(default=None)
72 num_threads: int | None = Field(default=None, gt=0)
73 compile_models: bool | None = Field(default=None)
74 artifacts_path: str | None = Field(default=None)
76 picture: DoclingPictureSettings = Field(default_factory=DoclingPictureSettings)
78 def to_config(self) -> ConversionConfig:
79 """Translate into the frozen engine config: profile baseline + set overrides."""
80 explicitly_set = self.model_fields_set
81 overrides: dict[str, object] = {}
83 for name in (
84 "max_file_size",
85 "document_timeout",
86 "device",
87 "num_threads",
88 "compile_models",
89 "artifacts_path",
90 ):
91 if name in explicitly_set:
92 overrides[name] = getattr(self, name)
94 if "enabled_formats" in explicitly_set and self.enabled_formats is not None:
95 # The frozen config stores a tuple; pydantic may hand back a list.
96 overrides["enabled_formats"] = tuple(self.enabled_formats)
98 if "picture" in explicitly_set:
99 overrides["picture"] = self.picture.to_config()
101 return ConversionConfig.from_profile(self.profile, **overrides)