A docling OCR plugin that delegates text recognition to a local GLM-OCR model served by Ollama.
docling-glm-ocr-ollama is a docling plugin that
replaces the built-in OCR stage with a call to a local
GLM-OCR model running inside
Ollama.
Each page crop is sent to Ollama's OpenAI-compatible chat completion endpoint as a base64-encoded image. The model returns Markdown-formatted text which docling merges back into the document structure.
The plugin registers itself under the "glm-ocr-ollama" OCR engine key so it
can be selected per-request through docling or docling-serve without changing
application code.
- Python 3.12+
- A running Ollama instance with the
glm-ocrmodel pulled
# with uv (recommended)
uv add docling-glm-ocr-ollama
# with pip
pip install docling-glm-ocr-ollama# Pull the model
ollama pull glm-ocr
# Start the server (if not already running)
ollama servefrom docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling_glm_ocr_ollama import GlmOcrOllamaOptions
pipeline_options = PdfPipelineOptions(
allow_external_plugins=True,
ocr_options=GlmOcrOllamaOptions(
api_url="http://localhost:11434/v1/chat/completions",
model_name="glm-ocr",
),
)
converter = DocumentConverter(
format_options={
InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options)
}
)
result = converter.convert("document.pdf")
print(result.document.export_to_markdown())Select the engine per-request via the standard API:
curl -X POST http://localhost:5001/v1/convert/source \
-H 'Content-Type: application/json' \
-d '{
"options": {
"ocr_engine": "glm-ocr-ollama"
},
"sources": [{"kind": "http", "url": "https://arxiv.org/pdf/2501.17887"}]
}'The server must have DOCLING_SERVE_ALLOW_EXTERNAL_PLUGINS=true set so the
plugin is loaded automatically.
All options can be set via environment variables (useful for Docker / Compose
deployments) or programmatically via GlmOcrOllamaOptions. Explicit
constructor arguments always take precedence over environment variables.
| Variable | Description | Default |
|---|---|---|
GLMOCR_OLLAMA_API_URL |
Ollama chat completion URL | http://localhost:11434/v1/chat/completions |
GLMOCR_OLLAMA_MODEL_NAME |
Model tag as shown by ollama list |
glm-ocr |
GLMOCR_OLLAMA_PROMPT |
Text prompt sent with each image crop | see below |
GLMOCR_OLLAMA_TIMEOUT |
HTTP timeout per crop (seconds) | 300 |
GLMOCR_OLLAMA_MAX_TOKENS |
Max tokens per completion | 16384 |
GLMOCR_OLLAMA_SCALE |
Image crop rendering scale | 3.0 |
GLMOCR_OLLAMA_MAX_IMAGE_PIXELS |
Pixel budget per crop | 4500000 |
GLMOCR_OLLAMA_MAX_CONCURRENT_REQUESTS |
Max concurrent API requests | 1 |
GLMOCR_OLLAMA_MAX_RETRIES |
Max retry attempts for HTTP errors | 3 |
GLMOCR_OLLAMA_RETRY_BACKOFF_FACTOR |
Exponential backoff factor for retries | 2.0 |
GLMOCR_OLLAMA_LANG |
Comma-separated language hint(s) | en |
GLMOCR_OLLAMA_API_KEY |
Bearer token for Authorization header |
unset (no header sent) |
All options can also be set programmatically:
| Option | Type | Description | Default |
|---|---|---|---|
api_url |
str |
OpenAI-compatible chat completion URL | GLMOCR_OLLAMA_API_URL env or http://localhost:11434/v1/chat/completions |
model_name |
str |
Model tag as shown by ollama list |
GLMOCR_OLLAMA_MODEL_NAME env or glm-ocr |
prompt |
str |
Text prompt for each image crop | GLMOCR_OLLAMA_PROMPT env or default prompt |
timeout |
float |
HTTP timeout per crop (seconds) | GLMOCR_OLLAMA_TIMEOUT env or 300 |
max_tokens |
int |
Max tokens per completion | GLMOCR_OLLAMA_MAX_TOKENS env or 16384 |
scale |
float |
Image crop rendering scale | GLMOCR_OLLAMA_SCALE env or 3.0 |
max_image_pixels |
int |
Pixel budget per crop | GLMOCR_OLLAMA_MAX_IMAGE_PIXELS env or 4500000 |
max_concurrent_requests |
int |
Max concurrent API requests | GLMOCR_OLLAMA_MAX_CONCURRENT_REQUESTS env or 1 |
max_retries |
int |
Max retry attempts for HTTP errors | GLMOCR_OLLAMA_MAX_RETRIES env or 3 |
retry_backoff_factor |
float |
Exponential backoff factor for retries | GLMOCR_OLLAMA_RETRY_BACKOFF_FACTOR env or 2.0 |
lang |
list[str] |
Language hint (passed to docling) | GLMOCR_OLLAMA_LANG env (comma-separated) or ["en"] |
api_key |
str | None |
Bearer token sent in Authorization header |
GLMOCR_OLLAMA_API_KEY env or None (no header) |
Default prompt:
Recognize the text in the image and output in Markdown format.
Preserve the original layout (headings/paragraphs/tables/formulas).
Do not fabricate content that does not exist in the image.
flowchart LR
subgraph docling
Pipeline --> GlmOcrOllamaModel
end
subgraph Ollama
GLMOCR["glm-ocr"]
end
GlmOcrOllamaModel -- "POST /v1/chat/completions\n(base64 image)" --> GLMOCR
GLMOCR -- "Markdown text" --> GlmOcrOllamaModel
For each page the model:
- Collects OCR regions from the docling layout analysis
- Renders each region using the page backend (scale configurable, default 3×)
- Encodes the crop as a base64 PNG data URI
- POSTs chat completion requests to the Ollama endpoint (with retry logic)
- Returns the recognised text as
TextCellobjects for docling to merge
By default max_concurrent_requests=1 because Ollama processes one request at a time
(single GPU, single generation). If you need higher throughput, start Ollama with
OLLAMA_NUM_PARALLEL and raise max_concurrent_requests to match:
OLLAMA_NUM_PARALLEL=4 ollama serveGlmOcrOllamaOptions(max_concurrent_requests=4)Note: Setting
max_concurrent_requestsaboveOLLAMA_NUM_PARALLELwill cause excess requests to queue on the server and may trigger timeouts on large documents.
Point api_url at your remote host and set api_key if the proxy requires
authentication:
GlmOcrOllamaOptions(
api_url="https://ollama.my-server.com/v1/chat/completions",
api_key="my-secret-token",
)Or via environment variables:
GLMOCR_OLLAMA_API_URL=https://ollama.my-server.com/v1/chat/completions
GLMOCR_OLLAMA_API_KEY=my-secret-tokenOllama returns HTTP 400 with "context length" in the body when an image crop
produces more tokens than the model's context window allows. The plugin detects
this automatically and retries at 2/3 scale before recording a conversion error.
To avoid this entirely, reduce scale or max_image_pixels:
GlmOcrOllamaOptions(
scale=2.0,
max_image_pixels=2_000_000,
)git clone https://github.com/your-org/docling-glm-ocr-ollama.git
cd docling-glm-ocr-ollama
make installmake install Install dependencies and pre-commit hooks
make check Run all quality checks (ruff lint, format, ty type check)
make test Run tests with coverage report
make build Build distribution packages
make publish Publish to PyPI
make testTests are in tests/ and use pytest.
Coverage reports are generated at coverage.xml and printed to the terminal.
The e2e tests hit a real Ollama server and are skipped by default.
To run them, ensure Ollama is running with glm-ocr pulled and use the e2e marker:
GLMOCR_OLLAMA_API_URL=http://localhost:11434/v1/chat/completions pytest -m e2eThis project uses:
- ruff – linting and formatting
- ty – type checking
- pre-commit – pre-commit hooks
Run all checks:
make checkReleases are published to PyPI automatically.
Update the version in pyproject.toml, then trigger the Publish workflow from GitHub Actions:
GitHub → Actions → Publish to PyPI → Run workflow
The workflow tags the commit, builds the package, and publishes to PyPI via trusted publishing.