From 0d876617280f7cc7ead6ad864182024cf5a681f3 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:26:00 +0100 Subject: [PATCH 01/15] Move src/ocr/docling to packages/ocr/docling --- {src => packages}/ocr/docling/Dockerfile | 0 {src => packages}/ocr/docling/README.md | 0 {src => packages}/ocr/docling/__init__.py | 0 {src => packages}/ocr/docling/api.py | 0 {src => packages}/ocr/docling/docker-compose.yml | 0 {src => packages}/ocr/docling/main.py | 0 {src => packages}/ocr/docling/requirements.txt | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {src => packages}/ocr/docling/Dockerfile (100%) rename {src => packages}/ocr/docling/README.md (100%) rename {src => packages}/ocr/docling/__init__.py (100%) rename {src => packages}/ocr/docling/api.py (100%) rename {src => packages}/ocr/docling/docker-compose.yml (100%) rename {src => packages}/ocr/docling/main.py (100%) rename {src => packages}/ocr/docling/requirements.txt (100%) diff --git a/src/ocr/docling/Dockerfile b/packages/ocr/docling/Dockerfile similarity index 100% rename from src/ocr/docling/Dockerfile rename to packages/ocr/docling/Dockerfile diff --git a/src/ocr/docling/README.md b/packages/ocr/docling/README.md similarity index 100% rename from src/ocr/docling/README.md rename to packages/ocr/docling/README.md diff --git a/src/ocr/docling/__init__.py b/packages/ocr/docling/__init__.py similarity index 100% rename from src/ocr/docling/__init__.py rename to packages/ocr/docling/__init__.py diff --git a/src/ocr/docling/api.py b/packages/ocr/docling/api.py similarity index 100% rename from src/ocr/docling/api.py rename to packages/ocr/docling/api.py diff --git a/src/ocr/docling/docker-compose.yml b/packages/ocr/docling/docker-compose.yml similarity index 100% rename from src/ocr/docling/docker-compose.yml rename to packages/ocr/docling/docker-compose.yml diff --git a/src/ocr/docling/main.py b/packages/ocr/docling/main.py similarity index 100% rename from src/ocr/docling/main.py rename to packages/ocr/docling/main.py diff --git a/src/ocr/docling/requirements.txt b/packages/ocr/docling/requirements.txt similarity index 100% rename from src/ocr/docling/requirements.txt rename to packages/ocr/docling/requirements.txt From 8a071b122238eefe2565c60cfc8bdd36903e3528 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:28:13 +0100 Subject: [PATCH 02/15] Make the docling package a workspace member --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 80d2b02..9fe77eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ optional-dependencies = {dev = [ "ruff", "tox", "twine", +], docling = [ + "pyonb-docling", ], docs = [ "mkdocs", "mkdocs-include-markdown-plugin", @@ -153,6 +155,7 @@ gh.python."3.12" = ["py312"] gh.python."3.13" = ["py313"] [tool.uv.sources] +pyonb-docling = {workspace = true} pyonb-kreuzberg = {workspace = true} [tool.uv.workspace] From bf39a8b3fe1411503b216c8563cd5a814a18b53f Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:33:24 +0100 Subject: [PATCH 03/15] Make packages/ocr/docling into an installable package --- packages/ocr/docling/pyproject.toml | 16 ++++++++++++++++ .../docling/{ => src/pyonb_docling}/__init__.py | 0 .../ocr/docling/{ => src/pyonb_docling}/api.py | 0 .../ocr/docling/{ => src/pyonb_docling}/main.py | 0 4 files changed, 16 insertions(+) create mode 100644 packages/ocr/docling/pyproject.toml rename packages/ocr/docling/{ => src/pyonb_docling}/__init__.py (100%) rename packages/ocr/docling/{ => src/pyonb_docling}/api.py (100%) rename packages/ocr/docling/{ => src/pyonb_docling}/main.py (100%) diff --git a/packages/ocr/docling/pyproject.toml b/packages/ocr/docling/pyproject.toml new file mode 100644 index 0000000..a99aaaf --- /dev/null +++ b/packages/ocr/docling/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +dependencies = [ + "docling", + "fastapi[standard]", + "python-dotenv", + "uvicorn", +] +description = "pyonb wrapper around docling" +name = "pyonb-docling" +readme = "README.md" +requires-python = ">=3.11" +version = "0.1.0" diff --git a/packages/ocr/docling/__init__.py b/packages/ocr/docling/src/pyonb_docling/__init__.py similarity index 100% rename from packages/ocr/docling/__init__.py rename to packages/ocr/docling/src/pyonb_docling/__init__.py diff --git a/packages/ocr/docling/api.py b/packages/ocr/docling/src/pyonb_docling/api.py similarity index 100% rename from packages/ocr/docling/api.py rename to packages/ocr/docling/src/pyonb_docling/api.py diff --git a/packages/ocr/docling/main.py b/packages/ocr/docling/src/pyonb_docling/main.py similarity index 100% rename from packages/ocr/docling/main.py rename to packages/ocr/docling/src/pyonb_docling/main.py From 51bf1f7a5a4df961759090ca71a6f65ddd177d7c Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:35:55 +0100 Subject: [PATCH 04/15] Update docling Dockerfile to use uv base image and cache the dependencies --- packages/ocr/docling/Dockerfile | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/ocr/docling/Dockerfile b/packages/ocr/docling/Dockerfile index 13bd4a9..1dab6d3 100644 --- a/packages/ocr/docling/Dockerfile +++ b/packages/ocr/docling/Dockerfile @@ -1,23 +1,18 @@ -FROM python:3.12-slim-bookworm +FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS app -WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates +SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-x", "-c"] -# Add uv installer, run it, then remove it -ADD https://astral.sh/uv/install.sh /uv-installer.sh -RUN sh /uv-installer.sh && rm /uv-installer.sh - -# Ensure installed uv binary is on the `PATH` -ENV PATH="/root/.local/bin/:$PATH" +WORKDIR /app +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 -# Tell uv to use system Python environment by default -ENV UV_SYSTEM_PYTHON=1 +COPY ./pyproject.toml ./README.md . +COPY ./src src/ -COPY requirements.txt . -RUN uv pip install --no-cache-dir -r requirements.txt +RUN uv venv +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked uv sync --no-editable --no-dev --compile-bytecode -COPY main.py api.py ./ +# make uvicorn etc available +ENV PATH="/app/.venv/bin:$PATH" -CMD uvicorn api:app --host 0.0.0.0 --port $DOCLING_API_PORT -# ENTRYPOINT ["python", "main.py"] +CMD uvicorn pyonb_docling.api:app --host 0.0.0.0 --port "$DOCLING_API_PORT" --workers 4 --reload --use-colors From 2573c07d35082eb121ce7c9c91be22c564a4a6ef Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:37:30 +0100 Subject: [PATCH 05/15] Import convert_pdf_to_markdown from pyonb_docling.main rather than relative import from .main --- packages/ocr/docling/src/pyonb_docling/api.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/ocr/docling/src/pyonb_docling/api.py b/packages/ocr/docling/src/pyonb_docling/api.py index 3b5952c..9af8f43 100644 --- a/packages/ocr/docling/src/pyonb_docling/api.py +++ b/packages/ocr/docling/src/pyonb_docling/api.py @@ -8,6 +8,8 @@ from fastapi import FastAPI, File, HTTPException, UploadFile, status from fastapi.responses import JSONResponse, RedirectResponse +from pyonb_docling.main import convert_pdf_to_markdown + logging.basicConfig( filename="docling." + datetime.datetime.now(tz=datetime.UTC).strftime("%Y%m%d") + ".log", format="%(asctime)s %(message)s", @@ -18,18 +20,6 @@ logger = logging.getLogger() logger.setLevel(logging.DEBUG) -# TODO(tom): improve imports - below try statements horrible -try: - # local - from .main import convert_pdf_to_markdown -except Exception: - logger.exception("Detected inside Docker container.") - # Docker container - try: - from main import convert_pdf_to_markdown # type: ignore # noqa: PGH003 - except Exception: - logger.exception("Docling imports not possible.") - app = FastAPI(swagger_ui_parameters={"tryItOutEnabled": True}) From c2992a445184d56f6b45f4a6fd7127cfa9371502 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:41:08 +0100 Subject: [PATCH 06/15] Use for docling volumes rather than and --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b9fec37..b103697 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,14 +152,14 @@ services: DOCLING_API_PORT: ${DOCLING_API_PORT} environment: <<: [*proxy-common, *common-env] - CONTAINER_DATA_FOLDER: /data + DATA_FOLDER: /data DOCLING_API_PORT: ${DOCLING_API_PORT} env_file: - ./.env ports: - "${DOCLING_API_PORT}:${DOCLING_API_PORT}" volumes: - - ${HOST_DATA_FOLDER}:${CONTAINER_DATA_FOLDER:-/data} + - ${PWD}/${DATA_FOLDER}:/data networks: - pyonb_ocr_api healthcheck: From 7e158885f22e75606519ee83b2d76a9d2fafb5ae Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 09:57:53 +0100 Subject: [PATCH 07/15] Use aiohttp for async calls to the docling api --- src/api/app/routers/docling.py | 89 ++++++++++++++++------------------ 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/src/api/app/routers/docling.py b/src/api/app/routers/docling.py index 9f1a9fe..8d5e3b3 100644 --- a/src/api/app/routers/docling.py +++ b/src/api/app/routers/docling.py @@ -1,15 +1,16 @@ """Routers for Docling OCR.""" import datetime -import json import logging import os +import time from pathlib import Path from typing import Annotated, Any +import aiohttp import requests from dotenv import load_dotenv -from fastapi import APIRouter, File, UploadFile, status +from fastapi import APIRouter, File, HTTPException, UploadFile, status from fastapi.responses import JSONResponse load_dotenv() @@ -23,44 +24,27 @@ logger = logging.getLogger() logger.setLevel(logging.DEBUG) -# Detect if in Docker container -is_docker = Path("/.dockerenv").exists() - router = APIRouter() -def check_data_folder() -> Path | str: - """Check if Docker or local deployment and adjust DATA_FOLDER accordingly.""" - # Detect if in Docker container - is_docker = Path("/.dockerenv").exists() - - logger.info("HOST_DATA_FOLDER: %s", str(os.environ.get("HOST_DATA_FOLDER"))) - - if is_docker: - logger.info("Detected running inside Docker container.") - DATA_FOLDER = str(os.environ.get("CONTAINER_DATA_FOLDER")) - elif not is_docker: - logger.info("Detected running on host machine.") - DATA_FOLDER = str(os.environ.get("HOST_DATA_FOLDER")) - - if Path(DATA_FOLDER).exists(): - logger.info("DATA_FOLDER: %s", DATA_FOLDER) - else: - e = f"{Path(DATA_FOLDER)!s} not found or does not exist." - logger.exception(NotADirectoryError(e)) - raise NotADirectoryError(e) - - return DATA_FOLDER - - @router.get("/docling/health") async def health() -> dict[str, Any]: """Test aliveness endpoint for Docling.""" - logger.info("[GET] /docling/health") - url = f"http://docling:{DOCLING_API_PORT}/health" - response = requests.get(url, timeout=5) # noqa: ASYNC210 + logger.info("[GET] /kreuzberg/health") + url = f"http://kreuzberg:{DOCLING_API_PORT}/health" + + try: + async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60 * 60)) as session: # noqa: SIM117 + async with session.get(url) as response: + response.raise_for_status() + except aiohttp.ClientError: + logger.exception("Failed to connect to kreuzberg service") + raise - return json.loads(response.content.decode("utf-8")) + return JSONResponse( + status_code=status.HTTP_200_OK, + content={"service": "kreuzberg", "status": "healthy"}, + ) @router.post("/docling/inference_single", status_code=status.HTTP_200_OK) @@ -73,26 +57,34 @@ async def inference_single_doc(file_upload: Annotated[UploadFile, File()] = None logger.info("[POST] /docling/inference_single_doc") url = f"http://docling:{DOCLING_API_PORT}/inference" - t1 = datetime.datetime.now(datetime.UTC) - - file_bytes = await file_upload.read() - file = {"file": (file_upload.filename, file_bytes, file_upload.content_type)} + data = aiohttp.FormData() + data.add_field( + "file", # field name expected by Kreuzberg's /extract API + file_upload.file, + filename=file_upload.filename, + content_type=file_upload.content_type, + ) headers = {"accept": "application/json"} logger.info("post request - url: %s", url) - logger.info("post request - file: %s", file) + logger.info("post request - data: %s", data) logger.info("post request - headers: %s", headers) - # nb: timeout currently arbitrarily one hour - response = requests.post(url=url, files=file, headers=headers, timeout=60 * 60) # noqa: ASYNC210 - - t2 = datetime.datetime.now(datetime.UTC) - td = t2 - t1 + t1 = time.perf_counter() + try: + async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60 * 60)) as session: # noqa: SIM117 + async with session.post(url, data=data, headers=headers) as response: + response.raise_for_status() + ocr_result = await response.json() + except aiohttp.ClientError: + logger.exception("Request Exception") + raise + t2 = time.perf_counter() response_json = { "filename": str(file_upload.filename), - "duration_in_second": td.total_seconds(), - "ocr-result": response.json(), + "duration_in_second": t2 - t1, + "ocr-result": ocr_result, } return JSONResponse(status_code=status.HTTP_200_OK, content=response_json) @@ -106,7 +98,12 @@ async def inference_folder() -> JSONResponse: # URL of docling service # TODO(tom): configure with env var (e.g. so can set 127.0.0.1 if running on host) - DATA_FOLDER = check_data_folder() + DATA_FOLDER = os.environ.get("DATA_FOLDER") + if DATA_FOLDER is None: + raise HTTPException( + status_code=500, + detail="DATA_FOLDER environment variable not defined.", + ) filenames = [str(f.name) for f in Path(DATA_FOLDER).iterdir() if f.suffix == ".pdf"] logger.info("Filenames in %s: %s", DATA_FOLDER, filenames) From 3ba8901e1841b020c2af688e50ff8b1ac0b45a43 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 10:03:12 +0100 Subject: [PATCH 08/15] Update path to docling package --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index b103697..c15362d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -145,7 +145,7 @@ services: docling: profiles: [docling] build: - context: src/ocr/docling + context: packages/ocr/docling dockerfile: Dockerfile args: <<: *build-args-common From 4e87b5e9da45ecfe1b67484eddf27ba60266227a Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 18 Aug 2025 12:15:21 +0100 Subject: [PATCH 09/15] Use for ocr-forwarding-api volumes rather than and --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c15362d..6b4db79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -219,7 +219,7 @@ services: OCR_FORWARDING_API_PORT: ${OCR_FORWARDING_API_PORT} environment: <<: [*proxy-common, *common-env] - CONTAINER_DATA_FOLDER: /data + DATA_FOLDER: /data OCR_FORWARDING_API_PORT: ${OCR_FORWARDING_API_PORT} env_file: - ./.env @@ -227,7 +227,7 @@ services: - "${OCR_FORWARDING_API_PORT}:${OCR_FORWARDING_API_PORT}" volumes: - ./src/api/app:/app - - ${HOST_DATA_FOLDER}:${CONTAINER_DATA_FOLDER:-/data} + - ${PWD}/${DATA_FOLDER}:/data networks: - pyonb_ocr_api healthcheck: From a15209fd2f9b8da1fd20fe97775e6665874159ad Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 11:29:20 +0100 Subject: [PATCH 10/15] Update instructions for running inference with docling --- packages/ocr/docling/Dockerfile | 7 ++++--- packages/ocr/docling/README.md | 23 +++++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/ocr/docling/Dockerfile b/packages/ocr/docling/Dockerfile index 1dab6d3..fafdff4 100644 --- a/packages/ocr/docling/Dockerfile +++ b/packages/ocr/docling/Dockerfile @@ -6,13 +6,14 @@ WORKDIR /app ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 -COPY ./pyproject.toml ./README.md . +COPY ./pyproject.toml . +COPY ./README . COPY ./src src/ RUN uv venv -RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked uv sync --no-editable --no-dev --compile-bytecode +RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked uv sync --no-editable --no-dev # make uvicorn etc available ENV PATH="/app/.venv/bin:$PATH" -CMD uvicorn pyonb_docling.api:app --host 0.0.0.0 --port "$DOCLING_API_PORT" --workers 4 --reload --use-colors +CMD uvicorn pyonb_docling.api:app --host 0.0.0.0 --port "$DOCLING_API_PORT" --workers 4 --use-colors diff --git a/packages/ocr/docling/README.md b/packages/ocr/docling/README.md index 2506a2e..032e4a1 100644 --- a/packages/ocr/docling/README.md +++ b/packages/ocr/docling/README.md @@ -2,18 +2,29 @@ ## Python -NB: below MUST have absolute paths +First install `pyonb_docling`. From the top-level `pyonb` directory: ```shell -cd pyonb/ocr/docling -python main.py ../tests/ms-note-one-page.pdf ../tests/output.txt +uv sync --extra docling +``` + +Then, to convert a PDF to markdown: + +```python +import pyonb_docling + +result = pyonb_docling.convert_pdf_to_markdown( + file_path="path/to/data/input.pdf", +) ``` ## Docker Compose -NB: Set DATA_FOLDER in .env, e.g: DATA_FOLDER=path/to/folder/containing/PDF +From the `pyonb/packages/ocr/docling` directory: ```shell -cd pyonb/ocr/docling -docker compose run docling data/ms-note-one-page.pdf data/output.txt +docker compose run docling data/input.pdf data/output.md ``` + +Note, you will need to set `DATA_FOLDER` in a `.env` file, +e.g: `DATA_FOLDER=path/to/data/input.pdf` From c9c448cc68dd24e4317d4f0bb7a54dba5f8212b5 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 11:29:56 +0100 Subject: [PATCH 11/15] Remove requirements.txt for docling package --- packages/ocr/docling/requirements.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 packages/ocr/docling/requirements.txt diff --git a/packages/ocr/docling/requirements.txt b/packages/ocr/docling/requirements.txt deleted file mode 100644 index 285696c..0000000 --- a/packages/ocr/docling/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi[standard] -requests -python-dotenv -docling From 2f54ae82ff95ed37f7f65a583d0ca4121c4c4fa4 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 11:39:13 +0100 Subject: [PATCH 12/15] Update docs on installing and running pyonb --- README.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 919e3e3..aef5152 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,13 @@ END COMMENT OUT--> -**pyonb** is two things: +`pyonb` is a Python library and suite of APIs that wrap open-source Optical Character Recognition (OCR) tools. It it designed for local deployment and can convert PDFs to structured text using the several +OCR tools: -- a Python SDK for document extraction via the Hyland OnBase REST API (_work in progress_) -- a suite of APIs wrapped around open-source Optical Character Recognition (OCR) tools, designed for local deployment, for converting PDFs to structured text including: - - [Marker](https://github.com/VikParuchuri/marker) - - [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) - - [Docling](https://github.com/docling-project/docling) - - [Kreuzberg](https://github.com/Goldziher/kreuzberg) +- [Marker](https://github.com/VikParuchuri/marker) +- [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) +- [Docling](https://github.com/docling-project/docling) +- [Kreuzberg](https://github.com/Goldziher/kreuzberg) ## Getting Started @@ -30,17 +29,27 @@ END COMMENT OUT--> ### Installation & Usage -1. Rename `.env.sample` to `.env`. +1. Clone `pyonb` -2. Edit `.env` with the correct `HOST_DATA_FOLDER` location, e.g.: +```sh +git clone git@github.com:SAFEHR-data/pyonb.git +cd pyonb +``` + +2. Rename `.env.sample` to `.env`. ```sh -HOST_DATA_FOLDER="/absolute/path/to/documents/folder" +mv .env.sample .env +``` -# e.g. for unit tests on GAE: -# HOST_DATA_FOLDER="/gae/pyonb/tests/data/single_synthetic_doc" +3. Edit `.env` with the correct `DATA_FOLDER` location, e.g.: + +```sh +DATA_FOLDER="path/to/documents/folder" ``` +where the path is relative to the `docker-compose.yml` file in the top-level `pyonb` directory. + 4. Set OCR service ports, e.g.: ```sh @@ -48,6 +57,7 @@ OCR_FORWARDING_API_PORT=8110 MARKER_API_PORT=8112 PADDLEOCR_API_PORT=8114 DOCLING_API_PORT=8115 +KREUZBERG_API_PORT=8116 ``` > [!IMPORTANT] @@ -60,7 +70,7 @@ DOCLING_API_PORT=8115 > HTTP_PROXY= > ``` -5. Start the OCR API Server (e.g. using marker and docling): +5. Start the OCR API Server (e.g. using `marker` and `docling`): ```sh docker compose --profile marker --profile docling up -d From db228edf929cf491e80c9d0c559a6ce6dd7db23c Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 11:51:43 +0100 Subject: [PATCH 13/15] Copy README.md into docling image --- packages/ocr/docling/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ocr/docling/Dockerfile b/packages/ocr/docling/Dockerfile index fafdff4..0dd97e7 100644 --- a/packages/ocr/docling/Dockerfile +++ b/packages/ocr/docling/Dockerfile @@ -7,7 +7,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 COPY ./pyproject.toml . -COPY ./README . +COPY ./README.md . COPY ./src src/ RUN uv venv From 02263ff284def5329bbe1eee23e69e4583e4ca08 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 12:24:15 +0100 Subject: [PATCH 14/15] Use correct service name in docling router --- src/api/app/routers/docling.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/api/app/routers/docling.py b/src/api/app/routers/docling.py index 8d5e3b3..d47c2b7 100644 --- a/src/api/app/routers/docling.py +++ b/src/api/app/routers/docling.py @@ -1,6 +1,5 @@ """Routers for Docling OCR.""" -import datetime import logging import os import time @@ -30,20 +29,20 @@ @router.get("/docling/health") async def health() -> dict[str, Any]: """Test aliveness endpoint for Docling.""" - logger.info("[GET] /kreuzberg/health") - url = f"http://kreuzberg:{DOCLING_API_PORT}/health" + logger.info("[GET] /docling/health") + url = f"http://docling:{DOCLING_API_PORT}/health" try: async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60 * 60)) as session: # noqa: SIM117 async with session.get(url) as response: response.raise_for_status() except aiohttp.ClientError: - logger.exception("Failed to connect to kreuzberg service") + logger.exception("Failed to connect to docling service") raise return JSONResponse( status_code=status.HTTP_200_OK, - content={"service": "kreuzberg", "status": "healthy"}, + content={"service": "docling", "status": "healthy"}, ) @@ -109,7 +108,7 @@ async def inference_folder() -> JSONResponse: logger.info("Filenames in %s: %s", DATA_FOLDER, filenames) ocr_result = [] - t1 = datetime.datetime.now(datetime.UTC) + t1 = time.perf_counter() for filename in filenames: abs_file_path = Path(DATA_FOLDER) / Path(filename) logger.info("abs_file_path: %s", abs_file_path) @@ -120,7 +119,7 @@ async def inference_folder() -> JSONResponse: with Path.open(abs_file_path, "rb") as pdf_file: # Send the file via POST request - s1 = datetime.datetime.now(datetime.UTC) + s1 = time.perf_counter() files = {"file": (str(filename), pdf_file, "application/pdf")} headers = {"accept": "application/json"} @@ -132,21 +131,19 @@ async def inference_folder() -> JSONResponse: # nb: timeout currently arbitrarily one hour response = requests.post(url, files=files, headers=headers, timeout=60 * 60) # noqa: ASYNC210 - s2 = datetime.datetime.now(datetime.UTC) - td = s2 - s1 + s2 = time.perf_counter() response_entry = { "filename": filename, - "duration_in_second": td.total_seconds(), + "duration_in_second": s2 - s1, "ocr-result": response.text, } logger.info("Filename: %s", filename) logger.info("response_entry: %s", response_entry) ocr_result.append(response_entry) - t2 = datetime.datetime.now(datetime.UTC) - total_duration = t2 - t1 + t2 = time.perf_counter() response_json = { - "total_duration_in_second": total_duration.total_seconds(), + "total_duration_in_second": t2 - t1, "result": ocr_result, } From 68e0dafb84962cc0c9f8709b42bd79490a6dc8ed Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 7 Oct 2025 12:26:01 +0100 Subject: [PATCH 15/15] Remove mention of kreuzberg from docling router --- src/api/app/routers/docling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/app/routers/docling.py b/src/api/app/routers/docling.py index d47c2b7..8b47071 100644 --- a/src/api/app/routers/docling.py +++ b/src/api/app/routers/docling.py @@ -58,7 +58,7 @@ async def inference_single_doc(file_upload: Annotated[UploadFile, File()] = None data = aiohttp.FormData() data.add_field( - "file", # field name expected by Kreuzberg's /extract API + "file", file_upload.file, filename=file_upload.filename, content_type=file_upload.content_type,