[GH-ISSUE #17025] issue: Docling integration: Auto-fallback to OCR + use /v1 route to avoid empty content on text-PDFs #33671

Closed
opened 2026-04-25 07:34:10 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @Soliver84 on GitHub (Aug 29, 2025).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/17025

Check Existing Issues

  • I have searched the existing issues and discussions.
  • I am using the latest version of Open WebUI.

Installation Method

Pip Install

Open WebUI Version

v0.6.26

Ollama Version (if applicable)

Nix OPENAI

Operating System

Windows Server 2022

Browser (if applicable)

Edge

Confirmation

  • I have read and followed all instructions in README.md.
  • I am using the latest version of both Open WebUI and Ollama.
  • I have included the browser console logs.
  • I have included the Docker container logs.
  • I have provided every relevant configuration, setting, and environment variable used in my setup.
  • I have clearly listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc).
  • I have documented step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation. My steps:
  • Start with the initial platform/version/OS and dependencies used,
  • Specify exact install/launch/configure commands,
  • List URLs visited, user input (incl. example values/emails/passwords if needed),
  • Describe all options and toggles enabled or changed,
  • Include any files or environmental changes,
  • Identify the expected and actual result at each stage,
  • Ensure any reasonably skilled user can follow and hit the same issue.

Expected Behavior

Expected behavior

Text-PDFs: Extract plain text (no OCR), stable even for large files.

Image/scan PDFs: If the first attempt yields no text, automatically retry with OCR (e.g., Tesseract eng,deu).

API path: Use the stable /v1/convert/file; for older Docling-Serve builds optionally fall back to /v1alpha/….

Actual Behavior

Actual behavior

With enforced OCR: Tesseract fails on low render DPI → md_content == "" → EMPTY_CONTENT.

Without OCR & without fallback: scan-only PDFs remain empty.

Docling-Serve is currently called via /v1alpha/convert/file; newer builds expect /v1/convert/file.

Steps to Reproduce

Steps to reproduce

Start OpenWebUI and Docling-Serve v0.16.1 or Docling Serve v1.3.1 (port 5001).

In OpenWebUI set ocr_engine=tesseract + ocr_lang=eng,deu.

Import a larger text-PDF (> 2 MB).

Observe EMPTY_CONTENT; Docling/Tesseract logs “0 dpi / Too few characters”.

(Optional: Disable OCR and import a scan-PDF → no automatic OCR fallback is attempted.)

Logs & Screenshots

Hier sind die relevanten Log-Auszüge, kompakt und bereit zum Einfügen ins Ticket:

OpenWebUI

2025-08-28 21:20:04.460 | INFO  | open_webui.routers.retrieval:save_docs_to_vector_db:1182 - save_docs_to_vector_db: document Effective C++.pdf af03cf83-dea4-4f53-8266-0c956a13ba43
2025-08-28 21:20:04.551 | INFO  | open_webui.routers.retrieval:save_docs_to_vector_db:1208 - Using token text splitter: cl100k_base
2025-08-28 21:20:05.176 | ERROR | open_webui.routers.retrieval:process_file:1549 - The content provided is empty. Please ensure that there is text or data present before proceeding.
ValueError: The content provided is empty. Please ensure that there is text or data present before proceeding.
2025-08-28 21:20:05.189 | INFO  | uvicorn.protocols.http.httptools_impl:send:476 - 127.0.0.1:2688 - "POST /api/v1/knowledge/af03cf83-dea4-4f53-8266-0c956a13ba43/file/add HTTP/1.1" 400

Docling-Serve (v0.16.1)

INFO:     172.17.0.1:49420 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK
INFO:     172.17.0.1:44460 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK
ERROR:docling.models.tesseract_ocr_cli_model:OSD failed (doc uploads/ec7da375-6824-42a4-a9de-c28f041ded11_Effective C++.pdf, page: 1, OCR rectangle: 0, processed image file <tempfile._TemporaryFileWrapper object at 0x7f3cda254680>):
b'Warning: Invalid resolution 0 dpi. Using 70 instead.\nEstimating resolution as 206\nToo few characters. Skipping this page\nWarning. Invalid resolution 0 dpi. Using 70 instead.\nToo few characters. Skipping this page\nError during processing.\n'
INFO:     172.17.0.1:44612 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK

