Skip to main content

pagemark

PyPI Python License: MIT

pagemark converts PDFs to Markdown with vision-language models.

Use it with any OpenAI-compatible endpoint. You provide the base_url, model, and api_key. pagemark renders each page, grounds the model with the PDF text layer, and checks the result for common failures: repetition loops, truncation, dropped columns, and hallucinated tables. If a check fails, it retries with a stricter strategy and falls back to the text layer if the model still cannot recover.

If one page fails, the document still finishes. pagemark records warnings for that page instead of aborting the whole conversion.

Prerequisites

  • Python 3.10+
  • An OpenAI-compatible endpoint serving a vision-language model (Ollama, vLLM, LM Studio, any hosted provider, etc.)

Install

pip install pagemark            # library only
pip install "pagemark[cli]"     # with CLI

With uv:

uv add pagemark                 # add to your project
uv add "pagemark[cli]"          # with CLI

uv tool install "pagemark[cli]" # standalone CLI, no project needed
uvx --from "pagemark[cli]" pagemark doctor --help  # run it without installing

The --from is needed for the uvx form: plain uvx pagemark installs the package without the cli extra, and the CLI exits telling you to add it.

Quick start

from pagemark import Pagemark

client = Pagemark(
    base_url="http://localhost:11434/v1", # OpenAI-compatible API
    api_key="your-key", # API key
)

doc = client.convert("paper.pdf", model="qwen3-vl:8b")

print(doc.markdown)
doc.save("out.md", assets_dir="out_assets/")

api_key is optional. pagemark resolves it from the explicit argument first, then from OPENAI_API_KEY. If neither exists, it raises an error that names the base_url.

Async

aconvert() is the implementation behind convert(). convert() wraps it with asyncio.run(). Pages run concurrently, bounded by a semaphore. The default limit is 4.

from pagemark import Pagemark

client = Pagemark(
    base_url="http://localhost:11434/v1", # OpenAI-compatible API
    api_key="your-key", # API key
)

doc = await client.aconvert(
    "paper.pdf",
    model="qwen3-vl:8b",
    concurrency=8,
)

Page selection

Convert selected pages instead of the whole document:

doc = client.convert("paper.pdf", model="qwen3-vl:8b", pages="1-5,9,12-")

pages accepts "3", "1-5", "1-5,9,12-", or a list[int]. Out-of-range indices raise an error instead of being silently clipped.

Bring your own model

If you need settings the constructor does not expose, pass a pre-configured BaseChatModel. That covers custom timeouts, proxies, non-OpenAI LangChain backends like ChatAnthropic or ChatOllama, callbacks, and similar options.

from langchain_openai import ChatOpenAI
from pagemark import Pagemark

my_model = ChatOpenAI(
    model="...",
    base_url="...",
    api_key="...",
    timeout=120,
    max_retries=5,
)

client = Pagemark(chat_model=my_model)
doc = client.convert("paper.pdf")

CLI

pagemark convert paper.pdf -o paper.md \
    --model qwen3-vl:8b \
    --base-url http://localhost:11434/v1

pagemark convert paper.pdf -o paper.md --json paper.json \
    --model qwen3-vl:8b \
    --base-url http://localhost:11434/v1

pagemark doctor --base-url http://localhost:11434/v1 --model qwen3-vl:8b

doctor checks whether the endpoint is reachable, whether the model is present, and where the API key came from.

How it works

pdf -> render + anchor -> prompt (profile) -> ChatOpenAI -> guardrails -> parse -> assemble -> Document
       pypdfium2          per-model           langchain      retry ladder   md-it    stitching   md + json
  1. Render each page to a PNG with pypdfium2, downscaled to the profile's pixel budget.
  2. Extract the PDF text layer as an anchor for the model. Long pages are middle-elided to about 6k characters.
  3. Send the image and anchor text to the VLM.
  4. Check every response with five guardrails.
  5. Retry with stricter strategies when a guardrail fails.
  6. Assemble the pages into a Document with typed blocks, then stitch across pages by stripping running headers and footers, joining split paragraphs, and dehyphenating text.

Guardrails

Check What it catches
Repetition n-gram loops, collapsed gzip ratio
Truncation finish_reason == "length"
Coverage anchor tokens missing from output; catches silently dropped columns
Hallucination high rate of output tokens absent from anchor (born-digital pages)
Refusal "I'm sorry", "I cannot", near-empty output on a non-blank page

Escalation ladder

When any guardrail fails, pagemark retries with more aggressive strategies:

Step Strategy Confidence ceiling
1 Normal render, standard prompt 0.95
2 Reseed: temperature=0.3, seed=42 0.85
3 High DPI: DPI x1.5 (capped at 400), pixels x1.5 0.75
4 Fallback prompt: simpler instruction 0.60
5 Text layer fallback 0.30

Each page includes source, confidence, and warnings, so downstream consumers can filter by quality.

Output

convert() returns a Document with:

  • markdown: the full stitched Markdown output
  • pages: list of Page objects, each with:
    • markdown: per-page Markdown
    • blocks: typed blocks (heading, paragraph, table, figure, code, list, formula, caption, footnote)
    • source: "vlm" or "text_layer" (if the model couldn't handle the page)
    • confidence: 0-1 score
    • warnings: what went wrong, if anything
  • assets: embedded images extracted from the PDF
  • model_dump_json(): full structured output as JSON
  • save(path, assets_dir=...): writes the Markdown file and extracted images

License

MIT. Runtime dependencies are permissively licensed - mostly MIT/Apache-2.0/BSD, with a few under MPL-2.0 (certifi, orjson) and PSF-2.0 (typing-extensions). No GPL/LGPL/AGPL dependencies in the tree.

Metadata

Release files for pagemark 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pagemark 0.1.1
File Size Uploaded
pagemark-0.1.1.tar.gz 23.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pagemark 0.1.1
File Interpreter ABI Platform
pagemark-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 45.3 kB

Release files / pagemark-0.1.1.tar.gz

Download URL pagemark-0.1.1.tar.gz
Size 23.7 kB
Tags Source
SHA-256 checksum
How to use checksums
bd16e1540a704808f23371cbfadc4593227441220652a3a755d9c27a72319f67
BLAKE2b-256 checksum
How to use checksums
8320c712c7b26eed3a4b51b2621c814a697d46ba438822dbdc30e4246b13c68f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / pagemark-0.1.1-py3-none-any.whl

Download URL pagemark-0.1.1-py3-none-any.whl
Size 21.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ad6f858aeeb71b0630f39ee5e2996f74c99802f5061fd4bd7e708f885f8414a
BLAKE2b-256 checksum
How to use checksums
19cf02a4ca0304990be4c04c0b038c7f90ebcac7de9bc1d6e1c781624ab7a761
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page