{"record":{"id":"29bbd2cd8236a8bc","repo":"docling-project/docling","slug":"cannot-specify-both-ocr-preset-and-ocr-custom-conf","errorCode":null,"errorMessage":"Cannot specify both ocr_preset and ocr_custom_config.","messagePattern":"Cannot specify both ocr_preset and ocr_custom_config\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/datamodel/service/options.py","lineNumber":1118,"sourceCode":"        \"\"\"Handle deprecated ocr_engine and sync to ocr_preset.\"\"\"\n        # If ocr_engine is explicitly set (not default), sync to ocr_preset\n        if (\n            hasattr(self, \"__pydantic_fields_set__\")\n            and \"ocr_engine\" in self.__pydantic_fields_set__\n            and \"ocr_preset\" not in self.__pydantic_fields_set__\n        ):\n            warnings.warn(\n                \"ocr_engine is deprecated and will be removed in a future version. \"\n                \"Use ocr_preset instead.\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            # Sync ocr_engine value to ocr_preset only if ocr_preset wasn't explicitly set\n            object.__setattr__(self, \"ocr_preset\", self.ocr_engine)\n\n        # Ensure preset and custom_config are mutually exclusive\n        if self.ocr_preset != \"auto\" and self.ocr_custom_config:\n            raise ValueError(\"Cannot specify both ocr_preset and ocr_custom_config.\")\n\n        return self\n\n    @model_validator(mode=\"after\")\n    def validate_chunking_options(self) -> Self:\n        if self.chunking_preset and self.chunking_options is not None:\n            raise ValueError(\n                \"Cannot specify both chunking_preset and chunking_options.\"\n            )\n\n        return self\n","sourceCodeStart":1100,"sourceCodeEnd":1130,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/datamodel/service/options.py#L1100-L1130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove ocr_preset (or set it to 'auto') and keep ocr_custom_config, or drop ocr_custom_config and keep the preset.","If you still pass the deprecated ocr_engine, remove it — it is synced to ocr_preset and counts as specifying a preset.","Prefer ocr_preset over ocr_engine in all new code to avoid the sync behavior surprising you."],"exampleFix":"# before (also triggers via deprecated ocr_engine)\nopts = ConvertOptions(\n    ocr_engine=\"tesseract\",\n    ocr_custom_config={\"force_full_page_ocr\": True},\n)\n\n# after\nopts = ConvertOptions(\n    ocr_preset=\"auto\",\n    ocr_custom_config={\"force_full_page_ocr\": True},\n)","handlingStrategy":"validation","validationCode":"def assert_ocr(opts: dict) -> None:\n    preset = opts.get(\"ocr_preset\", opts.get(\"ocr_engine\", \"auto\"))\n    assert preset == \"auto\" or not opts.get(\"ocr_custom_config\"), (\n        \"ocr_preset (including synced ocr_engine) conflicts with ocr_custom_config\"\n    )","typeGuard":null,"tryCatchPattern":"try:\n    ConvertOptions(**cfg)\nexcept ValidationError as e:\n    if \"ocr_preset\" in str(e):\n        cfg.setdefault(\"ocr_preset\", \"auto\")\n        cfg.pop(\"ocr_engine\", None)\n        opts = ConvertOptions(**cfg)\n    else:\n        raise","preventionTips":["Migrate off the deprecated ocr_engine entirely to avoid the silent sync into ocr_preset.","Treat ocr_preset='auto' as 'no preset' when pairing with ocr_custom_config."],"tags":["pydantic","validation","ocr","deprecation","mutually-exclusive"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}