pdfmux
Enrutador inteligente de PDF a Markdown que selecciona el mejor extractor por página, audita la calidad de salida y reextrae fallos automáticamente. Puntuación de confianza, soporte de LLM con clave propia, fragmentación para RAG.
Documentación
pdfmux
Extracción de PDF auto-reparable que marca las páginas que no puede leer en lugar de descartarlas — y ahora certifica la salida de cualquier extractor para detección de caídas silenciosas. Alternativa de código abierto a LlamaParse para pipelines de RAG, servidor MCP para Claude Desktop, cargadores de LangChain + LlamaIndex.
pdfmux extrae PDFs y verifica su propio trabajo — y ahora certifica el de cualquier extractor, indicándote qué páginas descartó silenciosamente. Gratis, MIT. Método con patente pendiente.
pip install pdfmux.
Dos trabajos, una herramienta:
- Extracción auto-reparable. El único extractor de PDF que audita su propia salida. Detecta páginas en blanco, columnas desordenadas, tablas rotas — las re-extrae con un backend más potente, y marca lo que aún no puede leer en lugar de descartarlo silenciosamente. Así tu LLM recibe datos limpios, no basura silenciosa. Enruta cada página al mejor de 7 backends de extracción integrados + respaldo LLM BYOK (Gemini / Claude / GPT-4o / Ollama). Una CLI. Una API. Cero configuración.
- Certify Anything — nuevo en v1.8.1.
pdfmux verifyaudita la salida de cualquier motor de extracción contra el PDF fuente — Reducto, Mistral OCR, LlamaParse, Docling, tu parser interno — y te dice qué páginas descartó silenciosamente. Gratis, MIT, sin patentes.
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)
Instalación
pip install pdfmux
Eso maneja PDFs digitales. Para cualquier lote del mundo real, instala también pdfmux[ocr] — casi todo directorio de PDFs tiene al menos un escaneo, y sin OCR esas páginas devuelven texto vacío:
pip install "pdfmux[ocr]" # ⭐ recommended — RapidOCR for scanned pages (~200MB, CPU)
Otros backends, por tipo de documento:
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
Requiere Python 3.11+.
Inicio Rápido
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
Para procesamiento por lotes, usa batch_extract() — no un bucle subprocess.run(['pdfmux', ...]). Mismo pipeline, sin generación de procesos por archivo, maneja nombres de archivo no 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
No envuelvas pdfmux con tu propio respaldo pypdf/pdfplumber. pdfmux ya enruta por página a través de PyMuPDF → RapidOCR → LLM de visión. PyMuPDF tolera PDFs malformados que pypdf rechaza ("Stream has ended unexpectedly"), por lo que un respaldo pypdf posterior convierte PDFs recuperables en fallos. Confía en el enrutador; revisa la puntuación de confianza en el resultado.
Certify Anything
pdfmux verify audita la salida de cualquier motor de extracción contra el PDF fuente y te dice qué páginas descartó silenciosamente — no solo la extracción propia de pdfmux. Apúntalo a la salida de Reducto, Mistral OCR, LlamaParse, Docling, o tu parser interno y re-deriva el texto fuente con el pase de auditoría propio de pdfmux, alinea la extracción con él, y puntúa cada página.
El fallo que detecta: una página donde la fuente tiene texto real pero el motor no devolvió nada — mientras reporta éxito. Esa "caída silenciosa" es exactamente el fallo que envenena un índice RAG sin un solo error en los registros.
# 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
Cada ejecución imprime un veredicto PASS / REVIEW / FAIL, confianza y cobertura generales, y — cuando los encuentra — las páginas descartadas silenciosamente por número:
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
Por página obtienes un veredicto (pass / review / fail), confianza, cobertura, alineación, riesgo de alucinación, e integridad de tablas/encabezados. El modo por lotes resume eso en una sola línea "N páginas descartadas silenciosamente en M documentos" — el informe que ejecutas en 100 de tus propios PDFs para encontrar los fallos silenciosos ya presentes en tu pipeline.
Funciona con la salida de cualquier motor
--extracted acepta JSON, Markdown o texto plano (--extracted-format auto | json | markdown | text). Cuando la extracción expone estructura real por página, pdfmux compara página por página; cuando es un solo bloque, recurre a comprobaciones de presencia de contenido para nunca fabricar una "caída silenciosa" por un desajuste de paginación.
API de Python
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")
Cada manifiesto lleva una firma de contenido SHA-256 a prueba de manipulaciones sobre su cuerpo canónico y una lista de limitaciones honesta integrada: el certificador es léxico, no lingüístico — detecta contenido faltante y distorsionado, no paráfrasis fieles ni traducciones.
MCP
verify_extraction se expone como herramienta MCP (la 7ª — ver Servidor MCP), para que un agente pueda certificar la salida de un motor en la misma sesión en la que extrae.
Gratis, MIT, sin patentes
Certify Anything reutiliza solo la capa de auditoría MIT incluida en pdfmux. No incluye, ni requiere, el método de traza de decisión con patente pendiente — eso permanece en pdfmux Cloud/Pro. pip install pdfmux te da el comando completo verify sin costo.
Referencia completa: docs/CERTIFY-ANYTHING.md.
Cuando necesitas probarlo a otra persona
Una instalación local puede auditar una extracción, pero no puede atestiguar una — cualquier cosa que firme, cualquiera podría falsificar. pdfmux Cloud devuelve un manifiesto firmado con Ed25519 sobre la extracción: tu auditor lo verifica sin conexión, contra una clave pública publicada, sin cuenta y sin confiar en pdfmux.
pdfmux verify-manifest manifest.json # free, MIT, offline — no account
La verificación es gratuita y abierta para siempre; solo la generación es de pago ($49/mes). Esa asimetría es deliberada — nunca deberías necesitar nuestro permiso para comprobar nuestro trabajo.
Herramienta gratuita, sin registro: app.pdfmux.com/audit — sube un PDF y ve qué páginas descartó silenciosamente tu extractor actual. Precisión medida (y sus puntos ciegos) publicada en pdfmux-bench.
Arquitectura
┌─────────────────────────────┐
│ 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)│
└────────────────────────────────────────┘
Decisiones clave de diseño
- Enrutador, no extractor. pdfmux no compite con PyMuPDF o Docling. Elige el mejor por página.
- Multi-pase agéntico. Extrae, audita confianza, re-extrae fallos con un backend más potente. Las páginas malas se reintentan automáticamente.
- Detección a nivel de segmento. Cada página se clasifica por tipo de contenido (texto, tablas, imágenes, fórmulas, encabezados) antes de enrutar.
- Confianza de 4 señales. Puntuación de calidad dinámica a partir de densidad de caracteres, ratio de ruido OCR, integridad de tablas y estructura de encabezados. No umbrales fijos.
- Caché de documentos. Cada PDF se abre una vez, no una vez por extractor. Compartido en todo el pipeline.
- Volante de datos. Telemetría local rastrea qué extractores ganan por tipo de documento. El enrutamiento mejora con el uso.
Características
| Característica | Qué hace | Comando |
|---|---|---|
| Extracción sin configuración | Enruta automáticamente al mejor backend | pdfmux convert file.pdf |
| Fragmentación RAG | Fragmentos conscientes de sección con estimaciones de tokens | pdfmux convert file.pdf --chunk --max-tokens 500 |
| Modos de costo | economy / balanced / premium con límites de presupuesto | pdfmux convert file.pdf --mode economy --budget 0.50 |
| Extracción de esquema | 5 presets integrados (factura, recibo, contrato, currículum, artículo) | pdfmux convert file.pdf --schema invoice |
| Perfiles | Guarda y reutiliza configuración; integrados para facturas/recibos/artículos/contratos/bulk-rag | pdfmux convert file.pdf --profile invoices |
| LLM BYOK | Gemini, Gemma 3, Claude, GPT-4o, Ollama, Mistral, cualquier API compatible con OpenAI | pdfmux convert file.pdf --llm-provider claude |
| Estimación de costo | Predice el gasto antes de ejecutar | pdfmux estimate file.pdf --llm-provider gemini |
| Salida en streaming | Eventos NDJSON página por página para documentos largos | pdfmux stream file.pdf |
| Caché inteligente | Caché de resultados con clave hash, TTL de 30 días, LRU de 1 GB | pdfmux convert file.pdf (auto), --no-cache para omitir |
| Modo vigilancia | Convierte automáticamente cualquier PDF añadido a una carpeta | pdfmux watch ./inbox/ |
| Diff | Compara dos extracciones | pdfmux diff a.pdf b.pdf |
| Benchmark | Evalúa todos los extractores instalados contra la verdad de referencia | pdfmux benchmark |
| Doctor | Muestra backends instalados, brechas de cobertura, recomendaciones | pdfmux doctor |
| Servidor MCP | Agentes de IA leen PDFs vía stdio o HTTP | pdfmux serve |
| Procesamiento por lotes | Convierte directorios completos | pdfmux convert ./docs/ |
| API de streaming a nivel de página | Iteración de páginas con memoria limitada para archivos grandes | for page in ext.extract("500pg.pdf") |
| Reintento con retroceso | Cada proveedor LLM reintenta automáticamente con retroceso exponencial + Retry-After | (integrado) |
Referencia de 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
Inicia el servidor MCP para integración con agentes de IA.
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
Predice el gasto (y qué backends se ejecutarán) antes de procesar.
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
Emite eventos NDJSON a medida que se completan las páginas — útil para PDFs muy largos y UIs en vivo.
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
Convierte automáticamente cualquier PDF que llegue a un directorio. Permanece hasta Ctrl+C.
pdfmux watch ./inbox/ -o ./output/ --profile bulk-rag
pdfmux diff
Comparación de extracción lado a lado (calidad, contenido, costo).
pdfmux diff a.pdf b.pdf --quality standard
pdfmux profiles
Configuraciones guardadas en ~/.config/pdfmux/profiles.yaml. Los integrados se envían para las formas comunes; guarda las tuyas para valores predeterminados del proyecto.
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
API de Python
Extracción de texto
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
Extracción estructurada
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": [...]}]
Fragmentación 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']})")
Extracción guiada por esquema
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
Streaming (memoria limitada)
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
Tipos y errores
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
)
Integraciones con frameworks
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]
Servidor MCP (Agentes de IA)
Listado en mcpservers.org. Configuración en una línea:
{
"mcpServers": {
"pdfmux": {
"command": "npx",
"args": ["-y", "pdfmux-mcp"]
}
}
}
O vía Claude Code:
claude mcp add pdfmux -- npx -y pdfmux-mcp
Herramientas expuestas: convert_pdf, analyze_pdf, extract_structured,
extract_streaming, get_pdf_metadata, batch_convert.
Configuración de LLM BYOK
pdfmux soporta cualquier LLM mediante 5 líneas de YAML. Trae tus propias claves -- nada sale de tu máquina a menos que lo configures.
# ~/.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
Proveedores soportados:
| Proveedor | Modelos | ¿Local? | Costo |
|---|---|---|---|
| Gemini | 2.5 Flash, 2.5 Pro | No | ~$0.01/página |
| Gemma 3 | 27B IT, 12B IT (excelente para árabe) | No (vía clave de Gemini) | ~$0.0002/página |
| Claude | Sonnet, Opus | No | ~$0.015/página |
| GPT-4o | GPT-4o, GPT-4o-mini | No | ~$0.01/página |
| Mistral | mistral-ocr-latest | No | $0.002/página |
| Ollama | Cualquier modelo local | Sí | Gratis |
| Personalizado | Cualquier API compatible con OpenAI | Configurable | Varía |
El extract_page() de cada proveedor está envuelto en @with_retry(max_attempts=3, backoff_base=2.0), which honors Retry-After en respuestas 429 y omite
reintentos en fallos de autenticación para que una clave mala falle rápido.
Soporte para árabe y RTL
pdfmux incluye soporte de primera clase para árabe, persa, urdu y hebreo. De serie, la detección RTL se ejecuta en cada PDF y las páginas extraídas con PyMuPDF pasan por el Algoritmo Bidireccional Unicode para que los glifos almacenados en orden de izquierda a derecha se rendericen en el orden de lectura correcto.
# 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=...
Qué ocurre automáticamente:
pdfmux convertdetecta contenido árabe y enruta páginas con >5% caracteres árabes a través de la cadena de extractores conscientes del árabe.- Las salidas de PyMuPDF, RapidOCR y Docling se post-procesan con el
algoritmo Bidi — los encabezados de markdown (
#) y las filas de tablas con tuberías preservan la estructura, solo se reordena el texto interno. DocumentResult.has_arabicse establece aTruecuando cualquier página contiene escritura árabe.
Qué requiere opt-in:
- Extracción con LLM de visión. Configura
--llm-provider gemma(o cualquier proveedor de visión) para enrutar páginas árabes a través de Gemma en lugar de PyMuPDF. - Normalización agresiva (eliminación de Tatweel, unificación de Alef/Yeh,
eliminación de Tashkeel) — llama a
pdfmux.arabic.normalize_arabic(text)sobre las cadenas extraídas si necesitas salida canonizada para búsqueda o embeddings.
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("أَحْمَدْ") # → "احمد"
Prueba: un lote real de clientes
Medimos pdfmux en 433 documentos reales de clientes — hojas de datos técnicos y de seguridad, mezcla de digitales y escaneados, algunos con codificación corrupta. Ejecutado primero de forma ingenua (una CLI temprana de pdfmux en un subproceso, respaldo pypdf, sin OCR), el pipeline descartó silenciosamente 16 documentos — 11 de ellos sin ninguna línea de registro. Ese era nuestro propio fallando exactamente en lo que promete.
Reconstruido con la auditoría por página + cascada OCR con presupuesto: 433 de 433 procesados, cero fallos silenciosos. Cada página irrecuperable se marca, no se descarta.
(Un pequeño conjunto interno de calibración de confianza también se incluye bajo eval/ — es una salvaguarda de regresión para la puerta de confianza, no un benchmark competitivo; ver eval/README.md.)
Benchmark
En opendataloader-bench — 200 PDFs del mundo real (declaraciones financieras, artículos académicos, contratos legales, informes gubernamentales) — pdfmux obtiene 0.903 en general — #2 de los 8 motores medidos, detrás de opendataloader-hybrid (0.909). Re-ejecución 2026-07-16 (reproducción a continuación).
| Rango | Motor | General | Orden de lectura | Tablas (TEDS) | Licencia | GPU |
|---|---|---|---|---|---|---|
| 1 | opendataloader-hybrid | 0.909 | 0.935 | 0.928 | Apache-2.0 | No |
| 2 | pdfmux | 0.903 | 0.920 | 0.911 | MIT | No |
| 3 | Docling | 0.877 | 0.900 | 0.887 | MIT | Opcional |
| 4 | marker | 0.861 | 0.890 | 0.808 | gratis | GPU |
| 5 | mineru | 0.831 | 0.857 | 0.873 | gratis | GPU |
Puntuaciones completas por documento: el cara a cara de 200 PDF · metodología: mejor biblioteca de extracción de PDF, evaluada.
Caché de Resultados Inteligente
Re-ejecutar la misma extracción es instantáneo. pdfmux calcula el hash de cada PDF de entrada (SHA-256) y clavea los resultados en (file_hash, quality, format, schema). Los archivos de caché viven bajo ~/.cache/pdfmux/results/, expiran después de 30 días y se eliminan por LRU a 1 GB.
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
La caché también acelera los cambios de --profile, --schema y --format — cada combinación se clavea de forma independiente, por lo que puedes alternar entre Markdown y JSON para el mismo documento sin costo después de la primera extracción.
Puntuación de Confianza
Cada resultado incluye una puntuación de confianza de 4 señales:
- 95-100% -- texto digital limpio, totalmente extraíble
- 80-95% -- buena extracción, ruido menor de OCR en algunas páginas
- 50-80% -- extracción parcial, algunas páginas irrecuperables
- <50% -- falta contenido significativo, se incluyen advertencias
Cuando la confianza cae por debajo del 80%, pdfmux te dice exactamente qué salió mal y cómo solucionarlo:
Page 4: 32% confidence. 0 chars extracted from image-heavy page.
-> Install pdfmux[ocr] for RapidOCR support on 6 image-heavy pages.
Modos de Costo
| Modo | Comportamiento | Costo típico |
|---|---|---|
| economy | Solo backends basados en reglas. Sin llamadas a LLM. | $0/página |
| balanced | LLM solo para páginas que fallan en la extracción basada en reglas. | ~$0.002/página promedio |
| premium | LLM en cada página para máxima calidad. | ~$0.01/página |
Establece un límite de presupuesto duro: --budget 0.50 detiene las llamadas a LLM cuando el gasto alcanza $0.50 por documento.
¿Por qué pdfmux?
pdfmux no es otro extractor de PDF. Es la capa de orquestación que elige el extractor adecuado por página, verifica el resultado y reintenta los fallos.
| Herramienta | Bueno en | Limitación |
|---|---|---|
| PyMuPDF | Texto digital rápido | No puede manejar escaneos o diseños de imagen |
| Docling | Tablas (97.9% de precisión) | Lento en documentos sin tablas |
| Marker | Extracción neuronal para artículos académicos | Necesita GPU para velocidad; excesivo para PDFs digitales |
| Mistral OCR | Tablas (96.6% TEDS), $0.002/página | API solo en la nube |
| Unstructured | Plataforma empresarial | Configuración compleja, niveles de pago |
| LlamaParse | Nativo de la nube | Requiere claves API, no local |
| Reducto | Alta precisión | $0.015/página, código cerrado |
| pdfmux | Orquesta todo lo anterior | Enruta por página, audita, re-extrae |
Alternativa de código abierto a Reducto: lo que cuesta $0.015/página en otros lugares es gratis con los backends basados en reglas de pdfmux, o ~$0.002/página promedio con respaldo de LLM BYOK.
Desarrollo
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/
Contribuciones
- Haz un fork del repositorio
- Crea una rama (
git checkout -b feature/your-feature) - Escribe pruebas para la nueva funcionalidad
- Asegúrate de que
pytestyruff checkpasen - Abre un PR
Licencia
La biblioteca pdfmux y el servidor MCP en este repositorio tienen licencia MIT — gratis para cualquier uso, y cada versión publicada permanece MIT.
El método de rastro de decisión con presupuesto de confianza (el rastro de decisión persistido por página con candidatos rechazados retenidos, y la salvaguarda de reparación monótona) está pendiente de patente (Solicitud Provisional de EE. UU. No. 64/106,302) y está reservado para pdfmux Cloud/Pro bajo una licencia comercial separada — no es parte de la concesión MIT. Ver LICENSING.md y NOTICE.