Kurzkontext für Reviewer: Docling wird via /v1alpha/convert/file erfolgreich (200) aufgerufen, aber Tesseract-OSD scheitert aufgrund sehr niedriger effektiver DPI („0→70/206“), meldet „Too few characters. Skipping this page“. Dadurch kommt md_content="" zurück und OpenWebUI bricht mit EMPTY_CONTENT/HTTP 400 ab.

Additional Information

Title: Docling integration: Auto-fallback to OCR + use /v1 route to avoid empty content on text-PDFs

Components: OpenWebUI → Retrieval → DoclingLoader (open_webui/retrieval/loaders/main.py)
Versions/Setup:

OpenWebUI (local, Windows, venv)

Docling-Serve v0.16.1 (CPU Docker, port 5001)

OpenWebUI talks to Docling over HTTP (Nginx proxy present but not required to reproduce)

Sometimes ocr_engine=tesseract + ocr_lang=eng,deu set in OpenWebUI

Embedding model: text-embedding-3-large (not relevant for extraction)

Problem description

Importing larger text-based PDFs (e.g., “Effective C++.pdf”, > 2 MB) results in empty content in OpenWebUI. UI shows:

ValueError: The content provided is empty. Please ensure that there is text or data present before proceeding.

Docling/Tesseract logs at the same time:

Warning: Invalid resolution 0 dpi. Using 70 instead.

Too few characters. Skipping this page.

Key point: If OpenWebUI has an OCR engine configured (Tesseract), OCR is always enforced — even for text-PDFs. Docling renders pages at a very low default (~72 DPI). Tesseract receives tiny/blurred pages; OSD detects “too few characters” and skips pages → empty md_content → OpenWebUI raises EMPTY_CONTENT.

If no OCR parameters are sent, Docling will read the text layer, but there is no automatic OCR fallback if a PDF actually has no text (scan).

In short:

Enforced OCR: breaks on text-PDFs due to DPI/OSD heuristics → empty results.

No OCR & no fallback: scans remain empty.

Expected behavior

Text-PDFs: Extract plain text (no OCR), stable even for large files.

Image/scan PDFs: If the first attempt yields no text, automatically retry with OCR (e.g., Tesseract eng,deu).

API path: Use the stable /v1/convert/file; for older Docling-Serve builds optionally fall back to /v1alpha/….

Actual behavior

With enforced OCR: Tesseract fails on low render DPI → md_content == "" → EMPTY_CONTENT.

Without OCR & without fallback: scan-only PDFs remain empty.

Docling-Serve is currently called via /v1alpha/convert/file; newer builds expect /v1/convert/file.

Steps to reproduce

Start OpenWebUI and Docling-Serve v0.16.1 (port 5001).

In OpenWebUI set ocr_engine=tesseract + ocr_lang=eng,deu.

Import a larger text-PDF (> 2 MB).

Observe EMPTY_CONTENT; Docling/Tesseract logs “0 dpi / Too few characters”.

(Optional: Disable OCR and import a scan-PDF → no automatic OCR fallback is attempted.)

Root cause

Forcing OCR on text-PDFs is unnecessary and fragile (DPI/OSD heuristics in Tesseract).

Docling’s default PDF render DPI (~72) is too low for reliable OCR; OSD skips pages.

OpenWebUI has no auto-fallback (first try text, then OCR), thus either OCR is enforced (and may fail) or never attempted.

Proposed fix (code changes)
A) Auto-fallback inside DoclingLoader (no infinite loop)

First call: send to Docling without OCR parameters.

If md_content is empty and the user did not explicitly force OCR →
Second call (fallback): retry with ocr_engine="tesseract", ocr_lang=["eng","deu"].

Clean logging; if the user forces OCR, we keep the current behavior (no fallback).

B) Modernize API route

In _post(), hit /v1/convert/file first. If 404/405/501, fallback to /v1alpha/convert/file for older Docling-Serve tags.

Patch (diff)

File: open_webui/retrieval/loaders/main.py – class DoclingLoader

