diff --git a/AGENTS.md b/AGENTS.md index 082e0209..b3411d9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,8 @@ Use these commands once the dev prerequisites above are satisfied. ```bash source .venv/bin/activate # macOS/Linux uv pip install -r application/requirements.txt # or: pip install -r application/requirements.txt +# Optional docling parser engine (OCR / read_document structured output): +# uv pip install -r application/requirements-docling.txt ``` Run the API. For local dev, prefer the ASGI entrypoint under uvicorn — it diff --git a/application/Dockerfile b/application/Dockerfile index 18eb943c..e91839b0 100644 --- a/application/Dockerfile +++ b/application/Dockerfile @@ -28,8 +28,8 @@ RUN wget -q -O - https://sh.rustup.rs | sh -s -- -y # Clean up to reduce container size RUN apt-get remove --purge -y wget unzip && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* -# Copy requirements.txt -COPY requirements.txt . +# Copy requirements manifests +COPY requirements.txt requirements-docling.txt ./ # Setup Python virtual environment RUN python3.12 -m venv /venv @@ -42,6 +42,15 @@ RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir tiktoken && \ pip install --no-cache-dir -r requirements.txt +# Optional docling parser engine (DOC_PARSER_ENGINE=docling, OCR for scanned +# PDFs/images, read_document's structured output) — OFF by default: it pulls +# the layout/OCR model stack and adds gigabytes to the image. anydoc (in +# requirements.txt) is the default parser and needs none of it. +ARG INSTALL_DOCLING=false +RUN if [ "$INSTALL_DOCLING" = "true" ]; then \ + pip install --no-cache-dir -r requirements-docling.txt; \ + fi + # Final Stage FROM ubuntu:24.04 as final diff --git a/application/requirements-docling.txt b/application/requirements-docling.txt new file mode 100644 index 00000000..07dd1074 --- /dev/null +++ b/application/requirements-docling.txt @@ -0,0 +1,14 @@ +# Optional docling parser engine. +# +# Needed only for: DOC_PARSER_ENGINE=docling, OCR of scanned PDFs and images +# (DOCLING_OCR_ENABLED / DOCLING_OCR_ATTACHMENTS_ENABLED), .adoc/.vtt/.xml +# source parsing, and the read_document tool's `structured` output. The +# default anydoc engine needs none of this. +# +# Install ON TOP of requirements.txt (torch/transformers stay pinned there, +# shared with sentence-transformers): +# pip install -r requirements.txt -r requirements-docling.txt +# Docker: build with --build-arg INSTALL_DOCLING=true +docling==2.119.0 +rapidocr==3.9.2 +onnxruntime==1.28.0 diff --git a/application/requirements.txt b/application/requirements.txt index 98c6b536..cb359123 100644 --- a/application/requirements.txt +++ b/application/requirements.txt @@ -12,12 +12,10 @@ cryptography==50.0.0 dataclasses-json==0.6.7 daytona==0.205.1 defusedxml==0.7.1 -docling==2.119.0 -# Default document converter (DOC_PARSER_ENGINE=anydoc); docling above is -# the optional heavy engine for OCR/structured output. +# Default document converter (DOC_PARSER_ENGINE=anydoc). The docling engine +# (OCR, layout/table models, read_document's structured output) is an +# optional extra: pip install -r requirements-docling.txt firecrawl-anydoc==0.2.3 -rapidocr==3.9.2 -onnxruntime==1.28.0 docx2txt==0.9 ddgs>=8.0.0 fast-ebook @@ -98,10 +96,14 @@ sqlalchemy>=2.0,<3 starlette>=1.0,<2 tiktoken==0.13.0 tokenizers==0.22.2 +# torch stays in core: sentence-transformers (local embeddings) needs it even +# with the docling extra uninstalled. torch==2.11.0 tqdm==4.67.3 -# Capped <5.9.0 to match docling-core's own darwin pin: 5.9+ breaks docling's -# PDF layout model on Apple Silicon ("Cannot convert a MPS Tensor to float64"). +# Kept in core for sentence-transformers, so installing the optional docling +# extra never changes the shared model stack. Capped <5.9.0 to match +# docling-core's own darwin pin: 5.9+ breaks docling's PDF layout model on +# Apple Silicon ("Cannot convert a MPS Tensor to float64"). transformers==5.8.1 typing-extensions==4.16.0 typing-inspect==0.9.0 diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index b027ac1a..d1dfc7c4 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -15,7 +15,12 @@ services: backend: user: root - build: ../application + build: + context: ../application + args: + # Bake the optional docling engine (OCR, structured output) into the + # image: set INSTALL_DOCLING=true in ../.env or the shell. + INSTALL_DOCLING: ${INSTALL_DOCLING:-false} env_file: - ../.env environment: @@ -38,7 +43,10 @@ services: worker: user: root - build: ../application + build: + context: ../application + args: + INSTALL_DOCLING: ${INSTALL_DOCLING:-false} # Consumes the default queue AND the dedicated `parsing` queue (read_document / # parse_document). Without `parsing` here the read_document await never resolves. # For heavy/OCR parsing run a separate worker with `-Q parsing`. diff --git a/docs/content/Deploying/Development-Environment.mdx b/docs/content/Deploying/Development-Environment.mdx index b94e8056..0a6a6bbf 100644 --- a/docs/content/Deploying/Development-Environment.mdx +++ b/docs/content/Deploying/Development-Environment.mdx @@ -96,6 +96,13 @@ To run the DocsGPT backend locally, you'll need to set up a Python environment a pip install -r application/requirements.txt ``` + Optionally add the docling parser engine (OCR, `structured` output for + `read_document`; the default anydoc engine does not need it): + + ```bash + pip install -r application/requirements-docling.txt + ``` + 5. **Run the Backend:** For local development, run the ASGI composition under uvicorn. It serves the **whole** application, hot-reloads on source changes, and matches the production runtime: diff --git a/docs/content/Deploying/DocsGPT-Settings.mdx b/docs/content/Deploying/DocsGPT-Settings.mdx index f344edac..cc10e7a0 100644 --- a/docs/content/Deploying/DocsGPT-Settings.mdx +++ b/docs/content/Deploying/DocsGPT-Settings.mdx @@ -206,7 +206,7 @@ for the engines and flows. | Setting | Default | Description | | --- | --- | --- | -| `DOC_PARSER_ENGINE` | `anydoc` | Parser engine: `anydoc` (fast Rust converter, no ML models) or `docling` (layout/table models, OCR, structured output; heavy optional install). Files anydoc cannot read fall back to Docling when installed. | +| `DOC_PARSER_ENGINE` | `anydoc` | Parser engine: `anydoc` (fast Rust converter, no ML models) or `docling` (layout/table models, OCR, structured output). Files anydoc cannot read fall back to Docling when installed. Docling is an optional extra: `pip install -r application/requirements-docling.txt`, or Docker builds with `--build-arg INSTALL_DOCLING=true`. | | `DOCLING_OCR_ENABLED` | `false` | OCR for source ingestion (Docling only). | | `DOCLING_OCR_ATTACHMENTS_ENABLED` | `false` | OCR for chat attachments (Docling only). | | `ATTACHMENT_PDF_TEXT_FAST_PATH` | `true` | Under the `docling` engine, read PDF attachments through their text layer (pypdfium2) and only hand scans to Docling. Not needed under `anydoc`. | diff --git a/docs/content/Guides/ocr.mdx b/docs/content/Guides/ocr.mdx index 3109ac52..16aec08d 100644 --- a/docs/content/Guides/ocr.mdx +++ b/docs/content/Guides/ocr.mdx @@ -41,6 +41,29 @@ sources, and the fallback for files anydoc rejects. Without Docling those formats use the standard parsers, and images are only read when `PARSE_IMAGE_REMOTE=true`. +## Installing the docling engine + +docling is not part of the base install. Add it when you need OCR, +`.adoc`/`.vtt`/`.xml` source parsing, or `read_document`'s `structured` +output: + +```bash +pip install -r application/requirements-docling.txt +``` + +Docker images build without it by default; opt in with the build argument: + +```bash +docker build --build-arg INSTALL_DOCLING=true ./application +``` + +`deployment/docker-compose.yaml` forwards the same switch, so setting +`INSTALL_DOCLING=true` in `.env` (or the shell) bakes docling into locally +built backend and worker images. `setup.sh` writes it automatically when you +answer yes to the OCR question. Either way no code changes are needed — +docling is picked up as the fallback engine as soon as it is importable, and +`DOC_PARSER_ENGINE=docling` makes it the primary one. + ## Docling OCR OCR is optional, Docling-only, and controlled by two settings: diff --git a/setup.sh b/setup.sh index e98a6e0b..310267ef 100755 --- a/setup.sh +++ b/setup.sh @@ -365,7 +365,10 @@ configure_doc_processing() { read -p "$(echo -e "${DEFAULT_FG}Enable OCR for document processing (Docling)? (y/N): ${NC}")" ocr_enabled if [[ "$ocr_enabled" =~ ^[yY]$ ]]; then echo "DOCLING_OCR_ENABLED=true" >> "$ENV_FILE" - echo -e "${GREEN}Docling OCR enabled.${NC}" + # OCR needs the optional docling engine; locally built images include + # it via this build arg (hub images must be published with it baked in). + echo "INSTALL_DOCLING=true" >> "$ENV_FILE" + echo -e "${GREEN}Docling OCR enabled (locally built images will include the docling engine).${NC}" fi } diff --git a/tests/parser/file/test_docling_parser.py b/tests/parser/file/test_docling_parser.py index 7fa3eba0..a7825c9e 100644 --- a/tests/parser/file/test_docling_parser.py +++ b/tests/parser/file/test_docling_parser.py @@ -910,6 +910,12 @@ class TestApplyInferenceSettings: and buys nothing for one-shot parses. """ + @pytest.fixture(autouse=True) + def _requires_docling(self): + # These tests reach into docling.datamodel.settings for real; a base + # install (docling is an optional extra) skips them. + pytest.importorskip("docling") + def test_disables_torch_compile_by_default(self, monkeypatch): from application.parser.file.docling_parser import _apply_inference_settings @@ -1412,6 +1418,12 @@ class TestOCRDropoutHelpers: class TestForceFullPageOCRWiring: """``force_full_page_ocr`` must reach the pipeline for any OCR engine.""" + @pytest.fixture(autouse=True) + def _requires_docling(self): + # Builds real ``docling.datamodel.pipeline_options``; a base install + # (docling is an optional extra) skips these. + pytest.importorskip("docling") + def _pipeline_options(self, monkeypatch, **kwargs): import docling.datamodel.pipeline_options as dpo diff --git a/tests/parser/file/test_pdfium_parser.py b/tests/parser/file/test_pdfium_parser.py index 22b44848..d3dc9f40 100644 --- a/tests/parser/file/test_pdfium_parser.py +++ b/tests/parser/file/test_pdfium_parser.py @@ -158,6 +158,10 @@ def test_threshold_sends_sparse_text_to_fallback(tmp_path, parser_cls): @pytest.fixture def docling_engine(monkeypatch): + # The wiring assertions below expect the docling map; on a base install + # (docling is an optional extra) the engine degrades to the legacy + # parsers and these tests do not apply. + pytest.importorskip("docling") from application.core.settings import settings monkeypatch.setattr(settings, "DOC_PARSER_ENGINE", "docling") diff --git a/tests/requirements.txt b/tests/requirements.txt index 72ddee03..806be501 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -12,3 +12,6 @@ jupyter-client>=8.0 # (python-pptx and openpyxl already ship in application/requirements.txt). python-docx>=1.1 reportlab>=4.0,<5 +# The optional docling engine stays installed in the test env so its parser +# paths keep CI coverage even though production installs it on demand. +-r ../application/requirements-docling.txt