Spaces:
Configuration error
Configuration error
Michele Dolfi
commited on
feat: Add new docling-serve cli (#50)
Browse filesSigned-off-by: Michele Dolfi <[email protected]>
- .github/workflows/ci-images-dryrun.yml +2 -2
- .github/workflows/images.yml +2 -2
- .github/workflows/job-image.yml +0 -4
- Containerfile +5 -11
- Makefile +3 -3
- README.md +68 -10
- docling_serve/.env.example +1 -1
- docling_serve/__main__.py +278 -17
- docling_serve/app.py +141 -139
- docling_serve/settings.py +24 -2
- pyproject.toml +6 -0
- start_server.sh +0 -30
- uv.lock +3 -1
.github/workflows/ci-images-dryrun.yml
CHANGED
|
@@ -20,7 +20,7 @@ jobs:
|
|
| 20 |
with:
|
| 21 |
publish: false
|
| 22 |
build_args: |
|
| 23 |
-
|
| 24 |
ghcr_image_name: ds4sd/docling-serve-cpu
|
| 25 |
quay_image_name: ""
|
| 26 |
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
| 37 |
with:
|
| 38 |
publish: false
|
| 39 |
build_args: |
|
| 40 |
-
|
| 41 |
platforms: linux/amd64
|
| 42 |
ghcr_image_name: ds4sd/docling-serve
|
| 43 |
quay_image_name: ""
|
|
|
|
| 20 |
with:
|
| 21 |
publish: false
|
| 22 |
build_args: |
|
| 23 |
+
UV_SYNC_EXTRA_ARGS=--no-extra cu124
|
| 24 |
ghcr_image_name: ds4sd/docling-serve-cpu
|
| 25 |
quay_image_name: ""
|
| 26 |
|
|
|
|
| 37 |
with:
|
| 38 |
publish: false
|
| 39 |
build_args: |
|
| 40 |
+
UV_SYNC_EXTRA_ARGS=--no-extra cpu
|
| 41 |
platforms: linux/amd64
|
| 42 |
ghcr_image_name: ds4sd/docling-serve
|
| 43 |
quay_image_name: ""
|
.github/workflows/images.yml
CHANGED
|
@@ -34,7 +34,7 @@ jobs:
|
|
| 34 |
publish: true
|
| 35 |
environment: registry-creds
|
| 36 |
build_args: |
|
| 37 |
-
|
| 38 |
ghcr_image_name: ds4sd/docling-serve-cpu
|
| 39 |
quay_image_name: ds4sd/docling-serve-cpu
|
| 40 |
|
|
@@ -53,7 +53,7 @@ jobs:
|
|
| 53 |
publish: true
|
| 54 |
environment: registry-creds
|
| 55 |
build_args: |
|
| 56 |
-
|
| 57 |
platforms: linux/amd64
|
| 58 |
ghcr_image_name: ds4sd/docling-serve
|
| 59 |
quay_image_name: ds4sd/docling-serve
|
|
|
|
| 34 |
publish: true
|
| 35 |
environment: registry-creds
|
| 36 |
build_args: |
|
| 37 |
+
UV_SYNC_EXTRA_ARGS=--no-extra cu124
|
| 38 |
ghcr_image_name: ds4sd/docling-serve-cpu
|
| 39 |
quay_image_name: ds4sd/docling-serve-cpu
|
| 40 |
|
|
|
|
| 53 |
publish: true
|
| 54 |
environment: registry-creds
|
| 55 |
build_args: |
|
| 56 |
+
UV_SYNC_EXTRA_ARGS=--no-extra cpu
|
| 57 |
platforms: linux/amd64
|
| 58 |
ghcr_image_name: ds4sd/docling-serve
|
| 59 |
quay_image_name: ds4sd/docling-serve
|
.github/workflows/job-image.yml
CHANGED
|
@@ -105,8 +105,6 @@ jobs:
|
|
| 105 |
cache-to: type=gha,mode=max
|
| 106 |
file: Containerfile
|
| 107 |
build-args: ${{ inputs.build_args }}
|
| 108 |
-
# |
|
| 109 |
-
# --build-arg CPU_ONLY=true
|
| 110 |
|
| 111 |
- name: Generate artifact attestation
|
| 112 |
if: ${{ inputs.publish }}
|
|
@@ -137,8 +135,6 @@ jobs:
|
|
| 137 |
cache-to: type=gha,mode=max
|
| 138 |
file: Containerfile
|
| 139 |
build-args: ${{ inputs.build_args }}
|
| 140 |
-
# |
|
| 141 |
-
# --build-arg CPU_ONLY=true
|
| 142 |
|
| 143 |
- name: Remove Local Docker Images
|
| 144 |
run: |
|
|
|
|
| 105 |
cache-to: type=gha,mode=max
|
| 106 |
file: Containerfile
|
| 107 |
build-args: ${{ inputs.build_args }}
|
|
|
|
|
|
|
| 108 |
|
| 109 |
- name: Generate artifact attestation
|
| 110 |
if: ${{ inputs.publish }}
|
|
|
|
| 135 |
cache-to: type=gha,mode=max
|
| 136 |
file: Containerfile
|
| 137 |
build-args: ${{ inputs.build_args }}
|
|
|
|
|
|
|
| 138 |
|
| 139 |
- name: Remove Local Docker Images
|
| 140 |
run: |
|
Containerfile
CHANGED
|
@@ -2,8 +2,8 @@ ARG BASE_IMAGE=quay.io/sclorg/python-312-c9s:c9s
|
|
| 2 |
|
| 3 |
FROM ${BASE_IMAGE}
|
| 4 |
|
| 5 |
-
ARG CPU_ONLY=false
|
| 6 |
ARG MODELS_LIST="layout tableformer picture_classifier easyocr"
|
|
|
|
| 7 |
|
| 8 |
USER 0
|
| 9 |
|
|
@@ -41,17 +41,10 @@ ENV PYTHONIOENCODING=utf-8
|
|
| 41 |
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
| 42 |
ENV UV_PROJECT_ENVIRONMENT=/opt/app-root
|
| 43 |
|
| 44 |
-
ENV WITH_UI=True
|
| 45 |
-
|
| 46 |
COPY --chown=1001:0 pyproject.toml uv.lock README.md ./
|
| 47 |
|
| 48 |
RUN --mount=type=cache,target=/opt/app-root/src/.cache/uv,uid=1001 \
|
| 49 |
-
|
| 50 |
-
NO_EXTRA=cu124; \
|
| 51 |
-
else \
|
| 52 |
-
NO_EXTRA=cpu; \
|
| 53 |
-
fi && \
|
| 54 |
-
uv sync --frozen --no-install-project --no-dev --all-extras --no-extra ${NO_EXTRA}
|
| 55 |
|
| 56 |
RUN echo "Downloading models..." && \
|
| 57 |
docling-tools models download ${MODELS_LIST} && \
|
|
@@ -59,8 +52,9 @@ RUN echo "Downloading models..." && \
|
|
| 59 |
chmod -R g=u /opt/app-root/src/.cache
|
| 60 |
|
| 61 |
COPY --chown=1001:0 --chmod=664 ./docling_serve ./docling_serve
|
| 62 |
-
|
|
|
|
| 63 |
|
| 64 |
EXPOSE 5001
|
| 65 |
|
| 66 |
-
CMD ["
|
|
|
|
| 2 |
|
| 3 |
FROM ${BASE_IMAGE}
|
| 4 |
|
|
|
|
| 5 |
ARG MODELS_LIST="layout tableformer picture_classifier easyocr"
|
| 6 |
+
ARG UV_SYNC_EXTRA_ARGS=""
|
| 7 |
|
| 8 |
USER 0
|
| 9 |
|
|
|
|
| 41 |
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
| 42 |
ENV UV_PROJECT_ENVIRONMENT=/opt/app-root
|
| 43 |
|
|
|
|
|
|
|
| 44 |
COPY --chown=1001:0 pyproject.toml uv.lock README.md ./
|
| 45 |
|
| 46 |
RUN --mount=type=cache,target=/opt/app-root/src/.cache/uv,uid=1001 \
|
| 47 |
+
uv sync --frozen --no-install-project --no-dev --all-extras ${UV_SYNC_EXTRA_ARGS} # --no-extra ${NO_EXTRA}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
RUN echo "Downloading models..." && \
|
| 50 |
docling-tools models download ${MODELS_LIST} && \
|
|
|
|
| 52 |
chmod -R g=u /opt/app-root/src/.cache
|
| 53 |
|
| 54 |
COPY --chown=1001:0 --chmod=664 ./docling_serve ./docling_serve
|
| 55 |
+
RUN --mount=type=cache,target=/opt/app-root/src/.cache/uv,uid=1001 \
|
| 56 |
+
uv sync --frozen --no-dev --all-extras ${UV_SYNC_EXTRA_ARGS} # --no-extra ${NO_EXTRA}
|
| 57 |
|
| 58 |
EXPOSE 5001
|
| 59 |
|
| 60 |
+
CMD ["docling-serve", "run"]
|
Makefile
CHANGED
|
@@ -26,15 +26,15 @@ md-lint-file:
|
|
| 26 |
|
| 27 |
.PHONY: docling-serve-cpu-image
|
| 28 |
docling-serve-cpu-image: Containerfile ## Build docling-serve "cpu only" container image
|
| 29 |
-
$(ECHO_PREFIX) printf " %-12s Containerfile\n" "[docling-serve CPU
|
| 30 |
-
$(CMD_PREFIX) docker build --build-arg
|
| 31 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve-cpu:$(TAG) ghcr.io/ds4sd/docling-serve-cpu:main
|
| 32 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve-cpu:$(TAG) quay.io/ds4sd/docling-serve-cpu:main
|
| 33 |
|
| 34 |
.PHONY: docling-serve-gpu-image
|
| 35 |
docling-serve-gpu-image: Containerfile ## Build docling-serve container image with GPU support
|
| 36 |
$(ECHO_PREFIX) printf " %-12s Containerfile\n" "[docling-serve with GPU]"
|
| 37 |
-
$(CMD_PREFIX) docker build --build-arg
|
| 38 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve:$(TAG) ghcr.io/ds4sd/docling-serve:main
|
| 39 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve:$(TAG) quay.io/ds4sd/docling-serve:main
|
| 40 |
|
|
|
|
| 26 |
|
| 27 |
.PHONY: docling-serve-cpu-image
|
| 28 |
docling-serve-cpu-image: Containerfile ## Build docling-serve "cpu only" container image
|
| 29 |
+
$(ECHO_PREFIX) printf " %-12s Containerfile\n" "[docling-serve CPU]"
|
| 30 |
+
$(CMD_PREFIX) docker build --load --build-arg "UV_SYNC_EXTRA_ARGS=--no-extra cu124" -f Containerfile -t ghcr.io/ds4sd/docling-serve-cpu:$(TAG) .
|
| 31 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve-cpu:$(TAG) ghcr.io/ds4sd/docling-serve-cpu:main
|
| 32 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve-cpu:$(TAG) quay.io/ds4sd/docling-serve-cpu:main
|
| 33 |
|
| 34 |
.PHONY: docling-serve-gpu-image
|
| 35 |
docling-serve-gpu-image: Containerfile ## Build docling-serve container image with GPU support
|
| 36 |
$(ECHO_PREFIX) printf " %-12s Containerfile\n" "[docling-serve with GPU]"
|
| 37 |
+
$(CMD_PREFIX) docker build --load --build-arg "UV_SYNC_EXTRA_ARGS=--no-extra cpu" -f Containerfile --platform linux/amd64 -t ghcr.io/ds4sd/docling-serve:$(TAG) .
|
| 38 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve:$(TAG) ghcr.io/ds4sd/docling-serve:main
|
| 39 |
$(CMD_PREFIX) docker tag ghcr.io/ds4sd/docling-serve:$(TAG) quay.io/ds4sd/docling-serve:main
|
| 40 |
|
README.md
CHANGED
|
@@ -327,25 +327,83 @@ See `[project.optional-dependencies]` section in `pyproject.toml` for full list
|
|
| 327 |
|
| 328 |
### Run the server
|
| 329 |
|
| 330 |
-
The
|
|
|
|
| 331 |
|
| 332 |
```sh
|
| 333 |
-
# Run the server
|
| 334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
```
|
| 339 |
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
|
| 342 |
-
The
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
- `DOCLING_ARTIFACTS_PATH`: if set Docling will use only the local weights of models, for example `/opt/app-root/.cache/docling/cache`.
|
| 345 |
- `TESSDATA_PREFIX`: Tesseract data location, example `/usr/share/tesseract/tessdata/`.
|
| 346 |
-
- `UVICORN_WORKERS`: Number of workers to use.
|
| 347 |
-
- `RELOAD`: If `True`, this will enable auto-reload when you modify files, useful for development.
|
| 348 |
-
- `WITH_UI`: If `True`, The Gradio UI will be available at `/ui`.
|
| 349 |
|
| 350 |
## Get help and support
|
| 351 |
|
|
|
|
| 327 |
|
| 328 |
### Run the server
|
| 329 |
|
| 330 |
+
The `docling-serve` executable is a convenient script for launching the webserver both in
|
| 331 |
+
development and production mode.
|
| 332 |
|
| 333 |
```sh
|
| 334 |
+
# Run the server in development mode
|
| 335 |
+
# - reload is enabled by default
|
| 336 |
+
# - listening on the 127.0.0.1 address
|
| 337 |
+
# - ui is enabled by default
|
| 338 |
+
docling-serve dev
|
| 339 |
+
|
| 340 |
+
# Run the server in production mode
|
| 341 |
+
# - reload is disabled by default
|
| 342 |
+
# - listening on the 0.0.0.0 address
|
| 343 |
+
# - ui is disabled by default
|
| 344 |
+
docling-serve run
|
| 345 |
+
```
|
| 346 |
+
|
| 347 |
+
### Options
|
| 348 |
|
| 349 |
+
The `docling-serve` executable allows is controlled with both command line
|
| 350 |
+
options and environment variables.
|
| 351 |
+
|
| 352 |
+
<details>
|
| 353 |
+
<summary>`docling-serve` help message</summary>
|
| 354 |
+
|
| 355 |
+
```sh
|
| 356 |
+
$ docling-serve dev --help
|
| 357 |
+
|
| 358 |
+
Usage: docling-serve dev [OPTIONS]
|
| 359 |
+
|
| 360 |
+
Run a Docling Serve app in development mode. 🧪
|
| 361 |
+
This is equivalent to docling-serve run but with reload
|
| 362 |
+
enabled and listening on the 127.0.0.1 address.
|
| 363 |
+
|
| 364 |
+
Options can be set also with the corresponding ENV variable, with the exception
|
| 365 |
+
of --enable-ui, --host and --reload.
|
| 366 |
+
|
| 367 |
+
╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
| 368 |
+
│ --host TEXT The host to serve on. For local development in localhost │
|
| 369 |
+
│ use 127.0.0.1. To enable public access, e.g. in a │
|
| 370 |
+
│ container, use all the IP addresses available with │
|
| 371 |
+
│ 0.0.0.0. │
|
| 372 |
+
│ [default: 127.0.0.1] │
|
| 373 |
+
│ --port INTEGER The port to serve on. [default: 5001] │
|
| 374 |
+
│ --reload --no-reload Enable auto-reload of the server when (code) files │
|
| 375 |
+
│ change. This is resource intensive, use it only during │
|
| 376 |
+
│ development. │
|
| 377 |
+
│ [default: reload] │
|
| 378 |
+
│ --root-path TEXT The root path is used to tell your app that it is being │
|
| 379 |
+
│ served to the outside world with some path prefix set up │
|
| 380 |
+
│ in some termination proxy or similar. │
|
| 381 |
+
│ --proxy-headers --no-proxy-headers Enable/Disable X-Forwarded-Proto, X-Forwarded-For, │
|
| 382 |
+
│ X-Forwarded-Port to populate remote address info. │
|
| 383 |
+
│ [default: proxy-headers] │
|
| 384 |
+
│ --enable-ui --no-enable-ui Enable the development UI. [default: enable-ui] │
|
| 385 |
+
│ --help Show this message and exit. │
|
| 386 |
+
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
| 387 |
```
|
| 388 |
|
| 389 |
+
</details>
|
| 390 |
+
|
| 391 |
+
#### Environment variables
|
| 392 |
+
|
| 393 |
+
The environment variables controlling the `uvicorn` execution can be specified with the `UVICORN_` prefix:
|
| 394 |
+
|
| 395 |
+
- `UVICORN_WORKERS`: Number of workers to use.
|
| 396 |
+
- `UVICORN_RELOAD`: If `True`, this will enable auto-reload when you modify files, useful for development.
|
| 397 |
|
| 398 |
+
The environment variables controlling specifics of the Docling Serve app can be specified with the
|
| 399 |
+
`DOCLING_SERVE_` prefix:
|
| 400 |
+
|
| 401 |
+
- `DOCLING_SERVE_ENABLE_UI`: If `True`, The Gradio UI will be available at `/ui`.
|
| 402 |
+
|
| 403 |
+
Others:
|
| 404 |
|
| 405 |
- `DOCLING_ARTIFACTS_PATH`: if set Docling will use only the local weights of models, for example `/opt/app-root/.cache/docling/cache`.
|
| 406 |
- `TESSDATA_PREFIX`: Tesseract data location, example `/usr/share/tesseract/tessdata/`.
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
## Get help and support
|
| 409 |
|
docling_serve/.env.example
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
TESSDATA_PREFIX=/usr/share/tesseract/tessdata/
|
| 2 |
UVICORN_WORKERS=2
|
| 3 |
-
|
|
|
|
| 1 |
TESSDATA_PREFIX=/usr/share/tesseract/tessdata/
|
| 2 |
UVICORN_WORKERS=2
|
| 3 |
+
UVICORN_RELOAD=True
|
docling_serve/__main__.py
CHANGED
|
@@ -1,20 +1,281 @@
|
|
| 1 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib
|
| 2 |
+
import logging
|
| 3 |
+
import platform
|
| 4 |
+
import sys
|
| 5 |
+
import warnings
|
| 6 |
+
from typing import Annotated, Any, Union
|
| 7 |
|
| 8 |
+
import typer
|
| 9 |
+
import uvicorn
|
| 10 |
+
from rich.console import Console
|
| 11 |
|
| 12 |
+
from docling_serve.settings import docling_serve_settings, uvicorn_settings
|
| 13 |
+
|
| 14 |
+
warnings.filterwarnings(action="ignore", category=UserWarning, module="pydantic|torch")
|
| 15 |
+
warnings.filterwarnings(action="ignore", category=FutureWarning, module="easyocr")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
err_console = Console(stderr=True)
|
| 19 |
+
console = Console()
|
| 20 |
+
|
| 21 |
+
app = typer.Typer(
|
| 22 |
+
no_args_is_help=True,
|
| 23 |
+
rich_markup_mode="rich",
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def version_callback(value: bool) -> None:
|
| 30 |
+
if value:
|
| 31 |
+
docling_serve_version = importlib.metadata.version("docling_serve")
|
| 32 |
+
docling_version = importlib.metadata.version("docling")
|
| 33 |
+
docling_core_version = importlib.metadata.version("docling-core")
|
| 34 |
+
docling_ibm_models_version = importlib.metadata.version("docling-ibm-models")
|
| 35 |
+
docling_parse_version = importlib.metadata.version("docling-parse")
|
| 36 |
+
platform_str = platform.platform()
|
| 37 |
+
py_impl_version = sys.implementation.cache_tag
|
| 38 |
+
py_lang_version = platform.python_version()
|
| 39 |
+
console.print(f"Docling Serve version: {docling_serve_version}")
|
| 40 |
+
console.print(f"Docling version: {docling_version}")
|
| 41 |
+
console.print(f"Docling Core version: {docling_core_version}")
|
| 42 |
+
console.print(f"Docling IBM Models version: {docling_ibm_models_version}")
|
| 43 |
+
console.print(f"Docling Parse version: {docling_parse_version}")
|
| 44 |
+
console.print(f"Python: {py_impl_version} ({py_lang_version})")
|
| 45 |
+
console.print(f"Platform: {platform_str}")
|
| 46 |
+
raise typer.Exit()
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@app.callback()
|
| 50 |
+
def callback(
|
| 51 |
+
version: Annotated[
|
| 52 |
+
Union[bool, None],
|
| 53 |
+
typer.Option(
|
| 54 |
+
"--version", help="Show the version and exit.", callback=version_callback
|
| 55 |
+
),
|
| 56 |
+
] = None,
|
| 57 |
+
verbose: Annotated[
|
| 58 |
+
int,
|
| 59 |
+
typer.Option(
|
| 60 |
+
"--verbose",
|
| 61 |
+
"-v",
|
| 62 |
+
count=True,
|
| 63 |
+
help="Set the verbosity level. -v for info logging, -vv for debug logging.",
|
| 64 |
+
),
|
| 65 |
+
] = 0,
|
| 66 |
+
) -> None:
|
| 67 |
+
if verbose == 0:
|
| 68 |
+
logging.basicConfig(level=logging.WARNING)
|
| 69 |
+
elif verbose == 1:
|
| 70 |
+
logging.basicConfig(level=logging.INFO)
|
| 71 |
+
elif verbose == 2:
|
| 72 |
+
logging.basicConfig(level=logging.DEBUG)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _run(
|
| 76 |
+
*,
|
| 77 |
+
command: str,
|
| 78 |
+
) -> None:
|
| 79 |
+
server_type = "development" if command == "dev" else "production"
|
| 80 |
+
|
| 81 |
+
console.print(f"Starting {server_type} server 🚀")
|
| 82 |
+
|
| 83 |
+
url = f"http://{uvicorn_settings.host}:{uvicorn_settings.port}"
|
| 84 |
+
url_docs = f"{url}/docs"
|
| 85 |
+
url_ui = f"{url}/ui"
|
| 86 |
+
|
| 87 |
+
console.print("")
|
| 88 |
+
console.print(f"Server started at [link={url}]{url}[/]")
|
| 89 |
+
console.print(f"Documentation at [link={url_docs}]{url_docs}[/]")
|
| 90 |
+
if docling_serve_settings.enable_ui:
|
| 91 |
+
console.print(f"UI at [link={url_ui}]{url_ui}[/]")
|
| 92 |
+
|
| 93 |
+
if command == "dev":
|
| 94 |
+
console.print("")
|
| 95 |
+
console.print(
|
| 96 |
+
"Running in development mode, for production use: "
|
| 97 |
+
"[bold]docling-serve run[/]",
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
console.print("")
|
| 101 |
+
console.print("Logs:")
|
| 102 |
+
|
| 103 |
+
uvicorn.run(
|
| 104 |
+
app="docling_serve.app:create_app",
|
| 105 |
+
factory=True,
|
| 106 |
+
host=uvicorn_settings.host,
|
| 107 |
+
port=uvicorn_settings.port,
|
| 108 |
+
reload=uvicorn_settings.reload,
|
| 109 |
+
workers=uvicorn_settings.workers,
|
| 110 |
+
root_path=uvicorn_settings.root_path,
|
| 111 |
+
proxy_headers=uvicorn_settings.proxy_headers,
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@app.command()
|
| 116 |
+
def dev(
|
| 117 |
+
*,
|
| 118 |
+
# uvicorn options
|
| 119 |
+
host: Annotated[
|
| 120 |
+
str,
|
| 121 |
+
typer.Option(
|
| 122 |
+
help=(
|
| 123 |
+
"The host to serve on. For local development in localhost "
|
| 124 |
+
"use [blue]127.0.0.1[/blue]. To enable public access, "
|
| 125 |
+
"e.g. in a container, use all the IP addresses "
|
| 126 |
+
"available with [blue]0.0.0.0[/blue]."
|
| 127 |
+
)
|
| 128 |
+
),
|
| 129 |
+
] = "127.0.0.1",
|
| 130 |
+
port: Annotated[
|
| 131 |
+
int,
|
| 132 |
+
typer.Option(help="The port to serve on."),
|
| 133 |
+
] = uvicorn_settings.port,
|
| 134 |
+
reload: Annotated[
|
| 135 |
+
bool,
|
| 136 |
+
typer.Option(
|
| 137 |
+
help=(
|
| 138 |
+
"Enable auto-reload of the server when (code) files change. "
|
| 139 |
+
"This is [bold]resource intensive[/bold], "
|
| 140 |
+
"use it only during development."
|
| 141 |
+
)
|
| 142 |
+
),
|
| 143 |
+
] = True,
|
| 144 |
+
root_path: Annotated[
|
| 145 |
+
str,
|
| 146 |
+
typer.Option(
|
| 147 |
+
help=(
|
| 148 |
+
"The root path is used to tell your app that it is being served "
|
| 149 |
+
"to the outside world with some [bold]path prefix[/bold] "
|
| 150 |
+
"set up in some termination proxy or similar."
|
| 151 |
+
)
|
| 152 |
+
),
|
| 153 |
+
] = uvicorn_settings.root_path,
|
| 154 |
+
proxy_headers: Annotated[
|
| 155 |
+
bool,
|
| 156 |
+
typer.Option(
|
| 157 |
+
help=(
|
| 158 |
+
"Enable/Disable X-Forwarded-Proto, X-Forwarded-For, "
|
| 159 |
+
"X-Forwarded-Port to populate remote address info."
|
| 160 |
+
)
|
| 161 |
+
),
|
| 162 |
+
] = uvicorn_settings.proxy_headers,
|
| 163 |
+
# docling options
|
| 164 |
+
enable_ui: Annotated[bool, typer.Option(help="Enable the development UI.")] = True,
|
| 165 |
+
) -> Any:
|
| 166 |
+
"""
|
| 167 |
+
Run a [bold]Docling Serve[/bold] app in [yellow]development[/yellow] mode. 🧪
|
| 168 |
+
|
| 169 |
+
This is equivalent to [bold]docling-serve run[/bold] but with [bold]reload[/bold]
|
| 170 |
+
enabled and listening on the [blue]127.0.0.1[/blue] address.
|
| 171 |
+
|
| 172 |
+
Options can be set also with the corresponding ENV variable, with the exception
|
| 173 |
+
of --enable-ui, --host and --reload.
|
| 174 |
+
"""
|
| 175 |
+
|
| 176 |
+
uvicorn_settings.host = host
|
| 177 |
+
uvicorn_settings.port = port
|
| 178 |
+
uvicorn_settings.reload = reload
|
| 179 |
+
uvicorn_settings.root_path = root_path
|
| 180 |
+
uvicorn_settings.proxy_headers = proxy_headers
|
| 181 |
+
|
| 182 |
+
docling_serve_settings.enable_ui = enable_ui
|
| 183 |
+
|
| 184 |
+
_run(
|
| 185 |
+
command="dev",
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
@app.command()
|
| 190 |
+
def run(
|
| 191 |
+
*,
|
| 192 |
+
host: Annotated[
|
| 193 |
+
str,
|
| 194 |
+
typer.Option(
|
| 195 |
+
help=(
|
| 196 |
+
"The host to serve on. For local development in localhost "
|
| 197 |
+
"use [blue]127.0.0.1[/blue]. To enable public access, "
|
| 198 |
+
"e.g. in a container, use all the IP addresses "
|
| 199 |
+
"available with [blue]0.0.0.0[/blue]."
|
| 200 |
+
)
|
| 201 |
+
),
|
| 202 |
+
] = uvicorn_settings.host,
|
| 203 |
+
port: Annotated[
|
| 204 |
+
int,
|
| 205 |
+
typer.Option(help="The port to serve on."),
|
| 206 |
+
] = uvicorn_settings.port,
|
| 207 |
+
reload: Annotated[
|
| 208 |
+
bool,
|
| 209 |
+
typer.Option(
|
| 210 |
+
help=(
|
| 211 |
+
"Enable auto-reload of the server when (code) files change. "
|
| 212 |
+
"This is [bold]resource intensive[/bold], "
|
| 213 |
+
"use it only during development."
|
| 214 |
+
)
|
| 215 |
+
),
|
| 216 |
+
] = uvicorn_settings.reload,
|
| 217 |
+
workers: Annotated[
|
| 218 |
+
Union[int, None],
|
| 219 |
+
typer.Option(
|
| 220 |
+
help=(
|
| 221 |
+
"Use multiple worker processes. "
|
| 222 |
+
"Mutually exclusive with the --reload flag."
|
| 223 |
+
)
|
| 224 |
+
),
|
| 225 |
+
] = uvicorn_settings.workers,
|
| 226 |
+
root_path: Annotated[
|
| 227 |
+
str,
|
| 228 |
+
typer.Option(
|
| 229 |
+
help=(
|
| 230 |
+
"The root path is used to tell your app that it is being served "
|
| 231 |
+
"to the outside world with some [bold]path prefix[/bold] "
|
| 232 |
+
"set up in some termination proxy or similar."
|
| 233 |
+
)
|
| 234 |
+
),
|
| 235 |
+
] = uvicorn_settings.root_path,
|
| 236 |
+
proxy_headers: Annotated[
|
| 237 |
+
bool,
|
| 238 |
+
typer.Option(
|
| 239 |
+
help=(
|
| 240 |
+
"Enable/Disable X-Forwarded-Proto, X-Forwarded-For, "
|
| 241 |
+
"X-Forwarded-Port to populate remote address info."
|
| 242 |
+
)
|
| 243 |
+
),
|
| 244 |
+
] = uvicorn_settings.proxy_headers,
|
| 245 |
+
# docling options
|
| 246 |
+
enable_ui: Annotated[
|
| 247 |
+
bool, typer.Option(help="Enable the development UI.")
|
| 248 |
+
] = docling_serve_settings.enable_ui,
|
| 249 |
+
) -> Any:
|
| 250 |
+
"""
|
| 251 |
+
Run a [bold]Docling Serve[/bold] app in [green]production[/green] mode. 🚀
|
| 252 |
+
|
| 253 |
+
This is equivalent to [bold]docling-serve dev[/bold] but with [bold]reload[/bold]
|
| 254 |
+
disabled and listening on the [blue]0.0.0.0[/blue] address.
|
| 255 |
+
|
| 256 |
+
Options can be set also with the corresponding ENV variable, e.g. UVICORN_PORT
|
| 257 |
+
or DOCLING_SERVE_ENABLE_UI.
|
| 258 |
+
"""
|
| 259 |
+
|
| 260 |
+
uvicorn_settings.host = host
|
| 261 |
+
uvicorn_settings.port = port
|
| 262 |
+
uvicorn_settings.reload = reload
|
| 263 |
+
uvicorn_settings.workers = workers
|
| 264 |
+
uvicorn_settings.root_path = root_path
|
| 265 |
+
uvicorn_settings.proxy_headers = proxy_headers
|
| 266 |
+
|
| 267 |
+
docling_serve_settings.enable_ui = enable_ui
|
| 268 |
+
|
| 269 |
+
_run(
|
| 270 |
+
command="run",
|
| 271 |
)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def main() -> None:
|
| 275 |
+
app()
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# Launch the CLI when calling python -m docling_serve
|
| 279 |
+
if __name__ == "__main__":
|
| 280 |
+
|
| 281 |
+
main()
|
docling_serve/app.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
import logging
|
| 2 |
-
import os
|
| 3 |
import tempfile
|
| 4 |
from contextlib import asynccontextmanager
|
| 5 |
from io import BytesIO
|
|
@@ -8,7 +7,6 @@ from typing import Annotated, Any, Dict, List, Optional, Union
|
|
| 8 |
|
| 9 |
from docling.datamodel.base_models import DocumentStream, InputFormat
|
| 10 |
from docling.document_converter import DocumentConverter
|
| 11 |
-
from dotenv import load_dotenv
|
| 12 |
from fastapi import BackgroundTasks, FastAPI, UploadFile
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
from fastapi.responses import RedirectResponse
|
|
@@ -22,17 +20,9 @@ from docling_serve.docling_conversion import (
|
|
| 22 |
converters,
|
| 23 |
get_pdf_pipeline_opts,
|
| 24 |
)
|
| 25 |
-
from docling_serve.helper_functions import FormDepends
|
| 26 |
from docling_serve.response_preparation import ConvertDocumentResponse, process_results
|
| 27 |
-
|
| 28 |
-
# Load local env vars if present
|
| 29 |
-
load_dotenv()
|
| 30 |
-
|
| 31 |
-
WITH_UI = _str_to_bool(os.getenv("WITH_UI", "False"))
|
| 32 |
-
if WITH_UI:
|
| 33 |
-
import gradio as gr
|
| 34 |
-
|
| 35 |
-
from docling_serve.gradio_ui import ui as gradio_ui
|
| 36 |
|
| 37 |
|
| 38 |
# Set up custom logging as we'll be intermixes with FastAPI/Uvicorn's logging
|
|
@@ -70,7 +60,6 @@ _log = logging.getLogger(__name__)
|
|
| 70 |
# Context manager to initialize and clean up the lifespan of the FastAPI app
|
| 71 |
@asynccontextmanager
|
| 72 |
async def lifespan(app: FastAPI):
|
| 73 |
-
# settings = Settings()
|
| 74 |
|
| 75 |
# Converter with default options
|
| 76 |
pdf_format_option, options_hash = get_pdf_pipeline_opts(ConvertDocumentsOptions())
|
|
@@ -86,143 +75,156 @@ async def lifespan(app: FastAPI):
|
|
| 86 |
yield
|
| 87 |
|
| 88 |
converters.clear()
|
| 89 |
-
if WITH_UI:
|
| 90 |
-
|
| 91 |
|
| 92 |
|
| 93 |
##################################
|
| 94 |
# App creation and configuration #
|
| 95 |
##################################
|
| 96 |
|
| 97 |
-
app = FastAPI(
|
| 98 |
-
title="Docling Serve",
|
| 99 |
-
lifespan=lifespan,
|
| 100 |
-
)
|
| 101 |
-
|
| 102 |
-
origins = ["*"]
|
| 103 |
-
methods = ["*"]
|
| 104 |
-
headers = ["*"]
|
| 105 |
-
|
| 106 |
-
app.add_middleware(
|
| 107 |
-
CORSMiddleware,
|
| 108 |
-
allow_origins=origins,
|
| 109 |
-
allow_credentials=True,
|
| 110 |
-
allow_methods=methods,
|
| 111 |
-
allow_headers=headers,
|
| 112 |
-
)
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
app = gr.mount_gradio_app(
|
| 119 |
-
app,
|
| 120 |
-
gradio_ui,
|
| 121 |
-
path="/ui",
|
| 122 |
-
allowed_paths=["./logo.png", tmp_output_dir],
|
| 123 |
-
root_path="/ui",
|
| 124 |
)
|
| 125 |
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
@app.get("/favicon.ico", include_in_schema=False)
|
| 134 |
-
async def favicon():
|
| 135 |
-
response = RedirectResponse(url="https://ds4sd.github.io/docling/assets/logo.png")
|
| 136 |
-
return response
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
# Status
|
| 140 |
-
class HealthCheckResponse(BaseModel):
|
| 141 |
-
status: str = "ok"
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
@app.get("/health")
|
| 145 |
-
def health() -> HealthCheckResponse:
|
| 146 |
-
return HealthCheckResponse()
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
# API readiness compatibility for OpenShift AI Workbench
|
| 150 |
-
@app.get("/api", include_in_schema=False)
|
| 151 |
-
def api_check() -> HealthCheckResponse:
|
| 152 |
-
return HealthCheckResponse()
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
# Convert a document from URL(s)
|
| 156 |
-
@app.post(
|
| 157 |
-
"/v1alpha/convert/source",
|
| 158 |
-
response_model=ConvertDocumentResponse,
|
| 159 |
-
responses={
|
| 160 |
-
200: {
|
| 161 |
-
"content": {"application/zip": {}},
|
| 162 |
-
# "description": "Return the JSON item or an image.",
|
| 163 |
-
}
|
| 164 |
-
},
|
| 165 |
-
)
|
| 166 |
-
def process_url(
|
| 167 |
-
background_tasks: BackgroundTasks, conversion_request: ConvertDocumentsRequest
|
| 168 |
-
):
|
| 169 |
-
sources: List[Union[str, DocumentStream]] = []
|
| 170 |
-
headers: Optional[Dict[str, Any]] = None
|
| 171 |
-
if isinstance(conversion_request, ConvertDocumentFileSourcesRequest):
|
| 172 |
-
for file_source in conversion_request.file_sources:
|
| 173 |
-
sources.append(file_source.to_document_stream())
|
| 174 |
-
else:
|
| 175 |
-
for http_source in conversion_request.http_sources:
|
| 176 |
-
sources.append(http_source.url)
|
| 177 |
-
if headers is None and http_source.headers:
|
| 178 |
-
headers = http_source.headers
|
| 179 |
-
|
| 180 |
-
# Note: results are only an iterator->lazy evaluation
|
| 181 |
-
results = convert_documents(
|
| 182 |
-
sources=sources, options=conversion_request.options, headers=headers
|
| 183 |
)
|
| 184 |
|
| 185 |
-
#
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
)
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
)
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
|
|
|
| 2 |
import tempfile
|
| 3 |
from contextlib import asynccontextmanager
|
| 4 |
from io import BytesIO
|
|
|
|
| 7 |
|
| 8 |
from docling.datamodel.base_models import DocumentStream, InputFormat
|
| 9 |
from docling.document_converter import DocumentConverter
|
|
|
|
| 10 |
from fastapi import BackgroundTasks, FastAPI, UploadFile
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
from fastapi.responses import RedirectResponse
|
|
|
|
| 20 |
converters,
|
| 21 |
get_pdf_pipeline_opts,
|
| 22 |
)
|
| 23 |
+
from docling_serve.helper_functions import FormDepends
|
| 24 |
from docling_serve.response_preparation import ConvertDocumentResponse, process_results
|
| 25 |
+
from docling_serve.settings import docling_serve_settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
# Set up custom logging as we'll be intermixes with FastAPI/Uvicorn's logging
|
|
|
|
| 60 |
# Context manager to initialize and clean up the lifespan of the FastAPI app
|
| 61 |
@asynccontextmanager
|
| 62 |
async def lifespan(app: FastAPI):
|
|
|
|
| 63 |
|
| 64 |
# Converter with default options
|
| 65 |
pdf_format_option, options_hash = get_pdf_pipeline_opts(ConvertDocumentsOptions())
|
|
|
|
| 75 |
yield
|
| 76 |
|
| 77 |
converters.clear()
|
| 78 |
+
# if WITH_UI:
|
| 79 |
+
# gradio_ui.close()
|
| 80 |
|
| 81 |
|
| 82 |
##################################
|
| 83 |
# App creation and configuration #
|
| 84 |
##################################
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
def create_app():
|
| 88 |
+
app = FastAPI(
|
| 89 |
+
title="Docling Serve",
|
| 90 |
+
lifespan=lifespan,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
)
|
| 92 |
|
| 93 |
+
origins = ["*"]
|
| 94 |
+
methods = ["*"]
|
| 95 |
+
headers = ["*"]
|
| 96 |
|
| 97 |
+
app.add_middleware(
|
| 98 |
+
CORSMiddleware,
|
| 99 |
+
allow_origins=origins,
|
| 100 |
+
allow_credentials=True,
|
| 101 |
+
allow_methods=methods,
|
| 102 |
+
allow_headers=headers,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
)
|
| 104 |
|
| 105 |
+
# Mount the Gradio app
|
| 106 |
+
if docling_serve_settings.enable_ui:
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
import gradio as gr
|
| 110 |
+
|
| 111 |
+
from docling_serve.gradio_ui import ui as gradio_ui
|
| 112 |
+
|
| 113 |
+
tmp_output_dir = Path(tempfile.mkdtemp())
|
| 114 |
+
gradio_ui.gradio_output_dir = tmp_output_dir
|
| 115 |
+
app = gr.mount_gradio_app(
|
| 116 |
+
app,
|
| 117 |
+
gradio_ui,
|
| 118 |
+
path="/ui",
|
| 119 |
+
allowed_paths=["./logo.png", tmp_output_dir],
|
| 120 |
+
root_path="/ui",
|
| 121 |
+
)
|
| 122 |
+
except ImportError:
|
| 123 |
+
_log.warning(
|
| 124 |
+
"Docling Serve enable_ui is activated, but gradio is not installed. "
|
| 125 |
+
"Install it with `pip install docling-serve[ui]` "
|
| 126 |
+
"or `pip install gradio`"
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
#############################
|
| 130 |
+
# API Endpoints definitions #
|
| 131 |
+
#############################
|
| 132 |
+
|
| 133 |
+
# Favicon
|
| 134 |
+
@app.get("/favicon.ico", include_in_schema=False)
|
| 135 |
+
async def favicon():
|
| 136 |
+
response = RedirectResponse(
|
| 137 |
+
url="https://ds4sd.github.io/docling/assets/logo.png"
|
| 138 |
+
)
|
| 139 |
+
return response
|
| 140 |
+
|
| 141 |
+
# Status
|
| 142 |
+
class HealthCheckResponse(BaseModel):
|
| 143 |
+
status: str = "ok"
|
| 144 |
+
|
| 145 |
+
@app.get("/health")
|
| 146 |
+
def health() -> HealthCheckResponse:
|
| 147 |
+
return HealthCheckResponse()
|
| 148 |
+
|
| 149 |
+
# API readiness compatibility for OpenShift AI Workbench
|
| 150 |
+
@app.get("/api", include_in_schema=False)
|
| 151 |
+
def api_check() -> HealthCheckResponse:
|
| 152 |
+
return HealthCheckResponse()
|
| 153 |
+
|
| 154 |
+
# Convert a document from URL(s)
|
| 155 |
+
@app.post(
|
| 156 |
+
"/v1alpha/convert/source",
|
| 157 |
+
response_model=ConvertDocumentResponse,
|
| 158 |
+
responses={
|
| 159 |
+
200: {
|
| 160 |
+
"content": {"application/zip": {}},
|
| 161 |
+
# "description": "Return the JSON item or an image.",
|
| 162 |
+
}
|
| 163 |
+
},
|
| 164 |
)
|
| 165 |
+
def process_url(
|
| 166 |
+
background_tasks: BackgroundTasks, conversion_request: ConvertDocumentsRequest
|
| 167 |
+
):
|
| 168 |
+
sources: List[Union[str, DocumentStream]] = []
|
| 169 |
+
headers: Optional[Dict[str, Any]] = None
|
| 170 |
+
if isinstance(conversion_request, ConvertDocumentFileSourcesRequest):
|
| 171 |
+
for file_source in conversion_request.file_sources:
|
| 172 |
+
sources.append(file_source.to_document_stream())
|
| 173 |
+
else:
|
| 174 |
+
for http_source in conversion_request.http_sources:
|
| 175 |
+
sources.append(http_source.url)
|
| 176 |
+
if headers is None and http_source.headers:
|
| 177 |
+
headers = http_source.headers
|
| 178 |
+
|
| 179 |
+
# Note: results are only an iterator->lazy evaluation
|
| 180 |
+
results = convert_documents(
|
| 181 |
+
sources=sources, options=conversion_request.options, headers=headers
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
# The real processing will happen here
|
| 185 |
+
response = process_results(
|
| 186 |
+
background_tasks=background_tasks,
|
| 187 |
+
conversion_options=conversion_request.options,
|
| 188 |
+
conv_results=results,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
return response
|
| 192 |
+
|
| 193 |
+
# Convert a document from file(s)
|
| 194 |
+
@app.post(
|
| 195 |
+
"/v1alpha/convert/file",
|
| 196 |
+
response_model=ConvertDocumentResponse,
|
| 197 |
+
responses={
|
| 198 |
+
200: {
|
| 199 |
+
"content": {"application/zip": {}},
|
| 200 |
+
}
|
| 201 |
+
},
|
| 202 |
)
|
| 203 |
+
async def process_file(
|
| 204 |
+
background_tasks: BackgroundTasks,
|
| 205 |
+
files: List[UploadFile],
|
| 206 |
+
options: Annotated[
|
| 207 |
+
ConvertDocumentsOptions, FormDepends(ConvertDocumentsOptions)
|
| 208 |
+
],
|
| 209 |
+
):
|
| 210 |
+
|
| 211 |
+
_log.info(f"Received {len(files)} files for processing.")
|
| 212 |
+
|
| 213 |
+
# Load the uploaded files to Docling DocumentStream
|
| 214 |
+
file_sources = []
|
| 215 |
+
for file in files:
|
| 216 |
+
buf = BytesIO(file.file.read())
|
| 217 |
+
name = file.filename if file.filename else "file.pdf"
|
| 218 |
+
file_sources.append(DocumentStream(name=name, stream=buf))
|
| 219 |
+
|
| 220 |
+
results = convert_documents(sources=file_sources, options=options)
|
| 221 |
+
|
| 222 |
+
response = process_results(
|
| 223 |
+
background_tasks=background_tasks,
|
| 224 |
+
conversion_options=options,
|
| 225 |
+
conv_results=results,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
return response
|
| 229 |
+
|
| 230 |
+
return app
|
docling_serve/settings.py
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
|
|
|
|
|
| 1 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 2 |
|
| 3 |
|
| 4 |
-
class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
|
|
|
|
|
| 1 |
+
from typing import Union
|
| 2 |
+
|
| 3 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 4 |
|
| 5 |
|
| 6 |
+
class UvicornSettings(BaseSettings):
|
| 7 |
+
model_config = SettingsConfigDict(
|
| 8 |
+
env_prefix="UVICORN_", env_file=".env", extra="allow"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
host: str = "0.0.0.0"
|
| 12 |
+
port: int = 5001
|
| 13 |
+
reload: bool = False
|
| 14 |
+
root_path: str = ""
|
| 15 |
+
proxy_headers: bool = True
|
| 16 |
+
workers: Union[int, None] = None
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class DoclingServeSettings(BaseSettings):
|
| 20 |
+
model_config = SettingsConfigDict(
|
| 21 |
+
env_prefix="DOCLING_SERVE_", env_file=".env", extra="allow"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
enable_ui: bool = False
|
| 25 |
+
|
| 26 |
|
| 27 |
+
uvicorn_settings = UvicornSettings()
|
| 28 |
+
docling_serve_settings = DoclingServeSettings()
|
pyproject.toml
CHANGED
|
@@ -36,6 +36,7 @@ dependencies = [
|
|
| 36 |
"pydantic~=2.10",
|
| 37 |
"pydantic-settings~=2.4",
|
| 38 |
"python-multipart>=0.0.14,<0.1.0",
|
|
|
|
| 39 |
"uvicorn[standard]>=0.29.0,<1.0.0",
|
| 40 |
]
|
| 41 |
|
|
@@ -74,6 +75,7 @@ dev = [
|
|
| 74 |
]
|
| 75 |
|
| 76 |
[tool.uv]
|
|
|
|
| 77 |
conflicts = [
|
| 78 |
[
|
| 79 |
{ extra = "cpu" },
|
|
@@ -104,6 +106,9 @@ explicit = true
|
|
| 104 |
[tool.setuptools.packages.find]
|
| 105 |
include = ["docling_serve"]
|
| 106 |
|
|
|
|
|
|
|
|
|
|
| 107 |
[project.urls]
|
| 108 |
Homepage = "https://github.com/DS4SD/docling-serve"
|
| 109 |
# Documentation = "https://ds4sd.github.io/docling"
|
|
@@ -118,6 +123,7 @@ include = '\.pyi?$'
|
|
| 118 |
|
| 119 |
[tool.isort]
|
| 120 |
profile = "black"
|
|
|
|
| 121 |
line_length = 88
|
| 122 |
py_version=311
|
| 123 |
|
|
|
|
| 36 |
"pydantic~=2.10",
|
| 37 |
"pydantic-settings~=2.4",
|
| 38 |
"python-multipart>=0.0.14,<0.1.0",
|
| 39 |
+
"typer~=0.12",
|
| 40 |
"uvicorn[standard]>=0.29.0,<1.0.0",
|
| 41 |
]
|
| 42 |
|
|
|
|
| 75 |
]
|
| 76 |
|
| 77 |
[tool.uv]
|
| 78 |
+
package = true
|
| 79 |
conflicts = [
|
| 80 |
[
|
| 81 |
{ extra = "cpu" },
|
|
|
|
| 106 |
[tool.setuptools.packages.find]
|
| 107 |
include = ["docling_serve"]
|
| 108 |
|
| 109 |
+
[project.scripts]
|
| 110 |
+
docling-serve = "docling_serve.__main__:main"
|
| 111 |
+
|
| 112 |
[project.urls]
|
| 113 |
Homepage = "https://github.com/DS4SD/docling-serve"
|
| 114 |
# Documentation = "https://ds4sd.github.io/docling"
|
|
|
|
| 123 |
|
| 124 |
[tool.isort]
|
| 125 |
profile = "black"
|
| 126 |
+
known_third_party = ["docling", "docling_core"]
|
| 127 |
line_length = 88
|
| 128 |
py_version=311
|
| 129 |
|
start_server.sh
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
set -Eeuo pipefail
|
| 3 |
-
|
| 4 |
-
# Network settings
|
| 5 |
-
export PORT="${PORT:-5001}"
|
| 6 |
-
export HOST="${HOST:-"0.0.0.0"}"
|
| 7 |
-
|
| 8 |
-
# Performance settings
|
| 9 |
-
UVICORN_WORKERS="${UVICORN_WORKERS:-1}"
|
| 10 |
-
|
| 11 |
-
# Development settings
|
| 12 |
-
export WITH_UI="${WITH_UI:-"true"}"
|
| 13 |
-
export RELOAD=${RELOAD:-"false"}
|
| 14 |
-
|
| 15 |
-
# --------------------------------------
|
| 16 |
-
# Process env settings
|
| 17 |
-
|
| 18 |
-
EXTRA_ARGS=""
|
| 19 |
-
if [ "$RELOAD" == "true" ]; then
|
| 20 |
-
EXTRA_ARGS="$EXTRA_ARGS --reload"
|
| 21 |
-
fi
|
| 22 |
-
|
| 23 |
-
# Launch
|
| 24 |
-
exec uv run uvicorn \
|
| 25 |
-
docling_serve.app:app \
|
| 26 |
-
--host=${HOST} \
|
| 27 |
-
--port=${PORT} \
|
| 28 |
-
--timeout-keep-alive=600 \
|
| 29 |
-
${EXTRA_ARGS} \
|
| 30 |
-
--workers=${UVICORN_WORKERS}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uv.lock
CHANGED
|
@@ -583,7 +583,7 @@ wheels = [
|
|
| 583 |
[[package]]
|
| 584 |
name = "docling-serve"
|
| 585 |
version = "0.2.0"
|
| 586 |
-
source = {
|
| 587 |
dependencies = [
|
| 588 |
{ name = "docling" },
|
| 589 |
{ name = "fastapi", extra = ["standard"] },
|
|
@@ -591,6 +591,7 @@ dependencies = [
|
|
| 591 |
{ name = "pydantic" },
|
| 592 |
{ name = "pydantic-settings" },
|
| 593 |
{ name = "python-multipart" },
|
|
|
|
| 594 |
{ name = "uvicorn", extra = ["standard"] },
|
| 595 |
]
|
| 596 |
|
|
@@ -646,6 +647,7 @@ requires-dist = [
|
|
| 646 |
{ name = "torch", marker = "extra == 'cu124'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "docling-serve", extra = "cu124" } },
|
| 647 |
{ name = "torchvision", marker = "extra == 'cpu'", specifier = ">=0.21.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "docling-serve", extra = "cpu" } },
|
| 648 |
{ name = "torchvision", marker = "extra == 'cu124'", specifier = ">=0.21.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "docling-serve", extra = "cu124" } },
|
|
|
|
| 649 |
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.29.0,<1.0.0" },
|
| 650 |
]
|
| 651 |
provides-extras = ["ui", "tesserocr", "rapidocr", "cpu", "cu124"]
|
|
|
|
| 583 |
[[package]]
|
| 584 |
name = "docling-serve"
|
| 585 |
version = "0.2.0"
|
| 586 |
+
source = { editable = "." }
|
| 587 |
dependencies = [
|
| 588 |
{ name = "docling" },
|
| 589 |
{ name = "fastapi", extra = ["standard"] },
|
|
|
|
| 591 |
{ name = "pydantic" },
|
| 592 |
{ name = "pydantic-settings" },
|
| 593 |
{ name = "python-multipart" },
|
| 594 |
+
{ name = "typer" },
|
| 595 |
{ name = "uvicorn", extra = ["standard"] },
|
| 596 |
]
|
| 597 |
|
|
|
|
| 647 |
{ name = "torch", marker = "extra == 'cu124'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "docling-serve", extra = "cu124" } },
|
| 648 |
{ name = "torchvision", marker = "extra == 'cpu'", specifier = ">=0.21.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "docling-serve", extra = "cpu" } },
|
| 649 |
{ name = "torchvision", marker = "extra == 'cu124'", specifier = ">=0.21.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "docling-serve", extra = "cu124" } },
|
| 650 |
+
{ name = "typer", specifier = "~=0.12" },
|
| 651 |
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.29.0,<1.0.0" },
|
| 652 |
]
|
| 653 |
provides-extras = ["ui", "tesserocr", "rapidocr", "cpu", "cu124"]
|