@@
class DoclingLoader:
def init(self, url, file_path=None, mime_type=None, params=None):
self.url = url.rstrip("/")
self.file_path = file_path
self.mime_type = mime_type
self.params = params or {}

  • def _post(self, files, params):
  •    """
    
  •    Try v1 route first; for older Docling-Serve builds automatically fall back to v1alpha.
    
  •    """
    
  •    v1 = f"{self.url}/v1/convert/file"
    
  •    r = requests.post(v1, files=files, data=params)
    
  •    if not r.ok and r.status_code in (404, 405, 501):
    
  •        v1a = f"{self.url}/v1alpha/convert/file"
    
  •        r = requests.post(v1a, files=files, data=params)
    
  •    return r
    
  • def load(self) -> list[Document]:
  •    def _build_params(base=None, include_ocr=False, ocr_langs=None):
    
  •        p = {"image_export_mode": "placeholder", "table_mode": "accurate"}
    
  •        if base:
    
  •            p.update(base)
    
  •        if self.params and self.params.get("do_picture_description"):
    
  •            p["do_picture_description"] = self.params.get("do_picture_description")
    
  •            mode = self.params.get("picture_description_mode", "").lower()
    
  •            if mode == "local" and self.params.get("picture_description_local", {}):
    
  •                p["picture_description_local"] = json.dumps(self.params["picture_description_local"])
    
  •            elif mode == "api" and self.params.get("picture_description_api", {}):
    
  •                p["picture_description_api"] = json.dumps(self.params["picture_description_api"])
    
  •        if include_ocr:
    
  •            p["ocr_engine"] = "tesseract"
    
  •            if isinstance(ocr_langs, list):
    
  •                p["ocr_lang"] = ocr_langs
    
  •            elif isinstance(ocr_langs, str):
    
  •                p["ocr_lang"] = [s.strip() for s in ocr_langs.split(",") if s.strip()]
    
  •        return p
    
  •    with open(self.file_path, "rb") as f:
           files = {
               "files": (
                   self.file_path,
                   f,
                   self.mime_type or "application/octet-stream",
               )
           }
    
  •        params = {"image_export_mode": "placeholder", "table_mode": "accurate"}
    
  •        if self.params:
    
  •            if self.params.get("do_picture_description"):
    
  •                params["do_picture_description"] = self.params.get("do_picture_description")
    
  •                picture_description_mode = self.params.get("picture_description_mode", "").lower()
    
  •                if picture_description_mode == "local" and self.params.get("picture_description_local", {}):
    
  •                    params["picture_description_local"] = json.dumps(self.params.get("picture_description_local", {}))
    
  •                elif picture_description_mode == "api" and self.params.get("picture_description_api", {}):
    
  •                    params["picture_description_api"] = json.dumps(self.params.get("picture_description_api", {}))
    
  •            if self.params.get("ocr_engine") and self.params.get("ocr_lang"):
    
  •                params["ocr_engine"] = self.params.get("ocr_engine")
    
  •                params["ocr_lang"] = [lang.strip() for lang in self.params.get("ocr_lang").split(",") if lang.strip()]
    
  •        endpoint = f"{self.url}/v1alpha/convert/file"
    
  •        r = requests.post(endpoint, files=files, data=params)
    
  •        user_forces_ocr = bool(self.params.get("ocr_engine"))
    
  •        params_first = _build_params(include_ocr=False)
    
  •        if user_forces_ocr:
    
  •            params_first = _build_params(include_ocr=True, ocr_langs=self.params.get("ocr_lang", "eng,deu"))
    
  •        r = self._post(files, params_first)
    
       if r.ok:
           result = r.json()
           document_data = result.get("document", {})
    
  •        text = document_data.get("md_content", "<No text content found>")
    
  •        text = document_data.get("md_content", "") or ""
           metadata = {"Content-Type": self.mime_type} if self.mime_type else {}
    
  •        log.debug("Docling extracted text: %s", text)
    
  •        return [Document(page_content=text, metadata=metadata)]
    
  •        if (not text.strip()) and (not user_forces_ocr):
    
  •            fb_langs = self.params.get("fallback_ocr_lang") or self.params.get("ocr_lang") or "eng,deu"
    
  •            log.warning("DoclingLoader: md_content empty – starting OCR fallback with Tesseract (%s)", fb_langs)
    
  •            with open(self.file_path, "rb") as f2:
    
  •                files2 = {
    
  •                    "files": (
    
  •                        self.file_path,
    
  •                        f2,
    
  •                        self.mime_type or "application/octet-stream",
    
  •                    )
    
  •                }
    
  •                r2 = self._post(files2, _build_params(include_ocr=True, ocr_langs=fb_langs))
    
  •            if r2.ok:
    
  •                result2 = r2.json()
    
  •                text2 = (result2.get("document", {}) or {}).get("md_content", "") or ""
    
  •                if text2.strip():
    
  •                    text = text2
    
  •                    log.info("DoclingLoader: OCR fallback succeeded.")
    
  •                else:
    
  •                    log.warning("DoclingLoader: OCR fallback also returned empty content.")
    
  •            else:
    
  •                log.error("DoclingLoader: OCR fallback failed: %s", r2.reason)
    
  •        log.debug("Docling extracted text length: %d", len(text))
    
  •        return [Document(page_content=text, metadata=metadata)]
       else:
           error_msg = f"Error calling Docling API: {r.reason}"
           if r.text:
               try:
                   error_data = r.json()
    

