Hybrid mode combines the speed of local Java processing with the accuracy of AI backends. Instead of sending every page to an AI service, OpenDataLoader intelligently routes only complex pages (tables, OCR) to the backend while processing simple text pages locally.
Results: Table accuracy jumps from 0.489 → 0.928 (+90%) with acceptable speed trade-off.
# Batch all files in one call — each invocation spawns a JVM process, so repeated calls are slowopendataloader-pdf --hybrid docling-fast file1.pdf file2.pdf folder/
import opendataloader_pdf# Batch all files in one call — each convert() spawns a JVM process, so repeated calls are slowopendataloader_pdf.convert( input_path=["file1.pdf", "file2.pdf", "folder/"], output_dir="output/", hybrid="docling-fast" # Routes complex pages to AI backend)
PDF Input │ ▼┌─────────────────────────────────────┐│ Triage Processor ││ Analyzes each page complexity │└─────────────────────────────────────┘ │ │ ▼ ▼┌─────────────┐ ┌─────────────────┐│ JAVA Path │ │ BACKEND Path ││ (0.015s) │ │ (AI processing)││ Simple │ │ Complex tables ││ text pages │ │ OCR pages │└─────────────┘ └─────────────────┘ │ │ └────────┬───────────┘ ▼┌─────────────────────────────────────┐│ Result Merger ││ Combines results by page order │└─────────────────────────────────────┘
This is --hybrid-mode auto, the default. --hybrid-mode full removes the triage step and sends every page down the backend path; running without --hybrid removes the backend path and sends every page down the Java one. See Auto-Triage Strategy.
Routing is not fixed — it is controlled by --hybrid-mode (hybrid_mode in Python), which defaults to auto. Together with leaving hybrid off, that gives three levels of engine use:
Setting
Where pages go
No hybrid
Every page on the local Java engine. No external calls, no server to start
--hybrid-mode auto
(Default) Decided per page: simple pages stay local, complex ones go to the backend
--hybrid-mode full
Every page to the backend. Triage is skipped entirely
--hybrid-mode needs a backend to route to: it does nothing unless --hybrid names one, and --hybrid is off until you set it.
Omitting --hybrid (or passing --hybrid off) keeps every page on the local Java engine. Nothing leaves the machine and there is no backend server to start.
opendataloader-pdf file1.pdf file2.pdf folder/
This is the fastest of the three and the baseline the table at the top of this page compares against.
The triage processor uses a conservative strategy: it routes uncertain pages to the backend to minimize missed tables (false negatives). This means:
Simple text pages → Fast Java path
Pages with tables → Backend path
Uncertain pages → Backend path (better safe than sorry)
# These two are identical — auto is the defaultopendataloader-pdf --hybrid docling-fast file1.pdf file2.pdf folder/opendataloader-pdf --hybrid docling-fast --hybrid-mode auto file1.pdf file2.pdf folder/
Triage is skipped and every page takes the backend path. This is the slowest setting and the one with the highest accuracy ceiling.
opendataloader-pdf --hybrid docling-fast --hybrid-mode full file1.pdf file2.pdf folder/
Two reasons to choose it:
Picture description requires it. The enrichment runs on the backend either way, but the descriptions only reach the output in full — see Chart and Image Description.
You already know every page needs the backend. Then per-page analysis is work with a foregone conclusion, and full skips it.
# Batch all files in one call — each convert() spawns a JVM process, so repeated calls are slowopendataloader_pdf.convert( input_path=["file1.pdf", "file2.pdf", "folder/"], output_dir="output/", hybrid="docling-fast", hybrid_mode="auto", # "auto" decides per page; "full" sends every page hybrid_url="http://localhost:5002", # Custom backend URL hybrid_timeout="60000", # 60 second timeout hybrid_fallback=True # Opt in to Java fallback on error)
# Batch all files in one call — each invocation spawns a JVM process, so repeated calls are slowopendataloader-pdf \ --hybrid docling-fast \ --hybrid-mode auto \ --hybrid-url http://localhost:5002 \ --hybrid-timeout 60000 \ --hybrid-fallback \ file1.pdf file2.pdf folder/
# Batch all files in one call — each invocation spawns a JVM process, so repeated calls are slowopendataloader-pdf --hybrid docling-fast file1.pdf file2.pdf folder/
For non-English documents, specify the OCR language. The default engine is EasyOCR, which uses ISO 639-1 codes:
Note for Arabic and other RTL scripts: With the default EasyOCR engine, character recognition uses EasyOCR's ar model. The current reading order algorithm processes text based on coordinates and does not perform RTL shaping or visual reordering, so text strings may appear in visual order rather than logical order. This limitation applies to all right-to-left scripts.
tesseract binary on PATH + tessdata for each language
CLI bridge. Honors --psm
tesserocr
tesserocr Python package + tesseract tessdata
Tesseract via Python bindings. Honors --psm
rapidocr
rapidocr and onnxruntime Python packages (pip install rapidocr onnxruntime)
ONNX-based engine
ocrmac
ocrmac Python package; macOS only
Apple Vision framework
auto
—
Delegates engine selection to docling
Each engine has its own license, language coverage, and accuracy characteristics; refer to the engine's own documentation. This server does not validate engine accuracy.
Prerequisite check: The server validates the selected engine at startup. If the binary or Python package is missing, it exits with code 2 and a message naming what to install — for example, "OCR engine 'tesseract' selected but the 'tesseract' binary was not found on PATH".
When the input PDFs already contain reliable embedded text, OCR can re-extract text from images such as charts, diagrams, or screenshots, producing duplicate fragments. Use --no-ocr to skip OCR entirely:
opendataloader-pdf-hybrid --port 5002 --no-ocr
--no-ocr and --force-ocr are mutually exclusive. When --no-ocr is combined with --ocr-engine, --ocr-lang, or --psm, the server logs a warning naming the inert flags.
import opendataloader_pdf# Batch all files in one call — each convert() spawns a JVM process, so repeated calls are slowopendataloader_pdf.convert( input_path=["file1.pdf", "file2.pdf", "folder/"], output_dir="output/", hybrid="docling-fast")
Start the backend server with --force-ocr before running the Python conversion.
Note: Standard digital PDFs do not need --force-ocr. Use it only for scanned or image-based PDFs where text cannot be selected.
Timeout: OCR is CPU-intensive. By default there is no timeout, but you can set one explicitly:
# Batch all files in one call — each invocation spawns a JVM process, so repeated calls are slowopendataloader-pdf --hybrid docling-fast --hybrid-timeout 120000 file1.pdf file2.pdf folder/
Generate AI-powered natural language descriptions for images and charts in your PDFs. This makes visual content searchable in RAG pipelines and produces alt text for accessibility.
Important: Picture description requires --hybrid-mode full on the client side. Without it, the enrichment runs on the backend but the descriptions are not included in the output.
# Batch all files in one call — each invocation spawns a JVM process, so repeated calls are slowopendataloader-pdf --hybrid docling-fast --hybrid-mode full file1.pdf file2.pdf folder/
import opendataloader_pdf# Batch all files in one call — each convert() spawns a JVM process, so repeated calls are slowopendataloader_pdf.convert( input_path=["file1.pdf", "file2.pdf", "folder/"], output_dir="output/", hybrid="docling-fast", hybrid_mode="full" # Required for picture description)
Start the backend server with --enrich-picture-description before running.
The description appears in the JSON output under "description" and as an italic caption in Markdown:
{ "type": "picture", "page number": 1, "bounding box": [72.0, 400.0, 540.0, 650.0], "description": "A bar chart showing waste generation by region from 2016 to 2030..."}
*A bar chart showing waste generation by region from 2016 to 2030...*
You can customize the prompt for specific document types:
opendataloader-pdf-hybrid --enrich-picture-description \ --picture-description-prompt "Describe this scientific figure in detail, including axis labels and data trends."
Note: Picture description uses SmolVLM (256M), a lightweight vision model. Results are suitable for general context but may not capture precise data values from complex charts. The model is English-centric — prompts asking for non-English output (e.g., "Describe the image in Korean.") will not produce coherent translations and are not recommended.
The --ocr-lang code system varies by engine. If omitted, each engine uses its own default languages.
Engine
Code system
Example
easyocr
ISO 639-1
ko,en
tesseract / tesserocr
ISO 639-2
kor,eng
rapidocr
Plain English names
english,chinese
ocrmac
BCP-47
en-US
Engine availability check: At startup, the server probes whether the selected engine's binary or Python package is installed. If not, it exits with code 2 and a message naming the missing prerequisite — for example, Tesseract requires the tesseract binary on PATH.
Inert flag warning: When --no-ocr is combined with OCR-related flags (--ocr-engine, --ocr-lang, --psm), the server logs a single warning naming the inert flags rather than silently dropping them.
Warning: Both --use-struct-tree and --hybrid were set on a tagged PDF. The structure tree takes precedence, so the hybrid backend was NOT called. A well-tagged PDF already carries reading order and structure; drop --use-struct-tree if you want the hybrid backend instead.
--use-struct-tree takes precedence over --hybrid on tagged PDFs. If hybrid mode gave no accuracy improvement, check whether --use-struct-tree is set — drop it to route complex pages to the backend, or keep it to rely on the PDF's own tags. On PDFs with no structure tree, --use-struct-tree is ignored and hybrid runs normally.