structmd converts PDFs, Office files and images into clean, hierarchical Markdown using vision language models you control — served locally by Ollama, or from your Ollama cloud account. No data leaves your machine unless you say so.
The VLM never writes your Markdown. It only describes the page as JSON — structmd's deterministic builder turns that JSON into Markdown, so results are reproducible, auditable and editable.
Each page is rendered to an image and sent to any Ollama vision model, which returns a typed, position-aware description of every element.
Elements land in a versioned JSON document — headings, paragraphs, tables, lists, images, bounding boxes, reading order. Cache it, diff it, fix it by hand.
Pure-Python rules resolve multi-column layouts, merge cross-page paragraphs, normalize heading levels and emit final Markdown. Same JSON in, same bytes out.
Why it matters
Spot a wrong heading or a missed footnote? Edit the JSON, run
structmd doc.pdf --from-json, and get corrected Markdown instantly —
without re-running the model.
Everything you need between "I have 400 PDFs" and "they are clean Markdown files".
Reading order is resolved geometrically — full-width elements first, then left/right columns interleaved by vertical position.
An async worker pool converts entire directories concurrently with live progress, per-file error isolation and resumable caching.
Stage 2 is pure Python. Rebuild from cached JSON produces byte-identical output — ideal for CI pipelines and regression tests.
Every conversion is keyed by file content hash. Re-run a 500-file batch after fixing one prompt and only changed files hit the model again.
Convert single files, pick page ranges, rebuild from JSON, or batch whole trees — all from a
single structmd command with layered configuration.
Runs against your own Ollama server for full privacy, or against Ollama cloud models when a beefier VLM is convenient. Same command line.
<# install with the extra for your input format> $ pip install "structmd[pdf]" <# grab any Ollama vision model> $ ollama pull qwen3.5-vl <# convert> $ structmd thesis.pdf -o thesis.md --pages 1-8
from pathlib import Path from structmd import StructMDPipeline, StructMDConfig config = StructMDConfig(ollama_model="gemma4:cloud", save_assets=True) with StructMDPipeline(config) as pipeline: doc = pipeline.process("report.pdf", output_md="report.md", output_json="report.json") print(doc.content[:200]) # batch: a whole folder of PDFs through one worker pool docs = pipeline.process_batch([str(p) for p in Path("papers").glob("*.pdf")])
<# convert every supported file in a tree, 4 workers> $ structmd batch ./contracts/ -o ./markdown/ --workers 4 <# only page 1 of each file (great for invoices)> $ structmd batch ./invoices/ -o ./out/ --pages 1
<# 1. extract once> $ structmd paper.pdf --json paper.json -o paper.md <# 2. fix anything you dislike in the JSON …> $ $EDITOR paper.json <# 3. rebuild deterministically — the VLM never runs again> $ structmd --from-json paper.json -o paper.md
| Input format | Extension(s) | Handled by | Extra needed |
|---|---|---|---|
| PDF documents | PyMuPDF page renderer | pip install "structmd[pdf]" | |
| Office documents | .docx .pptx .xlsx .odt .ods .odp | LibreOffice headless → PDF | system LibreOffice |
| Images | .png .jpg .jpeg .webp .tiff .bmp | Straight to the VLM | — |
| Model family | Examples | Prompt profile |
|---|---|---|
| Qwen-VL | qwen2-vl, qwen2.5-vl, qwen3.5-vl | Tuned for Qwen's grounding style |
| SmolVLM | smolvl2, smolvlm | Compact-model friendly wording |
| PaliGemma | paligemma, paligemma2 | Short-instruction profile |
| Llama Vision | llama3.2-vision | Llama-native formatting rules |
| Default | gemma*, minicpm-v, moondream, anything else | Generic robust contract |
| Ollama cloud | append :cloud to any tag | Same profiles, remote inference |
| Approach | Editable intermediate | Deterministic rebuild | Local-first | License | |
|---|---|---|---|---|---|
| structmd | Two-stage: VLM → JSON → rules | Yes — cached JSON | Yes — byte-identical | Yes (Ollama) | MIT |
| MinerU | Layout detection + OCR models | No | No | Yes | AGPL-3.0 |
| Marker | Deep learning heuristics | No | No | Yes | GPL-3.0 / commercial |
| py-zerox | VLM per page (OpenAI-centric) | No | No | Cloud API | MIT |
| LlamaParse | Hosted SaaS API | No | No | Cloud API | Proprietary |