Why this makes sense (OCR background)

Avoid unnecessary OCR: Text-PDFs should never be degraded by OCR. Auto-fallback guarantees: try text layer first, OCR only if needed.

Resilient to Tesseract heuristics: Low default DPI (~72) and OSD thresholds often cause “Too few characters → skip.” By minimizing OCR use and only retrying when necessary, we avoid fragile paths.

Better UX: No more “empty” imports. Scans work without toggling settings; text-PDFs remain fast.

Backward-compat: Prefer /v1; older Docling-Serve builds still work via automatic /v1alpha fallback.

Performance impact: Only an extra HTTP call in rare fallback cases; success path stays single-shot.

Follow-ups (optional)

In UI, do not prefill ocr_engine by default.

(Later) Expose OCR knobs (psm, min_characters_to_try) and Docling render images_scale in advanced settings.

Tests:

Large text-PDF → no OCR, success.

Scan-PDF → first attempt empty, OCR fallback yields text.

User forces OCR → no fallback; keep current single-path behavior.

Originally created by @Soliver84 on GitHub (Aug 29, 2025). Original GitHub issue: https://github.com/open-webui/open-webui/issues/17025 ### Check Existing Issues - [x] I have searched the existing issues and discussions. - [x] I am using the latest version of Open WebUI. ### Installation Method Pip Install ### Open WebUI Version v0.6.26 ### Ollama Version (if applicable) Nix OPENAI ### Operating System Windows Server 2022 ### Browser (if applicable) Edge ### Confirmation - [x] I have read and followed all instructions in `README.md`. - [x] I am using the latest version of **both** Open WebUI and Ollama. - [x] I have included the browser console logs. - [x] I have included the Docker container logs. - [x] I have **provided every relevant configuration, setting, and environment variable used in my setup.** - [x] I have clearly **listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup** (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc). - [x] I have documented **step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation**. My steps: - Start with the initial platform/version/OS and dependencies used, - Specify exact install/launch/configure commands, - List URLs visited, user input (incl. example values/emails/passwords if needed), - Describe all options and toggles enabled or changed, - Include any files or environmental changes, - Identify the expected and actual result at each stage, - Ensure any reasonably skilled user can follow and hit the same issue. ### Expected Behavior Expected behavior Text-PDFs: Extract plain text (no OCR), stable even for large files. Image/scan PDFs: If the first attempt yields no text, automatically retry with OCR (e.g., Tesseract eng,deu). API path: Use the stable /v1/convert/file; for older Docling-Serve builds optionally fall back to /v1alpha/…. ### Actual Behavior Actual behavior With enforced OCR: Tesseract fails on low render DPI → md_content == "" → EMPTY_CONTENT. Without OCR & without fallback: scan-only PDFs remain empty. Docling-Serve is currently called via /v1alpha/convert/file; newer builds expect /v1/convert/file. ### Steps to Reproduce Steps to reproduce Start OpenWebUI and Docling-Serve v0.16.1 or Docling Serve v1.3.1 (port 5001). In OpenWebUI set ocr_engine=tesseract + ocr_lang=eng,deu. Import a larger text-PDF (> 2 MB). Observe EMPTY_CONTENT; Docling/Tesseract logs “0 dpi / Too few characters”. (Optional: Disable OCR and import a scan-PDF → no automatic OCR fallback is attempted.) ### Logs & Screenshots Hier sind die **relevanten Log-Auszüge**, kompakt und bereit zum Einfügen ins Ticket: ### OpenWebUI ``` 2025-08-28 21:20:04.460 | INFO | open_webui.routers.retrieval:save_docs_to_vector_db:1182 - save_docs_to_vector_db: document Effective C++.pdf af03cf83-dea4-4f53-8266-0c956a13ba43 2025-08-28 21:20:04.551 | INFO | open_webui.routers.retrieval:save_docs_to_vector_db:1208 - Using token text splitter: cl100k_base 2025-08-28 21:20:05.176 | ERROR | open_webui.routers.retrieval:process_file:1549 - The content provided is empty. Please ensure that there is text or data present before proceeding. ValueError: The content provided is empty. Please ensure that there is text or data present before proceeding. 2025-08-28 21:20:05.189 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 127.0.0.1:2688 - "POST /api/v1/knowledge/af03cf83-dea4-4f53-8266-0c956a13ba43/file/add HTTP/1.1" 400 ``` ### Docling-Serve (v0.16.1) ``` INFO: 172.17.0.1:49420 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK INFO: 172.17.0.1:44460 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK ERROR:docling.models.tesseract_ocr_cli_model:OSD failed (doc uploads/ec7da375-6824-42a4-a9de-c28f041ded11_Effective C++.pdf, page: 1, OCR rectangle: 0, processed image file <tempfile._TemporaryFileWrapper object at 0x7f3cda254680>): b'Warning: Invalid resolution 0 dpi. Using 70 instead.\nEstimating resolution as 206\nToo few characters. Skipping this page\nWarning. Invalid resolution 0 dpi. Using 70 instead.\nToo few characters. Skipping this page\nError during processing.\n' INFO: 172.17.0.1:44612 - "POST /v1alpha/convert/file HTTP/1.1" 200 OK ``` **Kurzkontext für Reviewer:** Docling wird via `/v1alpha/convert/file` erfolgreich (200) aufgerufen, aber Tesseract-OSD scheitert aufgrund sehr niedriger effektiver DPI („0→70/206“), meldet „Too few characters. Skipping this page“. Dadurch kommt `md_content=""` zurück und OpenWebUI bricht mit `EMPTY_CONTENT`/HTTP 400 ab. ### Additional Information Title: Docling integration: Auto-fallback to OCR + use /v1 route to avoid empty content on text-PDFs Components: OpenWebUI → Retrieval → DoclingLoader (open_webui/retrieval/loaders/main.py) Versions/Setup: OpenWebUI (local, Windows, venv) Docling-Serve v0.16.1 (CPU Docker, port 5001) OpenWebUI talks to Docling over HTTP (Nginx proxy present but not required to reproduce) Sometimes ocr_engine=tesseract + ocr_lang=eng,deu set in OpenWebUI Embedding model: text-embedding-3-large (not relevant for extraction) Problem description Importing larger text-based PDFs (e.g., “Effective C++.pdf”, > 2 MB) results in empty content in OpenWebUI. UI shows: ValueError: The content provided is empty. Please ensure that there is text or data present before proceeding. Docling/Tesseract logs at the same time: Warning: Invalid resolution 0 dpi. Using 70 instead. Too few characters. Skipping this page. Key point: If OpenWebUI has an OCR engine configured (Tesseract), OCR is always enforced — even for text-PDFs. Docling renders pages at a very low default (~72 DPI). Tesseract receives tiny/blurred pages; OSD detects “too few characters” and skips pages → empty md_content → OpenWebUI raises EMPTY_CONTENT. If no OCR parameters are sent, Docling will read the text layer, but there is no automatic OCR fallback if a PDF actually has no text (scan). In short: Enforced OCR: breaks on text-PDFs due to DPI/OSD heuristics → empty results. No OCR & no fallback: scans remain empty. Expected behavior Text-PDFs: Extract plain text (no OCR), stable even for large files. Image/scan PDFs: If the first attempt yields no text, automatically retry with OCR (e.g., Tesseract eng,deu). API path: Use the stable /v1/convert/file; for older Docling-Serve builds optionally fall back to /v1alpha/…. Actual behavior With enforced OCR: Tesseract fails on low render DPI → md_content == "" → EMPTY_CONTENT. Without OCR & without fallback: scan-only PDFs remain empty. Docling-Serve is currently called via /v1alpha/convert/file; newer builds expect /v1/convert/file. Steps to reproduce Start OpenWebUI and Docling-Serve v0.16.1 (port 5001). In OpenWebUI set ocr_engine=tesseract + ocr_lang=eng,deu. Import a larger text-PDF (> 2 MB). Observe EMPTY_CONTENT; Docling/Tesseract logs “0 dpi / Too few characters”. (Optional: Disable OCR and import a scan-PDF → no automatic OCR fallback is attempted.) Root cause Forcing OCR on text-PDFs is unnecessary and fragile (DPI/OSD heuristics in Tesseract). Docling’s default PDF render DPI (~72) is too low for reliable OCR; OSD skips pages. OpenWebUI has no auto-fallback (first try text, then OCR), thus either OCR is enforced (and may fail) or never attempted. Proposed fix (code changes) A) Auto-fallback inside DoclingLoader (no infinite loop) First call: send to Docling without OCR parameters. If md_content is empty and the user did not explicitly force OCR → Second call (fallback): retry with ocr_engine="tesseract", ocr_lang=["eng","deu"]. Clean logging; if the user forces OCR, we keep the current behavior (no fallback). B) Modernize API route In _post(), hit /v1/convert/file first. If 404/405/501, fallback to /v1alpha/convert/file for older Docling-Serve tags. Patch (diff) File: open_webui/retrieval/loaders/main.py – class DoclingLoader @@ class DoclingLoader: def __init__(self, url, file_path=None, mime_type=None, params=None): self.url = url.rstrip("/") self.file_path = file_path self.mime_type = mime_type self.params = params or {} + def _post(self, files, params): + """ + Try v1 route first; for older Docling-Serve builds automatically fall back to v1alpha. + """ + v1 = f"{self.url}/v1/convert/file" + r = requests.post(v1, files=files, data=params) + if not r.ok and r.status_code in (404, 405, 501): + v1a = f"{self.url}/v1alpha/convert/file" + r = requests.post(v1a, files=files, data=params) + return r + def load(self) -> list[Document]: + def _build_params(base=None, include_ocr=False, ocr_langs=None): + p = {"image_export_mode": "placeholder", "table_mode": "accurate"} + if base: + p.update(base) + if self.params and self.params.get("do_picture_description"): + p["do_picture_description"] = self.params.get("do_picture_description") + mode = self.params.get("picture_description_mode", "").lower() + if mode == "local" and self.params.get("picture_description_local", {}): + p["picture_description_local"] = json.dumps(self.params["picture_description_local"]) + elif mode == "api" and self.params.get("picture_description_api", {}): + p["picture_description_api"] = json.dumps(self.params["picture_description_api"]) + if include_ocr: + p["ocr_engine"] = "tesseract" + if isinstance(ocr_langs, list): + p["ocr_lang"] = ocr_langs + elif isinstance(ocr_langs, str): + p["ocr_lang"] = [s.strip() for s in ocr_langs.split(",") if s.strip()] + return p + with open(self.file_path, "rb") as f: files = { "files": ( self.file_path, f, self.mime_type or "application/octet-stream", ) } - params = {"image_export_mode": "placeholder", "table_mode": "accurate"} - - if self.params: - if self.params.get("do_picture_description"): - params["do_picture_description"] = self.params.get("do_picture_description") - picture_description_mode = self.params.get("picture_description_mode", "").lower() - if picture_description_mode == "local" and self.params.get("picture_description_local", {}): - params["picture_description_local"] = json.dumps(self.params.get("picture_description_local", {})) - elif picture_description_mode == "api" and self.params.get("picture_description_api", {}): - params["picture_description_api"] = json.dumps(self.params.get("picture_description_api", {})) - - if self.params.get("ocr_engine") and self.params.get("ocr_lang"): - params["ocr_engine"] = self.params.get("ocr_engine") - params["ocr_lang"] = [lang.strip() for lang in self.params.get("ocr_lang").split(",") if lang.strip()] - - endpoint = f"{self.url}/v1alpha/convert/file" - r = requests.post(endpoint, files=files, data=params) + user_forces_ocr = bool(self.params.get("ocr_engine")) + params_first = _build_params(include_ocr=False) + if user_forces_ocr: + params_first = _build_params(include_ocr=True, ocr_langs=self.params.get("ocr_lang", "eng,deu")) + r = self._post(files, params_first) if r.ok: result = r.json() document_data = result.get("document", {}) - text = document_data.get("md_content", "<No text content found>") + text = document_data.get("md_content", "") or "" metadata = {"Content-Type": self.mime_type} if self.mime_type else {} - log.debug("Docling extracted text: %s", text) - return [Document(page_content=text, metadata=metadata)] + if (not text.strip()) and (not user_forces_ocr): + fb_langs = self.params.get("fallback_ocr_lang") or self.params.get("ocr_lang") or "eng,deu" + log.warning("DoclingLoader: md_content empty – starting OCR fallback with Tesseract (%s)", fb_langs) + with open(self.file_path, "rb") as f2: + files2 = { + "files": ( + self.file_path, + f2, + self.mime_type or "application/octet-stream", + ) + } + r2 = self._post(files2, _build_params(include_ocr=True, ocr_langs=fb_langs)) + if r2.ok: + result2 = r2.json() + text2 = (result2.get("document", {}) or {}).get("md_content", "") or "" + if text2.strip(): + text = text2 + log.info("DoclingLoader: OCR fallback succeeded.") + else: + log.warning("DoclingLoader: OCR fallback also returned empty content.") + else: + log.error("DoclingLoader: OCR fallback failed: %s", r2.reason) + + log.debug("Docling extracted text length: %d", len(text)) + return [Document(page_content=text, metadata=metadata)] else: error_msg = f"Error calling Docling API: {r.reason}" if r.text: try: error_data = r.json() Why this makes sense (OCR background) Avoid unnecessary OCR: Text-PDFs should never be degraded by OCR. Auto-fallback guarantees: try text layer first, OCR only if needed. Resilient to Tesseract heuristics: Low default DPI (~72) and OSD thresholds often cause “Too few characters → skip.” By minimizing OCR use and only retrying when necessary, we avoid fragile paths. Better UX: No more “empty” imports. Scans work without toggling settings; text-PDFs remain fast. Backward-compat: Prefer /v1; older Docling-Serve builds still work via automatic /v1alpha fallback. Performance impact: Only an extra HTTP call in rare fallback cases; success path stays single-shot. Follow-ups (optional) In UI, do not prefill ocr_engine by default. (Later) Expose OCR knobs (psm, min_characters_to_try) and Docling render images_scale in advanced settings. Tests: Large text-PDF → no OCR, success. Scan-PDF → first attempt empty, OCR fallback yields text. User forces OCR → no fallback; keep current single-path behavior.
GiteaMirror added the bug label 2026-04-25 07:34:10 -05:00
Author
Owner

