docling-project/docling · error · ValueError

Cannot specify both ocr_preset and ocr_custom_config.

Error message

Cannot specify both ocr_preset and ocr_custom_config.

What it means

The OCR options validator rejects configurations that set both ocr_preset (any value other than the default 'auto') and ocr_custom_config. Note the special case: ocr_preset='auto' may coexist with a custom config, because 'auto' is treated as 'no explicit preset'. A deprecated ocr_engine field is synced into ocr_preset first, so setting ocr_engine alongside ocr_custom_config also triggers this.

Source

Thrown at docling/datamodel/service/options.py:1118

        """Handle deprecated ocr_engine and sync to ocr_preset."""
        # If ocr_engine is explicitly set (not default), sync to ocr_preset
        if (
            hasattr(self, "__pydantic_fields_set__")
            and "ocr_engine" in self.__pydantic_fields_set__
            and "ocr_preset" not in self.__pydantic_fields_set__
        ):
            warnings.warn(
                "ocr_engine is deprecated and will be removed in a future version. "
                "Use ocr_preset instead.",
                DeprecationWarning,
                stacklevel=2,
            )
            # Sync ocr_engine value to ocr_preset only if ocr_preset wasn't explicitly set
            object.__setattr__(self, "ocr_preset", self.ocr_engine)

        # Ensure preset and custom_config are mutually exclusive
        if self.ocr_preset != "auto" and self.ocr_custom_config:
            raise ValueError("Cannot specify both ocr_preset and ocr_custom_config.")

        return self

    @model_validator(mode="after")
    def validate_chunking_options(self) -> Self:
        if self.chunking_preset and self.chunking_options is not None:
            raise ValueError(
                "Cannot specify both chunking_preset and chunking_options."
            )

        return self

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Remove ocr_preset (or set it to 'auto') and keep ocr_custom_config, or drop ocr_custom_config and keep the preset.
  2. If you still pass the deprecated ocr_engine, remove it — it is synced to ocr_preset and counts as specifying a preset.
  3. Prefer ocr_preset over ocr_engine in all new code to avoid the sync behavior surprising you.

Example fix

# before (also triggers via deprecated ocr_engine)
opts = ConvertOptions(
    ocr_engine="tesseract",
    ocr_custom_config={"force_full_page_ocr": True},
)

# after
opts = ConvertOptions(
    ocr_preset="auto",
    ocr_custom_config={"force_full_page_ocr": True},
)
Defensive patterns

Strategy: validation

Validate before calling

def assert_ocr(opts: dict) -> None:
    preset = opts.get("ocr_preset", opts.get("ocr_engine", "auto"))
    assert preset == "auto" or not opts.get("ocr_custom_config"), (
        "ocr_preset (including synced ocr_engine) conflicts with ocr_custom_config"
    )

Try / catch

try:
    ConvertOptions(**cfg)
except ValidationError as e:
    if "ocr_preset" in str(e):
        cfg.setdefault("ocr_preset", "auto")
        cfg.pop("ocr_engine", None)
        opts = ConvertOptions(**cfg)
    else:
        raise

Prevention

When it happens

Trigger: Passing ocr_preset='easyocr' together with ocr_custom_config={...}; or passing the deprecated ocr_engine='tesseract' together with ocr_custom_config, because the validator syncs ocr_engine into ocr_preset before the exclusivity check.

Common situations: Migrating old scripts that used ocr_engine to the new ocr_preset API while custom OCR settings are also present; presets used as a baseline with custom tweaks layered on; a deprecation warning was already emitted and the user half-migrated.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/29bbd2cd8236a8bc. Report an issue: GitHub.