PDF, Word, Excel, PowerPoint, imágenes, audio, webs y YouTube → Markdown estructurado y token-efficient
MarkItDown es la utilidad de Microsoft —nacida en el equipo de AutoGen— que convierte casi cualquier archivo en Markdown listo para un LLM. No busca una conversión bonita para humanos: busca preservar la estructura (títulos, listas, tablas, enlaces) en un formato que los modelos hablan de forma nativa y que además es barato en tokens. Con ~185.000 estrellas en GitHub, licencia MIT y un servidor MCP oficial, se ha convertido en la pieza estándar del primer paso de cualquier pipeline RAG. En este curso lo llevas desde pip install hasta un pipeline completo de documentos → Markdown, pasando por la CLI, la API de Python, el servidor MCP para agentes, los plugins y la conversión cloud con Azure.
MarkItDown es una librería de Python que convierte archivos y documentos de oficina a Markdown. Se compara a sí misma con textract, pero con una diferencia de enfoque: mientras textract extrae texto plano, MarkItDown intenta preservar la estructura del documento como Markdown —encabezados, listas, tablas, enlaces— porque ese texto no es para leerlo: es para que lo consuma una herramienta de análisis de texto.
El proyecto es explícito sobre su límite: la salida es razonablemente legible por humanos, pero no es la mejor opción para conversión de alta fidelidad para consumo humano. Eso es una decisión de diseño, no un defecto: optimiza para lo que un LLM necesita.
El razonamiento del proyecto es sencillo y vale la pena interiorizarlo:
El soporte incluye formatos de oficina, multimedia y web:
| Entrada | Qué extrae | Dependencia |
|---|---|---|
| Texto y estructura (o vía Azure si es escaneado) | [pdf] | |
| PowerPoint | Título, texto, notas y descripción de imágenes con LLM | [pptx] |
| Word | Encabezados, listas, tablas y enlaces | [docx] |
| Excel | Hojas y tablas ([xls] para el formato antiguo) | [xlsx] |
| Imágenes | Metadatos EXIF y OCR; descripciones con LLM Vision | core + llm_client |
| Audio | Metadatos EXIF y transcripción de voz | [audio-transcription] |
| HTML | Encabezados, párrafos con énfasis, tablas y listas | core |
| CSV, JSON, XML | Formatos basados en texto | core |
| ZIP | Itera sobre el contenido del archivo | core |
| YouTube | Transcripción del vídeo a partir de la URL | [youtube-transcription] |
| EPub | Libros electrónicos | core |
Dos hechos de contexto que ayudan a situar el proyecto: nació dentro del ecosistema de AutoGen (lleva el badge Built by AutoGen Team) y sus topics oficiales en GitHub son autogen, langchain, openai, markdown, microsoft-office y pdf — es decir, está pensado para vivir dentro de aplicaciones y agentes, no para ser una app de escritorio.
La documentación oficial es explícita: el repositorio existe para publicar librerías de Python que se incorporan a otros sistemas, no aplicaciones de usuario final. Si buscas un servidor REST, un frontend web o una app de escritorio para MarkItDown, no lo encontrarás aquí — la recomendación oficial es mantenerlo como un paquete o proyecto aparte que dependa de markitdown de PyPI.
MarkItDown requiere Python 3.10 o superior y se recomienda una máquina virtual para evitar conflictos de dependencias. El paquete se distribuye en PyPI como markitdown y su instalación completa es:
pip install 'markitdown[all]'
Alternativamente, desde el código fuente (útil si quieres contribuir o fijar un commit):
git clone git@github.com:microsoft/markitdown.git
cd markitdown
pip install -e 'packages/markitdown[all]'
Con Python estándar: python -m venv .venv y luego source .venv/bin/activate. Con uv: uv venv --python=3.12 .venv (y recuerda usar uv pip install, no pip install, dentro de ese entorno). Con conda: conda create -n markitdown python=3.12.
El paquete base es ligero; los conversores pesados viven en extras. Por ejemplo, para PDF, Word y PowerPoint: pip install 'markitdown[pdf, docx, pptx]'.
markitdown --version imprime el número de versión y sale. En la verificación de este curso la versión instalada y probada fue 0.1.7.
markitdown ejemplo.pdf > ejemplo.md. Si funciona, ya tienes el flujo completo: un archivo entra, Markdown sale.
| Extra | Activa |
|---|---|
[all] | Todas las dependencias opcionales |
[pptx] | Archivos PowerPoint |
[docx] | Archivos Word |
[xlsx] | Archivos Excel |
[xls] | Excel en formato antiguo |
[pdf] | Archivos PDF |
[outlook] | Mensajes de Outlook |
[az-doc-intel] | Azure Document Intelligence |
[az-content-understanding] | Azure Content Understanding |
[audio-transcription] | Transcripción de audio wav y mp3 |
[youtube-transcription] | Transcripción de vídeos de YouTube |
Instalar markitdown a secas y lanzarlo contra un .pdf o un .xlsx. El paquete base no incluye los conversores de oficina: sin los extras, esos formatos no se pueden procesar. Si un formato te falla, lo primero que hay que revisar es si su extra está instalado.
La interfaz de línea de comandos es la forma más directa de usar la herramienta. La forma canónica es escribir a stdout y redirigir:
markitdown ruta-al-archivo.pdf > documento.md
O usar -o para indicar el archivo de salida:
markitdown ruta-al-archivo.pdf -o documento.md
Y también acepta contenido por pipe:
cat ruta-al-archivo.pdf | markitdown
Esto es lo que devuelve MarkItDown al convertir un CSV, ejecutado de verdad durante la preparación de este curso:
$ markitdown ventas.csv
| producto | precio | stock |
| --- | --- | --- |
| Teclado mecanico | 89.90 | 12 |
| Monitor 27 pulgadas | 219.00 | 4 |
| SSD NVMe 1TB | 74.50 | 31 |
Fíjate en lo que ha ocurrido: el CSV se ha convertido en una tabla Markdown, no en texto separado por comas. Es exactamente el tipo de transformación que un LLM agradece.
Si no hay extensión que inspeccionar, se la das tú con pistas. Verificado en producción:
$ cat ventas.csv | markitdown -x csv
| producto | precio | stock |
| --- | --- | --- |
| Teclado mecanico | 89.90 | 12 |
| Monitor 27 pulgadas | 219.00 | 4 |
| SSD NVMe 1TB | 74.50 | 31 |
| Flag | Para qué sirve |
|---|---|
-o, --output | Archivo de salida. Si no se indica, se escribe en stdout. |
-x, --extension | Pista de extensión del archivo (por ejemplo al leer desde stdin). |
-m, --mime-type | Pista del tipo MIME del archivo. |
-c, --charset | Pista del juego de caracteres (por ejemplo UTF-8). |
-p, --use-plugins | Habilita plugins de terceros durante la conversión. |
--list-plugins | Lista los plugins instalados y sale. |
--keep-data-uris | Conserva los data URIs (imágenes en base64) en la salida. |
-d, --use-docintel | Usa Azure Document Intelligence en lugar de la conversión offline. |
-e, --endpoint | Endpoint de Document Intelligence (por defecto MARKITDOWN_DOCINTEL_ENDPOINT). |
--use-cu | Usa Azure Content Understanding. Requiere --cu-endpoint. |
--cu-analyzer | ID del analizador de Content Understanding (si no, autoselecciona por tipo). |
--cu-file-types | Tipos a enrutar a Content Understanding, separados por comas (por ejemplo pdf,jpeg,mp4). |
-v, --version | Muestra la versión y sale. |
-d/--use-docintel y --use-cu son mutuamente excluyentes: el parser los declara en un grupo exclusivo, así que no puedes pedir las dos nubes a la vez.
Para pipelines reales, el patrón habitual es un bucle que respete la estructura de directorios:
mkdir -p salida
for f in documentos/*.pdf; do
base=$(basename "$f" .pdf)
markitdown "$f" -o "salida/$base.md"
done
Los data URIs —imágenes incrustadas en base64— se truncan por defecto en la salida. Si necesitas las imágenes embebidas (por ejemplo para reconstruir un documento), añade --keep-data-uris; de lo contrario un pipeline que cuente caracteres se comportará de forma distinta a lo que esperas.
Antes de construir un pipeline complicado, comprueba los plugins disponibles con markitdown --list-plugins. La salida real del comando, con el paquete recién instalado, es: Installed MarkItDown 3rd-party Plugins seguido de No 3rd-party plugins installed y una sugerencia de buscar el hashtag #markitdown-plugin en GitHub.
El uso básico es de tres líneas. Se instancia MarkItDown y se llama a convert():
from markitdown import MarkItDown
md = MarkItDown(enable_plugins=False) # True para habilitar plugins
result = md.convert("test.xlsx")
print(result.markdown)
La conversión devuelve un DocumentConverterResult con dos campos relevantes:
result.markdown — el texto Markdown resultante. Es el campo que debes usar.result.title — título opcional del documento, si el conversor puede determinarlo (puede ser None).El objeto también implementa __str__ devolviendo el Markdown, y existe text_content como alias en desuso: si lo ves en código antiguo, el código nuevo debe migrar a markdown.
Este es el punto de seguridad más importante de todo el curso. MarkItDown hace E/S con los privilegios del proceso actual: igual que open() o requests.get(), accede a los recursos a los que el proceso tiene acceso. Por eso la API ofrece varios niveles de control:
| Método | Qué acepta | Cuándo usarlo |
|---|---|---|
convert() | Archivos locales, URIs remotas y streams de bytes | Intencionadamente permisivo; cómodo en scripts de confianza |
convert_local() | Solo archivos locales | Cuando solo necesitas leer del disco |
convert_response() | El objeto de respuesta de requests | Cuando quieres controlar tú la descarga de la URI |
convert_stream() | Un stream abierto | Control máximo sobre qué se lee y de dónde |
Un ejemplo real de conversión desde un stream de bytes en memoria, verificado durante la preparación de este curso:
$ python3 - <<'EOF'
import io
from markitdown import MarkItDown
md = MarkItDown()
with open('ventas.csv', 'rb') as f:
stream = io.BytesIO(f.read())
result = md.convert_stream(stream, stream_info=None)
print('tipo:', type(result).__name__)
print('title:', result.title)
print(result.markdown[:120])
EOF
tipo: DocumentConverterResult
title: None
| producto | precio | stock |
| --- | --- | --- |
| Teclado mecanico | 89.90 | 12 |
| Monitor 27 pulgadas | 219.00 | 4 |
Dos observaciones útiles de la salida: el tipo es DocumentConverterResult y el title es None porque un CSV no tiene título. Tu código debe estar preparado para que title venga vacío.
No pases entrada no confiable a convert(). Si el input puede estar controlado por un usuario o un sistema externo —el caso típico de una aplicación alojada o del lado servidor— hay que validarlo y restringirlo antes: limitar rutas de archivo, restringir esquemas de URI y destinos de red, y bloquear el acceso a direcciones privadas, de loopback, link-local o de servicios de metadatos. La documentación de seguridad del proyecto lo dice sin rodeos: sanitiza las entradas en entornos no confiables y llama a la función convert_* más estrecha que necesites.
Los formatos de oficina son el caso de uso principal. Cada uno tiene su dependencia y sus particularidades:
| Formato | Extra | Qué esperar |
|---|---|---|
[pdf] | Extracción de texto y estructura offline. Los PDF escaneados no tienen capa de texto: necesitan OCR o Azure Document Intelligence. | |
| Word (.docx) | [docx] | Encabezados, listas, tablas y enlaces preservados como Markdown. |
| PowerPoint (.pptx) | [pptx] | Contenido de las diapositivas; admite descripción de imágenes por LLM. |
| Excel (.xlsx) | [xlsx] | Una sección por hoja con sus tablas convertidas a tablas Markdown. |
| Outlook | [outlook] | Mensajes de correo. |
Creamos un libro con una hoja llamada Q3 y dos filas de datos. Esto es exactamente lo que devuelve MarkItDown:
$ markitdown informe.xlsx
## Q3
| Region | Ingresos | Margen |
| --- | --- | --- |
| EMEA | 412000 | 0.31 |
| LATAM | 188500 | 0.27 |
El detalle importante: el nombre de la hoja se convierte en un encabezado de nivel 2 (## Q3). Eso significa que si concatenas varias hojas, un LLM puede atribuir cada tabla a su contexto sin ambigüedad. Los valores numéricos se mantienen tal cual, sin formateo añadido: el trabajo de interpretarlos es del modelo, no de la herramienta.
$ markitdown pagina.html
# Experimento 14
Medimos **latencia** con 3 modelos.
| Modelo | ms |
| --- | --- |
| A | 120 |
| B | 98 |
* Repetir 5 veces
* Anotar p95
Se ve la traducción directa: <h1> → #, <strong> → **, <table> → tabla Markdown, <ul><li> → lista con asteriscos.
Como el objetivo es alimentar modelos, el nombre de la hoja o del fichero original es información valiosa que conviene conservar: al convertir en lote, guarda el Markdown en un archivo con el mismo nombre base y, si vas a concatenar, añade siempre el origen como encabezado. El contexto que no está en el texto, el LLM no lo puede adivinar.
Confundir «no se ha extraído nada» con «el documento está vacío». En PDFs escaneados, en PDFs protegidos o en documentos con el texto como imagen, la conversión offline devuelve muy poco. Antes de culpar a la herramienta, comprueba si el PDF tiene capa de texto.
MarkItDown no se limita a documentos. En imágenes extrae los metadatos EXIF (y OCR cuando está disponible) y, opcionalmente, genera descripciones con un LLM. En audio hace lo simétrico: metadatos EXIF y transcripción de voz.
Para usar un modelo en la descripción de imágenes —actualmente para pptx y archivos de imagen— se le pasan llm_client y llm_model:
from markitdown import MarkItDown
from openai import OpenAI
client = OpenAI(max_retries=5)
md = MarkItDown(
llm_client=client,
llm_model="gpt-4o",
llm_prompt="opcional: un prompt personalizado",
)
result = md.convert("ejemplo.jpg")
print(result.markdown)
Tres detalles que marcan la diferencia en producción:
max_retries controla los reintentos automáticos del cliente de OpenAI ante errores reintentables (el valor por defecto del SDK es 2; 5 permite hasta seis intentos con retroceso exponencial).FileConversionException si ninguno tiene éxito.Ese último comportamiento es la razón de que un pipeline pueda seguir funcionando cuando la API de vision falla: hay una cadena de respaldo antes de rendirse.
| Necesidad | Extra | Notas |
|---|---|---|
| Transcribir wav y mp3 | [audio-transcription] | Voz a texto dentro del propio Markdown |
| Metadatos de audio | core | Información EXIF del archivo |
| Audio de mayor calidad en la nube | [az-content-understanding] | Ver lección 10 |
| Vídeo | [az-content-understanding] | Los conversores integrados no soportan vídeo |
Si tu corpus tiene imágenes dentro de diapositivas o documentos (diagramas, capturas, gráficos), el paso de llm_client es el que convierte una imagen muda en texto buscable. Es la diferencia entre un RAG que ignora los gráficos y uno que los puede citar.
Olvidar que el vídeo no está cubierto por los conversores integrados. Si tu pipeline incluye archivos .mp4, la única vía de MarkItDown es Content Understanding de Azure: audio y vídeo con analizadores, y ninguna de las dos opciones locales los procesa.
Además de archivos locales, MarkItDown trabaja con fuentes remotas y contenedores. Esto es lo que abre la puerta a pipelines que ingieren documentación web o vídeos sin escribir un scraper propio.
| Fuente | Cómo | Notas |
|---|---|---|
| HTML | URL o archivo local | Encabezados, énfasis, tablas y listas a Markdown |
| YouTube | URL del vídeo | Requiere [youtube-transcription]; obtiene la transcripción |
| ZIP | Archivo comprimido | Itera sobre el contenido y convierte los elementos que reconoce |
| EPub | Libro electrónico | Libros completos a Markdown |
| CSV / JSON / XML | Formatos basados en texto | Sin dependencias extra |
| data URIs | URI data: | Truncados por defecto; --keep-data-uris para conservarlos |
El soporte de ZIP merece un párrafo aparte: en vez de devolver un binario ilegible, recorre el contenido del archivo y convierte lo que encuentra dentro. Para un corpus que llega comprimido —un lote de facturas, un export de correo, una carpeta de documentación— eso significa que un solo convert() puede producir Markdown de N documentos.
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("https://ejemplo.com/documentacion")
print(result.markdown)
Y por la CLI, igual de simple: markitdown https://ejemplo.com/documentacion -o doc.md. Para vídeos: markitdown "https://www.youtube.com/watch?v=..." -o transcripcion.md, siempre con el extra [youtube-transcription] instalado.
Aceptar URLs arbitrarias sin filtro cuando el proceso es un servicio. Un endpoint que pasa la URI del usuario directamente a convert() es, en la práctica, un proxy de red con los permisos del servidor: se puede usar para tocar direcciones internas o de metadatos cloud. Restringe esquemas y destinos, o descarga tú el contenido con requests.get() y pásalo a convert_response(): así el control de la red es tuyo.
Convierte primero a Markdown y guarda el Markdown en disco antes de meterlo en un índice vectorial. El paso de conversión es la parte frágil y lenta de un pipeline RAG; hacerlo una sola vez y cachearlo te permite reindexar, cambiar de modelo de embeddings o re-chunkear sin volver a tocar los originales.
El paquete markitdown-mcp expone la capacidad de conversión como un servidor MCP (Model Context Protocol) que un agente puede llamar. Es la forma de que tu asistente lea documentos en lugar de que tú le pegues el texto.
pip install markitdown-mcp
Expone una sola herramienta: convert_to_markdown(uri), donde la URI puede ser http:, https:, file: o data:.
| Modo | Comando | Cuándo |
|---|---|---|
| STDIO (por defecto) | markitdown-mcp | Cliente local que lanza el proceso |
| Streamable HTTP + SSE | markitdown-mcp --http --host 127.0.0.1 --port 3001 | Alternativa cómoda al STDIO; queda escuchando en localhost |
La ayuda real del comando instalado, ejecutada durante la preparación de este curso, es:
$ markitdown-mcp --help
usage: markitdown-mcp [-h] [--http] [--sse] [--host HOST] [--port PORT]
Run a MarkItDown MCP server
options:
-h, --help show this help message and exit
--http Run the server with Streamable HTTP and SSE transport rather
than STDIO (default: False)
--sse (Deprecated) An alias for --http (default: False)
--host HOST Host to bind to (default: 127.0.0.1)
--port PORT Port to listen on (default: 3001)
El valor por defecto de --host es 127.0.0.1 y el de --port es 3001. Y atención: --sse está obsoleto, es un alias de --http.
La recomendación oficial es usar la imagen Docker cuando el servidor se lanza desde Claude Desktop. En claude_desktop_config.json:
{
"mcpServers": {
"markitdown": {
"command": "docker",
"args": ["run", "--rm", "-i", "markitdown-mcp:latest"]
}
}
}
Y si además quieres dar acceso a una carpeta local, se monta el directorio:
{
"mcpServers": {
"markitdown": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "/home/usuario/datos:/workdir",
"markitdown-mcp:latest"
]
}
}
}
Con el volumen montado, un archivo ejemplo.txt en /home/usuario/datos es accesible dentro del contenedor como /workdir/ejemplo.txt.
docker build -t markitdown-mcp:latest .
docker run -it --rm markitdown-mcp:latest
docker run -it --rm -v /home/usuario/datos:/workdir markitdown-mcp:latest
npx @modelcontextprotocol/inspector
En el inspector (por defecto en http://localhost:5173/): elige STDIO y escribe markitdown-mcp como comando, o bien Streamable HTTP con http://127.0.0.1:3001/mcp, o SSE con http://127.0.0.1:3001/sse. Después: pestaña Tools → List Tools → convert_to_markdown → ejecútala sobre cualquier URI válida.
Bindear el servidor a una interfaz no local. El servidor no soporta autenticación y corre con los privilegios del usuario que lo lanza: convert_to_markdown puede leer cualquier archivo al que ese usuario tenga acceso, o cualquier dato de la red. En modo SSE o HTTP se ata a localhost precisamente por eso. La documentación es tajante: no lo expongas a otras interfaces salvo que entiendas las implicaciones de seguridad; si necesitas más aislamiento, ejecútalo en una máquina virtual o contenedor con permisos limitados. El caso de uso previsto es uso local con agentes locales de confianza.
El servidor MCP convierte a MarkItDown en una capacidad reutilizable por cualquier cliente compatible: Claude Desktop, un agente propio, un IDE. En vez de escribir código de conversión en cada proyecto, levantas un servidor y lo consumes como herramienta. Es la misma filosofía que el resto del ecosistema MCP: una herramienta, muchos consumidores.
MarkItDown admite plugins de terceros, y esto resuelve un dilema de diseño del proyecto: añadir un formato nuevo suele implicar una dependencia nueva, y el equipo es conservador con eso. La respuesta son los plugins: el soporte de un formato nuevo se puede publicar e instalar de forma independiente del repositorio principal.
Los plugins están deshabilitados por defecto. El ciclo de trabajo es:
# Ver qué hay instalado
markitdown --list-plugins
# Convertir con plugins habilitados
markitdown --use-plugins ruta-al-archivo.pdf
El plugin de referencia añade soporte OCR a los conversores de PDF, DOCX, PPTX y XLSX, extrayendo el texto de las imágenes incrustadas mediante LLM Vision. El detalle elegante: reutiliza el mismo patrón llm_client / llm_model que MarkItDown ya usa para las descripciones de imágenes, así que no introduce librerías de ML ni dependencias binarias.
pip install markitdown-ocr
pip install openai # o cualquier cliente compatible con OpenAI
Uso, pasando el mismo cliente y modelo que usarías para describir imágenes:
from markitdown import MarkItDown
from openai import OpenAI
md = MarkItDown(
enable_plugins=True,
llm_client=OpenAI(),
llm_model="gpt-4o",
)
result = md.convert("documento_con_imagenes.pdf")
print(result.markdown)
Y un comportamiento que hay que conocer bien: si no se proporciona llm_client, el plugin se carga igual, pero el OCR se omite silenciosamente y se usa el conversor integrado estándar. Es decir, un pipeline mal configurado no falla: simplemente pierde texto sin avisar.
El punto de partida oficial es packages/markitdown-sample-plugin, dentro del propio repositorio. Los pasos:
Clona el repositorio y abre packages/markitdown-sample-plugin: muestra la estructura mínima de un plugin funcional.
El plugin es un paquete de Python independiente que se registra mediante el grupo de entry points markitdown.plugin — así lo descubre el CLI al listar plugins.
Añade el hashtag #markitdown-plugin a tu repositorio en GitHub para que otras personas puedan encontrarlo, y documenta el uso.
Antes de abrir un pull request al repositorio principal para añadir un formato, piensa si tu caso encaja mejor como plugin. La política del proyecto es clara: las mejoras de fidelidad de los conversores existentes y los arreglos de bugs son bienvenidos, pero los formatos nuevos se añaden con cuentagotas, y en la mayoría de los casos la vía recomendada es publicarlo como plugin de terceros.
Escribir aplicaciones, servidores web o frontends y ofrecerlos como contribución al repositorio: están explícitamente fuera de alcance. El proyecto no acepta servidores web, APIs REST o HTTP, servicios de conversión alojados, frontends de navegador ni aplicaciones de escritorio o móviles. Si construyes algo así, mantenlo como paquete o proyecto separado que dependa de markitdown de PyPI.
La conversión offline cubre la mayoría de los casos, pero hay tres escenarios donde la nube gana: documentos escaneados o con tablas complejas, extracción de campos estructurados y tratamiento de audio y vídeo. MarkItDown ofrece dos integraciones de Azure.
markitdown ruta.pdf -o documento.md -d -e "<document_intelligence_endpoint>"
El endpoint se puede fijar una sola vez en el entorno, de modo que después baste -d:
export MARKITDOWN_DOCINTEL_ENDPOINT="<document_intelligence_endpoint>"
markitdown ruta.pdf -o documento.md -d
Y en Python: md = MarkItDown(docintel_endpoint="<endpoint>"), seguido de md.convert("test.pdf").
Content Understanding va un paso más allá: extracción con campos estructurados en front matter YAML, soporte multimodal (documentos, imágenes, audio y vídeo) y analizadores configurables.
pip install 'markitdown[az-content-understanding]'
# CLI
markitdown ruta.pdf --use-cu --cu-endpoint "<content_understanding_endpoint>"
# o fijando el endpoint en el entorno
export MARKITDOWN_CU_ENDPOINT="<content_understanding_endpoint>"
markitdown ruta.pdf --use-cu
En Python, con configuración cero y selección automática de analizador por tipo de archivo:
from markitdown import MarkItDown
md = MarkItDown(cu_endpoint="<content_understanding_endpoint>")
result = md.convert("informe.pdf") # documentos -> prebuilt-documentSearch
result = md.convert("reunion.mp4") # vídeo -> prebuilt-videoSearch
result = md.convert("llamada.wav") # audio -> prebuilt-audioSearch
print(result.markdown)
Con un analizador propio para extraer campos de dominio, la salida incluye el front matter:
md = MarkItDown(
cu_endpoint="<content_understanding_endpoint>",
cu_analyzer_id="mi-analizador-de-facturas",
)
result = md.convert("factura.pdf")
# ---
# contentType: document
# fields:
# VendorName: CONTOSO LTD.
# InvoiceDate: '2019-11-15'
# ---
# <!-- page 1 -->
Cuando se fija cu_analyzer_id, el conversor acota automáticamente el analizador a los tipos de archivo compatibles según su modalidad. Los tipos incompatibles —por ejemplo audio con un analizador de documentos— se enrutan a los analizadores predefinidos por defecto.
| Capacidad | Conversores integrados | Azure Document Intelligence | Azure Content Understanding |
|---|---|---|---|
| Conversión de documentos | Offline, específica por formato | Extracción de maquetación en la nube | Extracción multimodal en la nube |
| Campos estructurados | No disponible | No expuestos por esta integración | Front matter YAML desde los campos del analizador |
| Analizadores propios | No disponible | No configurable en esta integración | Soportado con cu_analyzer_id |
| Audio y vídeo | Audio básico, sin vídeo | No soportado | Analizadores de audio y vídeo |
| Coste | Solo cómputo local | Llamadas facturables a Azure | Llamadas facturables a Azure |
Para controlar el gasto, cu_file_types restringe qué formatos se enrutan a Content Understanding:
from markitdown.converters import ContentUnderstandingFileType
md = MarkItDown(
cu_endpoint="<content_understanding_endpoint>",
cu_file_types=[ContentUnderstandingFileType.PDF], # solo los PDF usan CU
)
Cada llamada a convert() sobre un formato enrutado a Content Understanding es una llamada facturable a la API de Azure. En un script que recorre un corpus grande, el coste se dispara sin que aparezca ningún error. Decide de antemano qué tipos de archivo merecen la nube y fíjalos con cu_file_types.
Recorre el corpus y guarda el Markdown en disco. Es el paso caro: hazlo una vez.
Parte por encabezados Markdown (los # y ## son fronteras naturales) y añade metadatos: ruta original, título y fecha.
Genera embeddings y guarda los fragmentos en tu almacén vectorial. Cuando el agente recupere un fragmento, citará el documento original gracias a los metadatos del paso 2.
from pathlib import Path
from markitdown import MarkItDown
md = MarkItDown()
entrada, salida = Path("documentos"), Path("markdown")
salida.mkdir(exist_ok=True)
for f in entrada.rglob("*"):
if f.is_dir():
continue
try:
result = md.convert_local(str(f))
destino = salida / (f.stem + ".md")
# conservamos el origen como encabezado: contexto que el LLM no puede adivinar
destino.write_text(f"# {f.name}\n\n{result.markdown}", encoding="utf-8")
print("ok ", f.name)
except Exception as e:
print("fallo", f.name, type(e).__name__)
Y la variante en contenedor, útil para pipelines reproducibles y sin ensuciar el entorno del host:
docker build -t markitdown:latest .
docker run --rm -i markitdown:latest < ~/tu-archivo.pdf > salida.md
Registra todos los fallos de conversión en vez de silenciarlos. Un pipeline RAG que descarta documentos sin avisar produce respuestas incompletas que parecen un problema del modelo, cuando en realidad faltaba información en el índice. Un contador de «convertidos / fallidos» por carpeta es la mejor inversión de diez líneas de código.
No, y el proyecto lo dice explícitamente: su salida es razonablemente legible, pero no es la mejor opción para conversión de alta fidelidad para consumo humano. Está diseñado para que el texto lo consuman herramientas de análisis de texto y LLMs, preservando estructura (títulos, listas, tablas, enlaces) y siendo eficiente en tokens. Si necesitas reproducir la maquetación exacta de un documento, esta no es la herramienta.
No. Toda la conversión offline —PDF, Office, HTML, CSV/JSON/XML, ZIP, EPub— funciona en local. Azure Document Intelligence y Content Understanding son opcionales y solo hacen falta para casos concretos: PDFs escaneados, campos estructurados, audio y vídeo. Las únicas piezas que requieren un modelo externo son las descripciones de imagen y el plugin de OCR, y ahí puedes usar cualquier cliente compatible con OpenAI.
La conversión offline necesita una capa de texto, así que un PDF escaneado devuelve muy poco o nada. Las salidas son OCR con un modelo de visión (por ejemplo el plugin markitdown-ocr con llm_client) o el análisis de maquetación en la nube de Azure, que es la vía diseñada para documentos escaneados, tablas complejas y documentos de varias páginas.
Sí, es justo lo que hace markitdown-mcp: un servidor MCP con una sola herramienta, convert_to_markdown(uri), que acepta URIs http:, https:, file: y data:. Se lanza por STDIO (por defecto) o por Streamable HTTP en 127.0.0.1:3001. Recuerda que no tiene autenticación y puede leer cualquier archivo accesible por su usuario: úsalo en local con agentes de confianza y nunca lo expongas a otras interfaces sin entender las implicaciones.