关于 pdfmux — PDF extraction that actually works
pdfmux 是一个 PDF 提取 MCP 服务,可按页审核输出结果,并标记无法读取的页面而非静默丢弃。它面向 RAG 流水线和需要可靠 PDF 文本的 AI Agent,并可用 pdfmux verify 校验其他提取工具的输出。
主要功能
- 01按页审核提取结果并自动重新提取失败页面
- 02按页面内容路由到最合适的提取后端
- 03支持 RAG 分块与 Markdown、JSON 输出
- 04通过 verify 检测其他引擎输出中的静默丢失页面
- 05扫描件 OCR 与可选的 LLM 回退
适用场景
- 01为 RAG 索引准备 PDF 文本并排查缺失页面
- 02让 AI Agent 在会话中读取和转换 PDF 文件
- 03批量校验第三方 PDF 提取工具的输出完整性
MCP 相关
该项目作为 MCP 服务运行,通过 pdfmux serve 提供 stdio 或 HTTP 模式,向 MCP 客户端暴露 PDF 元数据读取、转换、分析、批量转换和结构化提取等工具,以及 verify_extraction 校验工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
pdfmux
具有自我修复能力的 PDF 提取工具:标记无法读取的页面,而不是将其丢弃;现在还能验证任何提取器的输出,找出静默丢页。 开源的 LlamaParse 替代方案,适用于 RAG 工作流;可用作 Claude Desktop 的 MCP 服务,以及 LangChain + LlamaIndex 加载器。
pdfmux 能提取 PDF 并检查自己的工作;现在还能验证任何提取器的输出,告诉你哪些页面被静默丢弃。免费,采用 MIT 许可证。方法正在申请专利。
pip install pdfmux。
两项功能,一个工具:
- 自我修复式提取。 唯一能审核自身输出的 PDF 提取器。它能发现空白页面、错乱的分栏和损坏的表格,并使用更强大的后端重新提取;对于仍无法读取的内容,会进行标记,而不是静默丢弃。这样,大模型获取的是干净数据,而不是无声的垃圾数据。它会将每个页面路由到 7 个内置提取后端中的最佳选项,并支持 BYOK 大模型回退方案(Gemini / Claude / GPT-4o / Ollama)。一个 CLI,一个 API,零配置。
- 验证任何提取结果 — v1.8.1 新增。
pdfmux verify会根据源 PDF 审核任何提取引擎的输出,包括 Reducto、Mistral OCR、LlamaParse、Docling 和你自研的解析器,并告诉你哪些页面被静默丢弃。免费,采用 MIT 许可证,专利方面无风险。
PDF ──> pdfmux router ──> best extractor per page ──> audit ──> re-extract failures ──> Markdown / JSON / chunks
|
├─ PyMuPDF (digital text, 0.01s/page)
├─ OpenDataLoader (complex layouts, 0.05s/page)
├─ RapidOCR (scanned pages, CPU-only)
├─ Docling (tables, 97.9% TEDS)
├─ Surya (heavy OCR fallback)
├─ Marker (academic papers, neural)
├─ Mistral OCR ($0.002/page, 96.6% tables)
└─ YOUR LLM (Gemini / Gemma 3 / Claude / GPT-4o / Ollama / Mistral — BYOK via YAML)
安装
pip install pdfmux
这适用于数字 PDF。对于任何真实场景中的批量处理,也请安装 pdfmux[ocr]——几乎每个 PDF 文件夹里都有至少一份扫描件;没有 OCR,这些页面就会返回空文本:
pip install "pdfmux[ocr]" # ⭐ recommended — RapidOCR for scanned pages (~200MB, CPU)
按文档类型选择其他后端:
pip install "pdfmux[tables]" # Docling — table-heavy docs (~500MB)
pip install "pdfmux[opendataloader]" # OpenDataLoader — complex layouts (Java 11+)
pip install "pdfmux[marker]" # Marker — neural extraction for academic papers
pip install "pdfmux[llm]" # Gemini fallback (default LLM)
pip install "pdfmux[llm-claude]" # Claude (Sonnet / Opus)
pip install "pdfmux[llm-openai]" # GPT-4o family
pip install "pdfmux[llm-ollama]" # Ollama (any local model)
pip install "pdfmux[llm-mistral]" # Mistral OCR API ($0.002/page)
pip install "pdfmux[llm-all]" # all LLM providers (incl. Gemma via Gemini key)
pip install "pdfmux[watch]" # `pdfmux watch <dir>` auto-convert on change
pip install "pdfmux[all]" # everything
需要 Python 3.11+。
快速开始
CLI
# zero config — just works
pdfmux convert invoice.pdf
# invoice.pdf -> invoice.md (2 pages, 95% confidence, via pymupdf4llm)
# RAG-ready chunks with token limits
pdfmux convert report.pdf --chunk --max-tokens 500
# cost-aware extraction with budget cap
pdfmux convert report.pdf --mode economy --budget 0.50
# schema-guided structured extraction (5 built-in presets)
pdfmux convert invoice.pdf --schema invoice
# BYOK any LLM for hardest pages
pdfmux convert scan.pdf --llm-provider claude
# use a built-in or saved profile (invoices, receipts, papers, contracts, bulk-rag)
pdfmux convert invoice.pdf --profile invoices
# predict cost before running anything
pdfmux estimate big-report.pdf --llm-provider gemini
# stream pages as NDJSON as they finish (great for long documents)
pdfmux stream report.pdf --quality high
# auto-convert any new PDFs that land in a folder
pdfmux watch ./inbox/ -o ./output/
# diff two extractions side-by-side
pdfmux diff old.pdf new.pdf
# batch a directory — writes manifest.json with per-doc confidence
pdfmux convert ./docs/ -o ./output/
# CI mode: fail the run if any document is below 0.20 confidence
pdfmux convert ./docs/ -o ./output/ --strict --min-confidence 0.20
# pre-flight a directory: which extras do you actually need for THIS batch?
pdfmux doctor --check ./docs/
# results are cached by file hash — re-runs are instant; bypass with --no-cache
pdfmux convert report.pdf --no-cache
pdfmux convert report.pdf --clear-cache
Python
批量处理时,请使用 batch_extract(),不要循环调用 subprocess.run(['pdfmux', ...])。两者使用相同的流水线,但前者不会为每个文件单独启动进程,还能处理非 ASCII 文件名:
import pdfmux
from pathlib import Path
# Batch extract — yields (path, result) tuples as each PDF completes.
pdfs = list(Path("./inbox").glob("*.pdf"))
for path, result in pdfmux.batch_extract(pdfs, quality="standard"):
if isinstance(result, Exception):
print(f"FAILED {path.name}: {result}")
continue
if result.confidence < 0.50:
print(f"REVIEW {path.name} ({result.confidence:.2f})")
else:
print(f"OK {path.name} ({result.confidence:.2f})")
# Single-file helpers.
text = pdfmux.extract_text("report.pdf") # markdown string
data = pdfmux.extract_json("report.pdf") # locked schema dict
chunks = pdfmux.chunk("report.pdf", max_tokens=500) # RAG-ready chunks
不要自行用 pypdf/pdfplumber 为 pdfmux 添加回退方案。 pdfmux 已经会逐页通过 PyMuPDF → RapidOCR → 视觉大模型进行路由。PyMuPDF 能处理 pypdf 会拒绝的格式错误 PDF(“Stream has ended unexpectedly”),因此下游的 pypdf 回退会让原本可恢复的 PDF 变成失败。请信任路由器,并检查结果中的置信度分数。
验证任何提取结果
pdfmux verify 会根据源 PDF 审核任何提取引擎的输出,并告诉你哪些页面被静默丢弃——不仅限于 pdfmux 自身的提取结果。将 Reducto、Mistral OCR、LlamaParse、Docling 或自研解析器的输出交给它,它就会使用 pdfmux 自身的审核流程重新推导源文本,将提取结果与之对齐,并为每个页面评分。
它能捕获的故障: 源文件中某个页面包含真实文本,但引擎却未返回任何内容,同时还报告成功。这种“静默丢弃”正是会污染 RAG 索引、却不会在日志中产生任何错误的故障。
# Certify pdfmux's own extraction of a document
pdfmux verify --source report.pdf --engine pdfmux
# Certify ANOTHER engine's output (JSON / Markdown / text)
pdfmux verify --source report.pdf --extracted reducto.json --engine-name reducto
# Batch a whole directory — the "M pages silently dropped across N docs" report
pdfmux verify --source ./pdfs/ --extracted ./engine-outputs/ -o certification.json
# CI gate: exit non-zero unless the overall verdict is PASS
pdfmux verify --source report.pdf --extracted out.json --strict
每次运行都会输出 PASS / REVIEW / FAIL 判定、整体置信度和覆盖率;发现静默丢弃的页面时,还会列出页码:
pdfmux verify — report.pdf · engine: reducto
FAIL confidence 71% · coverage 68%
reducto: FAIL; 3 page(s) SILENTLY DROPPED (pages 7, 12, 31); overall
confidence 71%, coverage 68% across 40 page(s).
❌ 3 page(s) SILENTLY DROPPED: 7, 12, 31
每个页面都会提供判定(pass / review / fail)、置信度、覆盖率、对齐度、幻觉风险以及表格和标题完整性。批处理模式会将结果汇总为一行:“M 份文档中有 N 个页面被静默丢弃”——你可以用这份报告检查自有的 100 份 PDF,找出流水线中已经存在的静默故障。
适用于任何引擎的输出
--extracted 接受 JSON、Markdown 或纯文本(--extracted-format auto | json | markdown | text)。如果提取结果呈现真实的逐页结构,pdfmux 会逐页比较;如果结果是一个整体内容块,则会回退到内容存在性检查,因此绝不会因分页不匹配而虚构“静默丢失”。
Python API
from pdfmux import verify_extraction, verify_batch
# Single document → a CertificationManifest
manifest = verify_extraction("report.pdf", "reducto.json", engine="reducto")
print(manifest.verdict) # "PASS" | "REVIEW" | "FAIL"
print(manifest.silent_drops) # e.g. (7, 12, 31) — 1-indexed page numbers
print(manifest.coverage) # 0.0–1.0
# Many documents → a BatchCertification ("M pages dropped across N docs")
batch = verify_batch([("a.pdf", "a.json"), ("b.pdf", "b.json")], engine="llamaparse")
print(batch.total_silent_drops, "pages dropped across", batch.doc_count, "docs")
每份清单都包含一个防篡改的 SHA-256 内容签名,该签名基于其规范化正文生成;同时还嵌入了一份如实列出的局限性说明:该认证器基于词法,而非语言学——它能检测缺失和乱码内容,但无法判断释义或翻译是否忠实。
MCP
verify_extraction 作为 MCP 工具公开(第 7 个工具——参见 MCP 服务),因此 Agent 可以在提取内容的同一会话中认证引擎的输出。
免费、MIT、专利无争议
Certify Anything 仅复用 pdfmux 随附的 MIT 审计层。它不包含也不需要申请中的专利决策追踪方法——该方法仍仅适用于 pdfmux Cloud/Pro。pip install pdfmux 可免费提供完整的 verify 命令。
完整参考:docs/CERTIFY-ANYTHING.md。
需要向他人证明时
本地安装可以审计提取结果,但无法对其进行证明——任何人都可以伪造本地签署的内容。pdfmux Cloud 会返回一份针对提取结果的 Ed25519 签名清单:审计人员无需账号,也无需信任 pdfmux,即可根据公开发布的公钥离线验证。
pdfmux verify-manifest manifest.json # free, MIT, offline — no account
验证永久免费且保持开源;只有生成需要付费(每月 $49)。这种不对称是有意为之——你永远不该需要我们的许可才能核查我们的工作。
免费工具,无需注册:app.pdfmux.com/audit——上传 PDF,查看当前提取器静默漏掉了哪些页面。实测准确率(及其盲点)已发布在 pdfmux-bench。
架构
┌─────────────────────────────┐
│ Segment Detector │
│ text / tables / images / │
│ formulas / headers per page │
└─────────────┬───────────────┘
│
┌────────────────────────────────────────┐
│ Router Engine │
│ │
│ economy ── balanced ── premium │
│ (minimize $) (default) (max quality)│
│ budget caps: --budget 0.50 │
└────────────────────┬───────────────────┘
│
┌──────────┬──────────┬────────┴────────┬──────────┐
│ │ │ │ │
PyMuPDF OpenData RapidOCR Docling LLM
digital Loader scanned tables (BYOK)
0.01s/pg complex CPU-only 97.9% any provider
layouts TEDS
│ │ │ │ │
└──────────┴──────────┴────────┬────────┴──────────┘
│
┌────────────────────────────────────────┐
│ Quality Auditor │
│ │
│ 4-signal dynamic confidence scoring │
│ per-page: good / bad / empty │
│ if bad -> re-extract with next backend│
└────────────────────┬───────────────────┘
│
┌────────────────────────────────────────┐
│ Output Pipeline │
│ │
│ heading injection (font-size analysis)│
│ table extraction + normalization │
│ text cleanup + merge │
│ confidence score (honest, not inflated)│
└────────────────────────────────────────┘
关键设计决策
- 路由器,而非提取器。 pdfmux 不与 PyMuPDF 或 Docling 竞争,而是为每一页挑选最佳工具。
- Agent 多轮处理。 提取内容、审计置信度,再用更强的后端重新提取失败内容。较差的页面会自动重试。
- 片段级检测。 路由之前,先按内容类型(文本、表格、图像、公式、页眉)对每一页进行分类。
- 4 信号置信度。 根据字符密度、OCR 噪声比例、表格完整性和标题结构动态评估质量,而非使用硬编码阈值。
- 文档缓存。 每份 PDF 只打开一次,而不是每个提取器打开一次。整个处理流水线共享缓存。
- 数据飞轮。 本地遥测会追踪不同文档类型中表现最佳的提取器。路由效果会随着使用不断提升。
功能
| 功能 | 作用 | 命令 |
|---|---|---|
| 零配置提取 | 自动路由到最佳后端 | pdfmux convert file.pdf |
| RAG 分块 | 按章节切分,并估算令牌数 | pdfmux convert file.pdf --chunk --max-tokens 500 |
| 成本模式 | economy / balanced / premium,支持预算上限 | pdfmux convert file.pdf --mode economy --budget 0.50 |
| Schema 提取 | 内置 5 种预设(invoice、receipt、contract、resume、paper) | pdfmux convert file.pdf --schema invoice |
| 配置方案 | 保存并重用配置;内置 invoices/receipts/papers/contracts/bulk-rag 配置 | pdfmux convert file.pdf --profile invoices |
| 自带密钥的 LLM | Gemini、Gemma 3、Claude、GPT-4o、Ollama、Mistral,以及任何兼容 OpenAI 的 API | pdfmux convert file.pdf --llm-provider claude |
| 成本估算 | 运行前预测开销 | pdfmux estimate file.pdf --llm-provider gemini |
| 流式输出 | 长文档按页输出 NDJSON 事件 | pdfmux stream file.pdf |
| 智能缓存 | 基于哈希键的结果缓存,TTL 为 30 天,LRU 容量为 1 GB | pdfmux convert file.pdf(自动),使用 --no-cache 跳过 |
| 监视模式 | 自动转换添加到文件夹中的任何 PDF | pdfmux watch ./inbox/ |
| 差异对比 | 比较两次提取结果 | pdfmux diff a.pdf b.pdf |
| 基准测试 | 根据基准数据评估所有已安装的提取器 | pdfmux benchmark |
| 诊断 | 显示已安装的后端、覆盖缺口和建议 | pdfmux doctor |
| MCP 服务 | AI Agent 通过 stdio 或 HTTP 读取 PDF | pdfmux serve |
| 批量处理 | 转换整个目录 | pdfmux convert ./docs/ |
| 页面级流式 API | 以有界内存逐页处理大型文件 | for page in ext.extract("500pg.pdf") |
| 带退避的重试 | 每个 LLM 提供商都会自动进行指数退避重试,并遵循 Retry-After | (内置) |
CLI 参考
pdfmux convert
pdfmux convert <file-or-dir> [options]
Options:
-o, --output PATH Output file or directory
-f, --format FORMAT markdown | json | csv | llm (default: markdown)
-q, --quality QUALITY fast | standard | high (default: standard)
-s, --schema SCHEMA JSON schema file or preset (invoice, receipt, contract, resume, paper)
--chunk Output RAG-ready chunks
--max-tokens N Max tokens per chunk (default: 500)
--mode MODE economy | balanced | premium (default: balanced)
--budget AMOUNT Max spend per document in USD
--llm-provider PROVIDER LLM backend: gemini | claude | openai | ollama
--confidence Include confidence score in output
--stdout Print to stdout instead of file
pdfmux serve
启动 MCP 服务,以便与 AI Agent 集成。
pdfmux serve # stdio mode (Claude Desktop, Cursor)
pdfmux serve --http 8080 # HTTP mode
pdfmux doctor
pdfmux doctor
# ┌──────────────────┬─────────────┬─────────┬──────────────────────────────────┐
# │ Extractor │ Status │ Version │ Install │
# ├──────────────────┼─────────────┼─────────┼──────────────────────────────────┤
# │ PyMuPDF │ installed │ 1.25.3 │ │
# │ OpenDataLoader │ installed │ 0.3.1 │ │
# │ RapidOCR │ installed │ 3.0.6 │ │
# │ Docling │ missing │ -- │ pip install pdfmux[tables] │
# │ Surya │ missing │ -- │ pip install pdfmux[ocr-heavy] │
# │ LLM (Gemini) │ configured │ -- │ GEMINI_API_KEY set │
# └──────────────────┴─────────────┴─────────┴──────────────────────────────────┘
pdfmux benchmark
pdfmux benchmark report.pdf
# ┌──────────────────┬────────┬────────────┬─────────────┬──────────────────────┐
# │ Extractor │ Time │ Confidence │ Output │ Status │
# ├──────────────────┼────────┼────────────┼─────────────┼──────────────────────┤
# │ PyMuPDF │ 0.02s │ 95% │ 3,241 chars │ all pages good │
# │ Multi-pass │ 0.03s │ 95% │ 3,241 chars │ all pages good │
# │ RapidOCR │ 4.20s │ 88% │ 2,891 chars │ ok │
# │ OpenDataLoader │ 0.12s │ 97% │ 3,310 chars │ best │
# └──────────────────┴────────┴────────────┴─────────────┴──────────────────────┘
pdfmux estimate
在处理前预测开销(以及将运行哪些后端)。
pdfmux estimate report.pdf --quality high --llm-provider gemini
# Pages : 47
# Extractors : pymupdf4llm + gemini-2.5-flash on 9 pages
# Estimated : $0.0234
# Cache hit? : no (first run for this file)
pdfmux stream
页面处理完成后输出 NDJSON 事件,适用于超长 PDF 和实时 UI。
pdfmux stream long.pdf --quality high
# {"event":"classified","page_count":312,"plan":"pymupdf+gemini-fallback"}
# {"event":"page","page_num":0,"confidence":0.97,"chars":1842}
# {"event":"page","page_num":1,"confidence":0.92,"chars":1611,"ocr":true}
# ...
# {"event":"complete","confidence":0.94,"cost_usd":0.0712}
pdfmux watch
自动转换放入目录中的任何 PDF。持续运行,直到按下 Ctrl+C。
pdfmux watch ./inbox/ -o ./output/ --profile bulk-rag
pdfmux diff
并排比较提取结果(质量、内容、成本)。
pdfmux diff a.pdf b.pdf --quality standard
pdfmux profiles
已保存的配置位于 ~/.config/pdfmux/profiles.yaml。内置常见类型的配置;也可为项目默认设置保存自定义配置。
pdfmux profiles list
# invoices quality=standard, schema=invoice, format=json
# receipts quality=fast, schema=receipt, format=json
# papers quality=high, chunk=true, max_tokens=500
# contracts quality=high, schema=contract
# bulk-rag quality=standard, format=llm, chunk=true
pdfmux profiles show invoices
pdfmux profiles save my-default --quality high --format llm --chunk
pdfmux profiles delete my-default
# use a profile when converting
pdfmux convert file.pdf --profile invoices
Python API
文本提取
import pdfmux
text = pdfmux.extract_text("report.pdf") # -> str (markdown)
text = pdfmux.extract_text("report.pdf", quality="fast") # PyMuPDF only, instant
text = pdfmux.extract_text("report.pdf", quality="high") # LLM-assisted
结构化提取
data = pdfmux.extract_json("report.pdf")
# data["page_count"] -> 12
# data["confidence"] -> 0.91
# data["ocr_pages"] -> [2, 5, 8]
# data["pages"][0]["key_values"] -> [{"key": "Date", "value": "2026-02-28"}]
# data["pages"][0]["tables"] -> [{"headers": [...], "rows": [...]}]
RAG 分块
chunks = pdfmux.chunk("report.pdf", max_tokens=500)
for c in chunks:
print(f"{c['title']}: {c['tokens']} tokens (pages {c['page_start']}-{c['page_end']})")
Schema 引导的提取
data = pdfmux.extract_json("invoice.pdf", schema="invoice")
# Uses built-in invoice preset: extracts date, vendor, line items, totals
# Also accepts a path to a custom JSON Schema file
流式处理(有界内存)
from pdfmux.extractors import get_extractor
ext = get_extractor("fast")
for page in ext.extract("large-500-pages.pdf"): # Iterator[PageResult]
process(page.text) # constant memory, even on 500-page PDFs
类型和错误
from pdfmux import (
# Enums
Quality, # FAST, STANDARD, HIGH
OutputFormat, # MARKDOWN, JSON, CSV, LLM
PageQuality, # GOOD, BAD, EMPTY
# Data objects (frozen dataclasses)
PageResult, # page: text, page_num, confidence, quality, extractor
DocumentResult, # document: pages, source, confidence, extractor_used
Chunk, # chunk: title, text, page_start, page_end, tokens
# Errors
PdfmuxError, # base -- catch this for all pdfmux errors
FileError, # file not found, unreadable, not a PDF
ExtractionError, # extraction failed
ExtractorNotAvailable,# requested backend not installed
FormatError, # invalid output format
AuditError, # audit could not complete
)
框架集成
LangChain
pip install langchain-pdfmux
from langchain_pdfmux import PDFMuxLoader
loader = PDFMuxLoader("report.pdf", quality="standard")
docs = loader.load() # -> list[Document] with confidence metadata
LlamaIndex
pip install llama-index-readers-pdfmux
from llama_index.readers.pdfmux import PDFMuxReader
reader = PDFMuxReader(quality="standard")
docs = reader.load_data("report.pdf") # -> list[Document]
MCP 服务(AI Agent)
已收录于 mcpservers.org。一行即可完成设置:
{
"mcpServers": {
"pdfmux": {
"command": "npx",
"args": ["-y", "pdfmux-mcp"]
}
}
}
也可通过 Claude Code 设置:
claude mcp add pdfmux -- npx -y pdfmux-mcp
提供的工具:convert_pdf、analyze_pdf、extract_structured、
extract_streaming、get_pdf_metadata、batch_convert。
BYOK LLM 配置
pdfmux 只需 5 行 YAML 即可支持任意 LLM。使用你自己的密钥——除非你进行配置,否则任何数据都不会离开你的设备。
# ~/.pdfmux/llm.yaml
provider: claude # gemini | claude | openai | ollama | any OpenAI-compatible
model: claude-sonnet-4-20250514
api_key: ${ANTHROPIC_API_KEY}
base_url: https://api.anthropic.com # optional, for custom endpoints
max_cost_per_page: 0.02 # budget cap
支持的提供商:
| 提供商 | 模型 | 本地? | 费用 |
|---|---|---|---|
| Gemini | 2.5 Flash、2.5 Pro | 否 | 约 $0.01/页 |
| Gemma 3 | 27B IT、12B IT(非常适合阿拉伯语) | 否(通过 Gemini 密钥) | 约 $0.0002/页 |
| Claude | Sonnet、Opus | 否 | 约 $0.015/页 |
| GPT-4o | GPT-4o、GPT-4o-mini | 否 | 约 $0.01/页 |
| Mistral | mistral-ocr-latest | 否 | $0.002/页 |
| Ollama | 任意本地模型 | 是 | 免费 |
| 自定义 | 任意兼容 OpenAI 的 API | 可配置 | 不等 |
每个提供商的 extract_page() 都由 @with_retry(max_attempts=3, backoff_base=2.0) 包装,会遵循 429 响应中的 Retry-After 标头,并跳过认证失败后的重试,确保密钥无效时能快速失败。
阿拉伯语和 RTL 支持
pdfmux 原生支持阿拉伯语、波斯语、乌尔都语和希伯来语。 默认情况下,系统会对每个 PDF 执行 RTL 检测,并对 PyMuPDF 提取的页面应用 Unicode 双向算法,确保以从左到右顺序存储的字形按正确的阅读顺序显示。
# Default install — already includes python-bidi for RTL reordering
pip install pdfmux
# Recommended for Arabic-heavy docs — adds Gemma vision OCR
# (Gemma speaks the OpenAI protocol, so it needs the openai SDK)
pip install "pdfmux[llm-openai]"
# One credential covers Gemma + Gemini (same Google endpoint)
export GEMINI_API_KEY=...
自动执行的操作:
pdfmux convert会检测阿拉伯语内容,并将阿拉伯字符占比 >5% 的页面交由支持阿拉伯语的提取器链处理。- PyMuPDF、RapidOCR 和 Docling 的输出会经过 Bidi 算法后处理——Markdown 标题(
#)和管道符表格行会保留结构,仅调整内部文本的顺序。 - 只要任意页面包含阿拉伯文字,
DocumentResult.has_arabic就会设为True。
需要选择启用的功能:
- 视觉 LLM 提取。设置
--llm-provider gemma(或任意视觉提供商),即可将阿拉伯语页面交由 Gemma 而非 PyMuPDF 处理。 - 激进规范化(移除 Tatweel、统一 Alef/Yeh、去除 Tashkeel)——如果你需要用于搜索或嵌入的规范化输出,请对提取的字符串调用
pdfmux.arabic.normalize_arabic(text)。
from pdfmux.arabic import (
is_arabic_text,
is_rtl_dominant,
fix_bidi_order,
normalize_arabic,
)
text = "مرحبا بالعالم"
assert is_arabic_text(text)
assert is_rtl_dominant(text)
# Fix glyph order from PyMuPDF / OCR engines
visual = fix_bidi_order(text)
# Canonicalize for indexing — strip Tatweel, unify Alef variants, drop diacritics
indexable = normalize_arabic("أَحْمَدْ") # → "احمد"
实证:真实客户批次
我们在 433 份真实客户文档上测试了 pdfmux——包括技术数据表和安全数据表,既有数字文档,也有扫描件,其中一些存在编码损坏。先用朴素方式运行(早期版本的 pdfmux CLI 在子进程中运行,使用 pypdf 作为回退方案,不进行 OCR),结果处理流程 悄无声息地丢弃了 16 份文档——其中 11 份连一条日志都没有。 我们自己的工具在它承诺解决的关键问题上失效了。
使用逐页审计和预算控制的 OCR 级联重建后:433 份全部处理成功,没有任何静默失败。 每个无法恢复的页面都会被标记,而非丢弃。
(eval/ 中还附带了一小组内部置信度校准数据——它用于防止置信度门控出现回归,并非竞争性基准;请参阅 eval/README.md。)
基准测试
在 opendataloader-bench 上——涵盖 200 份真实 PDF(财务申报文件、学术论文、法律合同、政府报告)——pdfmux 的总分为 0.903,在测评的 8 个引擎中排名第 2,仅次于 opendataloader-hybrid(0.909)。于 2026-07-16 重新运行(复现方法见下文)。
| 排名 | 引擎 | 总分 | 阅读顺序 | 表格(TEDS) | 许可证 | GPU |
|---|---|---|---|---|---|---|
| 1 | opendataloader-hybrid | 0.909 | 0.935 | 0.928 | Apache-2.0 | 否 |
| 2 | pdfmux | 0.903 | 0.920 | 0.911 | MIT | 否 |
| 3 | Docling | 0.877 | 0.900 | 0.887 | MIT | 可选 |
| 4 | marker | 0.861 | 0.890 | 0.808 | 免费 | GPU |
| 5 | mineru | 0.831 | 0.857 | 0.873 | 免费 | GPU |
完整的逐文档得分: 200 份 PDF 正面对比 · 方法说明:最佳 PDF 提取库:基准测试。
智能结果缓存
重新运行相同的提取任务可即时完成。pdfmux 会对每个输入 PDF 计算哈希值(SHA-256),并根据 (file_hash, quality, format, schema) 为结果建立键。缓存文件保存在 ~/.cache/pdfmux/results/ 下,30 天后过期,并会在达到 1 GB 时按 LRU 策略淘汰。
pdfmux convert big-report.pdf # first run: 14.2s
pdfmux convert big-report.pdf # cache hit: 0.05s
pdfmux convert big-report.pdf --no-cache # bypass cache (still writes back)
pdfmux convert big-report.pdf --clear-cache # purge and re-run
缓存还可以加快切换 --profile、--schema 和 --format 的速度——每种组合都会单独建立键,因此对同一文档首次提取后,便可免费在 Markdown 和 JSON 之间切换。
置信度评分
每个结果都包含一个基于 4 项信号的置信度评分:
- 95-100% —— 数字文本清晰,可完整提取
- 80-95% —— 提取效果良好,部分页面存在少量 OCR 噪声
- 50-80% —— 提取不完整,部分页面无法恢复
- <50% —— 大量内容缺失,并附有警告
当置信度低于 80% 时,pdfmux 会准确告知出错原因以及修复方法:
Page 4: 32% confidence. 0 chars extracted from image-heavy page.
-> Install pdfmux[ocr] for RapidOCR support on 6 image-heavy pages.
成本模式
| 模式 | 行为 | 典型成本 |
|---|---|---|
| economy | 仅使用基于规则的后端。不调用大模型。 | $0/页 |
| balanced | 仅对基于规则的提取失败的页面调用大模型。 | 平均约 $0.002/页 |
| premium | 对每一页都调用大模型,以获得最高质量。 | $0.01/页 |
设置硬性预算上限:--budget 0.50 会在单个文档的支出达到 $0.50 时停止调用大模型。
为什么选择 pdfmux?
pdfmux 不只是另一个 PDF 提取器。它是一个编排层,会为每页选择合适的提取器、验证结果,并在失败时重试。
| 工具 | 擅长处理 | 局限 |
|---|---|---|
| PyMuPDF | 快速提取数字文本 | 无法处理扫描件或图像布局 |
| Docling | 表格(准确率 97.9%) | 处理非表格文档时速度较慢 |
| Marker | 学术论文的神经网络提取 | 需要 GPU 才能获得较快速度;用于数字 PDF 时大材小用 |
| Mistral OCR | 表格(TEDS 96.6%),$0.002/页 | 仅提供云端 API |
| Unstructured | 企业平台 | 设置复杂,并提供付费层级 |
| LlamaParse | 原生云端 | 需要 API 密钥,无法本地运行 |
| Reducto | 高准确率 | $0.015/页,闭源 |
| pdfmux | 编排以上所有工具 | 按页路由、审计并重新提取 |
开源的 Reducto 替代方案:在其他地方每页收费 $0.015 的功能,使用 pdfmux 的基于规则的后端即可免费实现;或通过 BYOK 大模型回退方案,平均每页约 $0.002。
开发
git clone https://github.com/NameetP/pdfmux.git
cd pdfmux
python3.12 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # 659 tests
ruff check src/ tests/
ruff format src/ tests/
贡献
- Fork 此代码库
- 创建分支(
git checkout -b feature/your-feature) - 为新功能编写测试
- 确保
pytest和ruff check均通过 - 提交 PR
许可证
本代码库中的 pdfmux 库和 MCP 服务采用 MIT 许可证——可免费用于任何用途,且每个发布版本均继续采用 MIT 许可证。
按置信度预算的 decision-trace 方法(持久化的逐页决策跟踪,保留被拒绝的候选项,并采用单调修复保护机制)正在申请专利(美国临时专利申请号 64/106,302),并根据单独的商业许可证保留给 pdfmux Cloud/Pro 使用——它不属于 MIT 许可授权范围。请参阅 LICENSING.md 和 NOTICE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。