Image: AI generated
pip install markitdownpor sí solo convierte PDF, documentos de Word, hojas de Excel y presentaciones de PowerPoint en markdown. Pero si le das un PDF que no es más que imágenes escaneadas —un contrato escaneado, por ejemplo— obtienes un documento vacío. Este artículo avanza por dos vías: por qué y cómo usar MarkItDown, y cómo añadir OCR justo en el punto donde esa confianza se rompe: los PDF que solo contienen imágenes.
Por qué deberías usar MarkItDown — la condición agent-operable
MarkItDown es la biblioteca de código abierto de Microsoft para CLI/Python que convierte archivos PDF, Word, Excel, PowerPoint, HTML, imágenes y audio en markdown.
Empecemos por el concepto de agent-operable (un estado con el que un agente puede trabajar). Para que un agente maneje un documento de forma autónoma, ese documento debe estar en un estado que una máquina pueda analizar estructuralmente, no solo en un estado que un humano encuentre legible. Esta no es una condición que solo aplique al código. Una regla de negocio enterrada dentro de un PDF o una hoja de Excel bien podría no existir para un agente, ya que un humano tiene que abrirla para saber qué dice. Esta es la primera de tres condiciones —legible, verificable, persistente— expuestas en Construyendo sistemas agent-operable: “legible sin ruido”.
Entonces, ¿por qué MarkItDown específicamente? Podrías construir la misma conversión tú mismo combinando analizadores de terceros (PyPDF2, python-docx). La diferencia está en quién lo construyó. docx, xlsx y pptx son todos OOXML, una especificación que Microsoft posee. Cuando la empresa dueña de la especificación construye el conversor para su propio formato, los casos extremos (celdas combinadas, tablas anidadas, notas al pie) tienen estructuralmente más probabilidades de funcionar bien que con un analizador de terceros. Si la conversión en sí no es confiable, todo lo que se construya encima hacia un estado agent-operable no tiene sentido. Esa es la razón para usar MarkItDown: para los formatos propios de Microsoft, esa confianza viene integrada de forma predeterminada.
El problema es que esa confianza no se traslada a los PDF, especialmente los que solo contienen imágenes. La segunda mitad de este artículo se centra en cerrar esa brecha con OCR.
Instalación
Instalación base
pip install markitdown
La instalación base por sí sola solo maneja formatos ligeros como texto plano y HTML. Para convertir documentos PDF y de Office, se necesitan los extras específicos de cada formato.
Extras específicos por formato
pip install 'markitdown[pdf]' # PDF (pdfminer.six + pdfplumber)
pip install 'markitdown[docx]' # Word
pip install 'markitdown[pptx]' # PowerPoint
pip install 'markitdown[xlsx]' # Excel
pip install 'markitdown[all]' # everything above, plus audio transcription, YouTube captions, Azure Document Intelligence, etc.
Si este artículo solo necesita PDF, con markitdown[pdf] es suficiente.
Uso básico
CLI
markitdown example.pdf # print markdown to stdout
markitdown example.pdf -o example.md # save to a file
cat example.pdf | markitdown # read from stdin (pipe)
API de Python
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("example.pdf")
print(result.markdown)
Ambos métodos seleccionan automáticamente el conversor según la extensión del archivo o el tipo MIME. Cuando no hay información de extensión —al leer desde stdin, por ejemplo— se puede dar una pista con -x (--extension) o -m (--mime-type).
Lo que realmente hace la conversión de PDF — solo lee la capa de texto
El conversor de PDF de MarkItDown (packages/markitdown/src/markitdown/converters/_pdf_converter.py) primero usa pdfplumber para detectar la estructura de tablas/formularios en cada página, y recurre a page.extract_text() cuando una página no es una tabla. Si pdfplumber falla, recurre a pdfminer.six.
Ambas bibliotecas son herramientas que leen una capa de texto que ya existe dentro del PDF. Si una página consiste únicamente en una imagen escaneada sin capa de texto, extract_text() devuelve una cadena vacía, y el resultado de MarkItDown termina siendo efectivamente vacío. No existe ninguna ruta de código en el paquete base que renderice una imagen o reconozca caracteres en ella.
En otras palabras, son dos problemas distintos:
- Un PDF con capa de texto (exportado desde Word, la mayoría de los contratos electrónicos) → la instalación base funciona bien. La misma confianza que respalda los formatos propios de Microsoft se traslada aquí también.
- Un PDF sin capa de texto (un documento pasado por un escáner, una foto incrustada como imagen) → la instalación base devuelve un resultado vacío. Aquí es donde se rompe la condición “legible” de agent-operable.
Configurando OCR — donde se repara la confianza rota
Aquí empieza la segunda vía del artículo. La confianza que MarkItDown asegura para los formatos propios de Microsoft debe reconstruirse por separado para los PDF que solo contienen imágenes.
1. Instalar el plugin
El soporte de OCR vive en un paquete separado, markitdown-ocr. Está en el mismo repositorio (microsoft/markitdown), pero no se incluye en pip install markitdown.
pip install markitdown-ocr
pip install openai # an OpenAI-compatible client — this plugin has no OCR model of its own
Después de instalar, comprueba que el plugin se detecta.
markitdown --list-plugins
# * ocr (package: markitdown_ocr)
2. Lo que no es negociable — un cliente de LLM Vision
markitdown-ocr no incluye un motor de OCR tradicional como Tesseract o PaddleOCR. Lo único que hace realmente es codificar la imagen en base64 y pedirle a una API chat.completions compatible con OpenAI (por ejemplo, gpt-4o) que “extraiga el texto de esta imagen” (LLMVisionOCRService en _ocr_service.py). Por eso, una clave de API capaz de llamar a un modelo de visión es un requisito indispensable para que el OCR funcione en absoluto.
from markitdown import MarkItDown
from openai import OpenAI
md = MarkItDown(
enable_plugins=True,
llm_client=OpenAI(), # reads OPENAI_API_KEY from the environment
llm_model="gpt-4o",
)
result = md.convert("scanned_contract.pdf")
print(result.markdown)
Cualquier cliente compatible con OpenAI —incluido Azure OpenAI— se puede pasar de la misma forma.
from openai import AzureOpenAI
md = MarkItDown(
enable_plugins=True,
llm_client=AzureOpenAI(
api_key="...",
azure_endpoint="https://<resource>.openai.azure.com/",
api_version="2024-02-01",
),
llm_model="gpt-4o",
)
3. No funciona desde la CLI — verificado contra el código real
El README de markitdown-ocr presenta este comando como ejemplo de uso:
markitdown document.pdf --use-plugins --llm-client openai --llm-model gpt-4o
Pero al revisar directamente las definiciones de argumentos de la CLI del núcleo de MarkItDown (packages/markitdown/src/markitdown/__main__.py) queda claro que esas banderas no existen: no hay --llm-client ni --llm-model. La CLI admite -o (archivo de salida), -x (pista de extensión), -m (pista de MIME), -c (charset), -d/--use-docintel, --use-cu, -p/--use-plugins, --list-plugins y --keep-data-uris, y eso es todo; el constructor de MarkItDown solo recibe enable_plugins=args.use_plugins. En otras palabras, el ejemplo de CLI del README es un error de documentación: en realidad no funciona.
El OCR solo funciona a través de la API de Python — hay que pasar llm_client/llm_model directamente. No hay forma de entregarle una clave de API a la CLI.
4. Cómo funciona realmente — OCR de imágenes incrustadas y respaldo de OCR de página completa
PdfConverterWithOCR (_pdf_converter_with_ocr.py) opera en dos etapas.
- Imágenes incrustadas primero: cuando una página mezcla texto con imágenes (digamos, una firma escaneada colocada en medio del texto), solo se recorta la imagen, se envía a LLM Vision, y el resultado se intercala con el texto circundante según su posición Y original, preservando el orden de lectura.
- Respaldo de OCR de página completa: si la extracción de texto vuelve completamente vacía (un PDF totalmente escaneado), la página completa se renderiza como PNG a 300dpi y se envía a LLM Vision una página a la vez (
_ocr_full_pages).
El texto extraído siempre se envuelve en este formato:
*[Image OCR]
<extracted text>
[End OCR]*
5. Ajustar la precisión con un prompt personalizado
El prompt predeterminado es “extrae todo el texto de esta imagen, conservando el diseño y el orden originales”. Para documentos con muchas tablas, texto vertical, o sellos y firmas, sobrescribirlo con llm_prompt puede mejorar la precisión.
md = MarkItDown(
enable_plugins=True,
llm_client=OpenAI(),
llm_model="gpt-4o",
llm_prompt="Extract all text from this image, but preserve table structure as markdown tables.",
)
6. Procesamiento por lotes de varios archivos
Al ejecutar toda una carpeta de PDF escaneados de una vez, cada archivo dispara su propia llamada a la API — hay que envolver cada conversión para que un solo fallo no arruine todo el lote.
from pathlib import Path
from markitdown import MarkItDown
from openai import OpenAI
md = MarkItDown(enable_plugins=True, llm_client=OpenAI(), llm_model="gpt-4o")
for pdf_path in Path("scans/").glob("*.pdf"):
try:
result = md.convert(str(pdf_path))
pdf_path.with_suffix(".md").write_text(result.markdown, encoding="utf-8")
except Exception as e:
print(f"failed: {pdf_path.name} — {e}")
7. ¿Qué pasa si instalas el plugin sin un llm_client?
El plugin se carga, pero el OCR se apaga silenciosamente. register_converters() en _plugin.py solo construye un LLMVisionOCRService cuando tanto llm_client como llm_model están presentes; de lo contrario, registra los conversores con ocr_service=None. En ese estado, recurre silenciosamente a la extracción de solo la capa de texto — sin error, sin advertencia. La mayoría de los problemas de “instalé el plugin pero el OCR no funciona” se remontan exactamente a este punto.
8. Incluso intenta recuperar PDF corruptos
Cuando pdfplumber y pdfminer no pueden abrir un PDF en absoluto —un EOF truncado, por ejemplo— markitdown-ocr recurre a renderizar las páginas directamente con PyMuPDF (fitz) y reintenta. Si esa ruta también falla, deja *[Error: Could not process scanned PDF]* en el resultado.
9. Costo y velocidad
Como envía páginas completas a un modelo de visión, el costo y la latencia se acumulan en proporción al número de páginas del documento escaneado. Un renderizado a 300dpi no es barato en tokens por imagen, así que es más seguro tomar una muestra de unas cuantas páginas primero para calibrar calidad y costo antes de procesar un documento completo de cientos de páginas.
Verificar el resultado del OCR — próximamente por separado
Todo lo anterior trata sobre cómo “activar” el OCR. Activarlo no es lo mismo que poder confiar en el resultado — bajo el diseño actual, donde todo pasa silenciosamente tanto si llm_client está configurado como si no, y tanto si la página está en blanco como si no, no existe un procedimiento determinista para juzgar si el resultado del OCR es realmente confiable.
Ese procedimiento no se cubre aquí. Cómo aplicar el principio del Symbolic Feedback Loop —el LLM genera → una herramienta determinista juzga → retroalimentación → repetir— al resultado del OCR es un artículo/herramienta aparte.
Lista de verificación práctica
- Si solo manejas PDF normales con capa de texto, la instalación base
markitdown[pdf]es todo lo que necesitas. No se requiere el plugin de OCR. - Si tienes que manejar PDF escaneados o fotografiados,
markitdown-ocrmás una clave de API de OpenAI (o compatible) son obligatorios. No hay integración con un motor de OCR tradicional, local y gratuito como Tesseract. - Si el resultado del OCR vuelve vacío, lo primero que hay que comprobar es si realmente se pasaron
llm_client/llm_model. Falla silenciosamente, así que nada aparece en los registros. - El OCR no se puede activar solo desde la CLI. Se necesita un script de Python que llame a
MarkItDown(enable_plugins=True, llm_client=..., llm_model=...). - Todavía no hay una forma mecánica de juzgar cuán confiable es el resultado — por ahora, un humano tiene que revisar una muestra.
Tabla resumen
| Situación | Qué se necesita | Notas |
|---|---|---|
| PDF con capa de texto | pip install markitdown[pdf] | pdfplumber/pdfminer extraen el texto |
| Algunas imágenes incrustadas necesitan OCR | markitdown-ocr + cliente de LLM Vision | recorta solo las imágenes, las intercala con el texto |
| Escaneado completo (toda la página es una imagen) | markitdown-ocr + cliente de LLM Vision | renderiza la página completa a 300dpi, luego la procesa con OCR |
| PDF corrupto | igual que arriba (respaldo automático) | reintenta renderizando con PyMuPDF |
llm_client no configurado | — | OCR omitido silenciosamente, sin error |
Pasar --llm-client en la CLI | no es posible (esa bandera no existe) | solo API de Python |
| Juzgar la confiabilidad del resultado de OCR | (planeado) | lo cubrirá un verificador aparte |
Artículos relacionados
- Construyendo sistemas agent-operable — las tres condiciones (legible, verificable, persistente), y dónde encaja MarkItDown
- Agent Operable Codebase — el mismo principio aplicado al código
Referencias
- microsoft/markitdown — raíz del repositorio
_pdf_converter.py— el conversor de PDF base (solo extracción de la capa de texto)__main__.py— definiciones de argumentos de la CLI (confirma que--llm-client/--llm-modelno existen)markitdown-ocrREADME_pdf_converter_with_ocr.py_ocr_service.py_plugin.py- markitdown pyproject.toml — lista de extras
Registro de cambios
- 2026-07-09: Versión inicial — se añadió el enfoque agent-operable, se amplió la configuración de OCR como segunda mitad, se añadió una sección de marcador de posición para el verificador