Open source · MIT licensed

Documents in.
Structured Markdown out.

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.

Get started $ pip install structmd
Python 3.9 – 3.13 100% offline test suite Deterministic rebuilds Local or cloud VLMs
How it works

A strict two-stage pipeline

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.

STAGE 1 · EXTRACT

Vision model reads the page

Each page is rendered to an image and sent to any Ollama vision model, which returns a typed, position-aware description of every element.

PyMuPDF render any VLM retry + backoff
ARTIFACT · JSON

Inspectable extraction file

Elements land in a versioned JSON document — headings, paragraphs, tables, lists, images, bounding boxes, reading order. Cache it, diff it, fix it by hand.

content-hash cache human-editable schema-versioned
STAGE 2 · BUILD

Deterministic Markdown builder

Pure-Python rules resolve multi-column layouts, merge cross-page paragraphs, normalize heading levels and emit final Markdown. Same JSON in, same bytes out.

column detection heading remap byte-stable

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.

Features

Built for real pipelines

Everything you need between "I have 400 PDFs" and "they are clean Markdown files".

Multi-column aware

Reading order is resolved geometrically — full-width elements first, then left/right columns interleaved by vertical position.

Fast parallel batch mode

An async worker pool converts entire directories concurrently with live progress, per-file error isolation and resumable caching.

Deterministic by design

Stage 2 is pure Python. Rebuild from cached JSON produces byte-identical output — ideal for CI pipelines and regression tests.

Content-hash caching

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.

One-command CLI

Convert single files, pick page ranges, rebuild from JSON, or batch whole trees — all from a single structmd command with layered configuration.

Local-first, cloud-capable

Runs against your own Ollama server for full privacy, or against Ollama cloud models when a beefier VLM is convenient. Same command line.

Quick start

From zero to Markdown in three commands

<# 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 structmd import StructMDPipeline

with StructMDPipeline(model="qwen3.5-vl") as pipeline:
    doc = pipeline.process("report.pdf", output_path="report.md")
    print(doc.markdown[:200])

# need just the JSON artifact?
extracted = pipeline.extract_only("report.pdf")
extracted.save_json("report.extraction.json")
<# 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 --extract-only -o paper.extraction.json

<# 2. fix anything you dislike in the JSON …>
$ $EDITOR paper.extraction.json

<# 3. rebuild deterministically — the VLM never runs again>
$ structmd paper.pdf --from-json -o paper.md
Compatibility

Formats and models

Input formatExtension(s)Handled byExtra needed
PDF documents.pdfPyMuPDF page rendererpip install "structmd[pdf]"
Office documents.docx .pptx .xlsx .odt .ods .odpLibreOffice headless → PDFsystem LibreOffice
Images.png .jpg .jpeg .webp .tiff .bmpStraight to the VLM
Model familyExamplesPrompt profile
Qwen-VLqwen2-vl, qwen2.5-vl, qwen3.5-vlTuned for Qwen's grounding style
SmolVLMsmolvl2, smolvlmCompact-model friendly wording
PaliGemmapaligemma, paligemma2Short-instruction profile
Llama Visionllama3.2-visionLlama-native formatting rules
Defaultgemma*, minicpm-v, moondream, anything elseGeneric robust contract
Ollama cloudappend :cloud to any tagSame profiles, remote inference
Landscape

How structmd compares

ApproachEditable intermediateDeterministic rebuildLocal-firstLicense
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