@BakirBukvic commented on GitHub (Aug 29, 2025):

I had a similar issue. A workaround is to setup your own docling server, without using the docker image. Then connect it through custom document engine.
And then handle the logic there. If text page, then don't use OCR. If image, then use OCR.
It's not a fix, just a workaround.

<!-- gh-comment-id:3236079876 --> @BakirBukvic commented on GitHub (Aug 29, 2025): I had a similar issue. A workaround is to setup your own docling server, without using the docker image. Then connect it through custom document engine. And then handle the logic there. If text page, then don't use OCR. If image, then use OCR. It's not a fix, just a workaround.
Author
Owner

@Soliver84 commented on GitHub (Aug 29, 2025):

@BakirBukvic
I also have Docling an openwebu running locally.

Can you give me your configuration, how it works for you with all PDFs, even larger ones up to 80MB?

And also what exactly you've configured in openwebui. I've disabled Tesseract OCR.

<!-- gh-comment-id:3236319969 --> @Soliver84 commented on GitHub (Aug 29, 2025): @BakirBukvic I also have Docling an openwebu running locally. Can you give me your configuration, how it works for you with all PDFs, even larger ones up to 80MB? And also what exactly you've configured in openwebui. I've disabled Tesseract OCR.
Author
Owner

@tjbck commented on GitHub (Aug 31, 2025):

PR welcome.

<!-- gh-comment-id:3239979679 --> @tjbck commented on GitHub (Aug 31, 2025): PR welcome.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#33671