docling-project / docling-project/docling-jobkit

`ConvertDocumentsOptions` default `pdf_backend` is unreachable in `_parse_backend`, breaking startup

Open
#241 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
36
Forks
47
Avg merge
7d 3h
Merged PRs (30d)
17

Description

## Summary

`ConvertDocumentsOptions.pdf_backend` defaults to `PdfBackend.THREADED_DOCLING_PARSE`, but `DoclingConverterManager._parse_backend` only handles `PdfBackend.DOCLING_PARSE` and `PdfBackend.PYPDFIUM2`, and `normalize_pdf_backend` does not map `THREADED_DOCLING_PARSE` onto either of them. Any call to `get_pdf_pipeline_opts()` with default options therefore raises:

```
RuntimeError: Unexpected PDF backend type PdfBackend.THREADED_DOCLING_PARSE
```

Because `LocalOrchestrator.warm_up_caches()` calls `get_pdf_pipeline_opts(ConvertDocumentsOptions())` with no arguments, this fires during ASGI lifespan startup and `docling-serve` cannot start at all on a clean install. No user-supplied request options are involved — the shipped default alone is enough to trigger it.

## Environment

| | |
|---|---|
| `docling-jobkit` | 1.24.0 |
| `docling` | 2.123.0 |
| `docling-core` | 2.92.0 |
| `docling-parse` | 7.16.0 |
| `docling-serve` | 1.12.0 (also reproduced on 1.15.1) |
| Python | 3.13.9 (CPython, uv-managed) |
| OS | Windows 11 |
| Install | fresh `uv venv` + `uv pip install "docling-serve[ui]"` |

## Reproduction

```powershell
uv venv
uv pip install "docling-serve[ui]"
.venv\Scripts\docling-serve.exe run
```

Startup fails immediately. Note this reproduces with the plain entry point, without `--enable-ui`, so it is unrelated to the Gradio UI.

Minimal, without `docling-serve`:

```python
from docling_jobkit.datamodel.convert import ConvertDocumentsOptions
from docling_jobkit.convert.manager import normalize_pdf_backend

opts = ConvertDocumentsOptions()
print(opts.pdf_backend) # PdfBackend.THREADED_DOCLING_PARSE
print(normalize_pdf_backend(opts.pdf_backend)) # PdfBackend.THREADED_DOCLING_PARSE (unchanged)
```

The value passes through the normalizer unchanged and then falls through to the `else` branch in `_parse_backend`.

## Traceback

```
INFO: Waiting for application startup.
ERROR: Traceback (most recent call last):
File "...\starlette\routing.py", line 694, in lifespan
async with self.lifespan_context(app) as maybe_state:
File "...\contextlib.py", line 214, in __aenter__
return await anext(self.gen)
File "...\docling_serve\app.py", line 134, in lifespan
await orchestrator.warm_up_caches()
File "...\docling_jobkit\orchestrators\local\orchestrator.py", line 133, in warm_up_caches
pdf_format_option = self.cm.get_pdf_pipeline_opts(ConvertDocumentsOptions())
File "...\docling_jobkit\convert\manager.py", line 1694, in get_pdf_pipeline_opts
backend = self._parse_backend(request)
File "...\docling_jobkit\convert\manager.py", line 1550, in _parse_backend
raise RuntimeError(f"Unexpected PDF backend type {request.pdf_backend}")
RuntimeError: Unexpected PDF backend type PdfBackend.THREADED_DOCLING_PARSE
ERROR: Application startup failed. Exiting.
```

## Relevant source

`PdfBackend` (from `docling.datamodel.pipeline_options`) has six members:

```
PYPDFIUM2, DOCLING_PARSE, THREADED_DOCLING_PARSE, DLPARSE_V1, DLPARSE_V2, DLPARSE_V4
```

`docling_jobkit/convert/manager.py` — `_parse_backend` handles two of them:

```python
def _parse_backend(self, request: ConvertDocumentsOptions) -> type[PdfDocumentBackend]:
pdf_backend = normalize_pdf_backend(request.pdf_backend)
if pdf_backend == PdfBackend.DOCLING_PARSE:
backend: type[PdfDocumentBackend] = DoclingParseDocumentBackend
elif pdf_backend == PdfBackend.PYPDFIUM2:
backend = PyPdfiumDocumentBackend
else:
raise RuntimeError(f"Unexpected PDF backend type {request.pdf_backend}")
return backend
```

`normalize_pdf_backend` collapses three of the remaining four, but not `THREADED_DOCLING_PARSE`:

```python
deprecated_mapping = {
PdfBackend.DLPARSE_V1: PdfBackend.DOCLING_PARSE,
PdfBackend.DLPARSE_V2: PdfBackend.DOCLING_PARSE,
PdfBackend.DLPARSE_V4: PdfBackend.DOCLING_PARSE,
}
```

`docling_jobkit/datamodel/convert.py` hardcodes the default, so pinning `docling` core to an older version is not a workaround:

```python
pdf_backend: Annotated[
PdfBackend,
Field(
description=(
"The PDF backend to use. String. "
f"Allowed values: {', '.join([v.value for v in PdfBackend])}. "
f"Optional, defaults to {PdfBackend.THREADED_DOCLING_PARSE.value}."
...
```

## Possible fixes

Two approaches, and which is correct depends on intent:

1. **Map it in the normalizer.** Add `THREADED_DOCLING_PARSE -> DOCLING_PARSE`. Smallest change and restores startup, but silently discards the threading intent, and the "deprecated" framing of that map doesn't really fit a current enum member.
2. **Handle it properly in `_parse_backend` / pipeline selection.** If the threaded path is meant to be live, the threading concern looks like it belongs to pipeline selection (`ThreadedStandardPdfPipeline`) rather than backend class selection, with the backend still resolving to `DoclingParseDocumentBackend`.
If it's useful, a regression test asserting that `get_pdf_pipeline_opts(ConvertDocumentsOptions())` succeeds on the bare default would catch any future drift between the declared default and the dispatch.

## Workaround for anyone hitting this

Set `pdf_backend`'s default in `docling_jobkit/datamodel/convert.py` to `PdfBackend.DOCLING_PARSE`. Explicit per-request backends are unaffected.

Contributor guide

Open the contributing guide

Research direction

Start in docling_jobkit/convert/manager.py at normalize_pdf_backend and _parse_backend, then inspect docling_jobkit/datamodel/convert.py for the declared default and the pipeline selection path. Reproduce with ConvertDocumentsOptions() and add a regression test showing that get_pdf_pipeline_opts() succeeds with the bare default. Done means clean startup no longer raises the unexpected backend error while preserving the intended threaded behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.