Installer et utiliser MarkItDown Image: AI generated

pip install markitdown suffit à transformer PDF, documents Word, feuilles Excel et présentations PowerPoint en markdown. Mais donnez-lui un PDF qui n’est rien d’autre que des images scannées — un contrat scanné, par exemple — et vous obtenez un document vide. Cet article suit deux axes : pourquoi et comment utiliser MarkItDown, et comment greffer l’OCR précisément là où cette confiance se brise — les PDF composés uniquement d’images.


Pourquoi utiliser MarkItDown — la condition agent-operable

MarkItDown est la bibliothèque open source de Microsoft pour CLI/Python qui convertit les fichiers PDF, Word, Excel, PowerPoint, HTML, image et audio en markdown.

Commençons par le concept d’agent-operable (un état avec lequel un agent peut travailler). Pour qu’un agent traite un document de façon autonome, ce document doit se trouver dans un état qu’une machine peut analyser structurellement — pas simplement dans un état qu’un humain trouve lisible. Ce n’est pas une condition qui s’applique uniquement au code. Une règle métier enfouie dans un PDF ou une feuille Excel équivaut à ne pas exister pour un agent, puisqu’un humain doit l’ouvrir pour savoir ce qu’elle contient. C’est la première des trois conditions — lisible, vérifiable, persistant — exposées dans Building Agent-Operable Systems : « lisible sans bruit ».

Alors pourquoi MarkItDown en particulier ? On pourrait construire soi-même la même conversion en assemblant des parseurs tiers (PyPDF2, python-docx). La différence tient à qui l’a construit. docx, xlsx et pptx sont tous de l’OOXML — une spécification que Microsoft possède elle-même. Quand l’entreprise propriétaire de la spécification construit le convertisseur pour son propre format, les cas limites (cellules fusionnées, tableaux imbriqués, notes de bas de page) ont structurellement plus de chances de tenir la route qu’avec un parseur tiers. Si la conversion elle-même n’est pas fiable, tout ce qu’on construit par-dessus vers un état agent-operable perd son sens. C’est la raison d’utiliser MarkItDown — pour les formats propres à Microsoft, cette confiance est intégrée par défaut.

Le problème est que cette confiance ne se transmet pas aux PDF, en particulier ceux composés uniquement d’images. La seconde moitié de cet article se concentre sur la façon de combler cet écart avec l’OCR.


Installation

Installation de base

pip install markitdown

L’installation de base seule ne gère que des formats légers comme le texte brut et le HTML. Pour convertir des documents PDF et Office, il faut installer les extras spécifiques à chaque format.

Extras spécifiques par format

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 cet article n’a besoin que du PDF, markitdown[pdf] suffit.


Utilisation de base

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 Python

from markitdown import MarkItDown

md = MarkItDown()
result = md.convert("example.pdf")
print(result.markdown)

Les deux approches sélectionnent automatiquement le convertisseur selon l’extension du fichier ou le type MIME. Quand il n’y a pas d’information d’extension — en lisant depuis stdin, par exemple — on peut donner un indice avec -x (--extension) ou -m (--mime-type).


Ce que fait réellement la conversion PDF — elle ne lit que la couche de texte

Le convertisseur PDF de MarkItDown (packages/markitdown/src/markitdown/converters/_pdf_converter.py) utilise d’abord pdfplumber pour détecter la structure de tableau/formulaire de chaque page, et se rabat sur page.extract_text() quand une page n’est pas un tableau. Si pdfplumber échoue, il se rabat sur pdfminer.six.

Les deux bibliothèques sont des outils qui lisent une couche de texte déjà existante à l’intérieur du PDF. Si une page ne consiste qu’en une image scannée sans couche de texte, extract_text() renvoie une chaîne vide, et la sortie de MarkItDown finit par être effectivement vide. Il n’existe aucun chemin de code dans le paquet de base qui restitue une image ou reconnaît des caractères à l’intérieur.

Autrement dit, ce sont deux problèmes différents :

  • Un PDF avec couche de texte (exporté depuis Word, la plupart des contrats électroniques) → l’installation de base fonctionne bien. La même confiance qui porte les formats propres à Microsoft se transmet ici aussi.
  • Un PDF sans couche de texte (un document passé au scanner, une photo intégrée comme image) → l’installation de base renvoie un résultat vide. C’est ici que la condition « lisible » d’agent-operable se brise.

Configurer l’OCR — là où la confiance brisée est réparée

C’est ici que commence le second axe de l’article. La confiance que MarkItDown assure pour les formats propres à Microsoft doit être reconstruite séparément pour les PDF composés uniquement d’images.

1. Installer le plugin

Le support OCR se trouve dans un paquet séparé, markitdown-ocr. Il est dans le même dépôt (microsoft/markitdown), mais n’est pas inclus dans pip install markitdown.

pip install markitdown-ocr
pip install openai   # an OpenAI-compatible client — this plugin has no OCR model of its own

Après l’installation, vérifiez que le plugin est détecté.

markitdown --list-plugins
#   * ocr             (package: markitdown_ocr)

2. Ce qui n’est pas négociable — un client LLM Vision

markitdown-ocr n’intègre pas de moteur OCR traditionnel comme Tesseract ou PaddleOCR. Tout ce qu’il fait réellement, c’est encoder l’image en base64 et demander à une API chat.completions compatible OpenAI (par exemple gpt-4o) d’« extraire le texte de cette image » (LLMVisionOCRService dans _ocr_service.py). Par conséquent, une clé API capable d’appeler un modèle de vision est une exigence incontournable pour que l’OCR fonctionne, tout simplement.

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)

N’importe quel client compatible OpenAI — Azure OpenAI y compris — peut être fourni de la même manière.

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. Ça ne fonctionne pas depuis la CLI — vérifié sur le code réel

Le README de markitdown-ocr donne cette commande comme exemple d’usage :

markitdown document.pdf --use-plugins --llm-client openai --llm-model gpt-4o

Mais en vérifiant directement les définitions d’arguments de la CLI du cœur de MarkItDown (packages/markitdown/src/markitdown/__main__.py), on constate que ces drapeaux n’existent pas — il n’y a ni --llm-client ni --llm-model. La CLI prend en charge -o (fichier de sortie), -x (indice d’extension), -m (indice MIME), -c (charset), -d/--use-docintel, --use-cu, -p/--use-plugins, --list-plugins et --keep-data-uris, et c’est tout — le constructeur de MarkItDown ne reçoit que enable_plugins=args.use_plugins. Autrement dit, l’exemple de CLI du README est une erreur de documentation ; il ne fonctionne en réalité pas.

L’OCR ne fonctionne que via l’API Python — il faut passer llm_client/llm_model directement. Il n’existe aucun moyen de transmettre une clé API à la CLI.

4. Comment ça fonctionne réellement — OCR des images intégrées et repli sur l’OCR de page entière

PdfConverterWithOCR (_pdf_converter_with_ocr.py) fonctionne en deux étapes.

  1. Images intégrées en priorité : quand une page mélange texte et images (disons une signature scannée déposée au milieu du corps du texte), seule l’image est découpée, envoyée à LLM Vision, et le résultat est réintercalé avec le texte environnant selon sa position Y d’origine, en préservant l’ordre de lecture.
  2. Repli sur l’OCR de page entière : si l’extraction de texte revient complètement vide (un PDF entièrement scanné), la page entière est restituée en PNG à 300dpi et envoyée à LLM Vision une page à la fois (_ocr_full_pages).

Le texte extrait est toujours enveloppé dans ce format :

*[Image OCR]
<extracted text>
[End OCR]*

5. Affiner la précision avec un prompt personnalisé

Le prompt par défaut est « extrais tout le texte de cette image, en conservant la mise en page et l’ordre d’origine ». Pour les documents riches en tableaux, en texte vertical, ou comportant des tampons et signatures, le remplacer via llm_prompt peut améliorer la précision.

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. Traitement par lots de plusieurs fichiers

En exécutant tout un dossier de PDF scannés d’un coup, chaque fichier déclenche son propre appel API — il faut envelopper chaque conversion pour qu’un seul échec ne fasse pas échouer tout le lot.

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. Que se passe-t-il si vous installez le plugin sans llm_client ?

Le plugin se charge, mais l’OCR se désactive silencieusement. register_converters() dans _plugin.py ne construit un LLMVisionOCRService que lorsque llm_client et llm_model sont tous deux présents ; sinon, il enregistre les convertisseurs avec ocr_service=None. Dans cet état, on retombe silencieusement sur l’extraction de la seule couche de texte — sans erreur, sans avertissement. La plupart des problèmes du type « j’ai installé le plugin mais l’OCR ne fonctionne pas » remontent exactement à ce point.

8. Il tente même de récupérer les PDF corrompus

Quand pdfplumber et pdfminer ne parviennent absolument pas à ouvrir un PDF — un EOF tronqué, disons — markitdown-ocr se rabat sur le rendu direct des pages avec PyMuPDF (fitz) et réessaie. Si ce chemin échoue aussi, il laisse *[Error: Could not process scanned PDF]* dans le résultat.

9. Coût et vitesse

Comme il envoie des pages entières à un modèle de vision, le coût et la latence s’accumulent proportionnellement au nombre de pages du document scanné. Un rendu à 300dpi n’est pas bon marché en tokens par image, il est donc plus prudent d’échantillonner d’abord quelques pages pour évaluer qualité et coût avant de traiter un document entier de plusieurs centaines de pages.


Vérifier le résultat de l’OCR — à venir, séparément

Tout ce qui précède concerne la façon d’« activer » l’OCR. L’activer n’équivaut pas à pouvoir faire confiance au résultat — dans la conception actuelle, où tout passe silencieusement que llm_client soit configuré ou non, et que la page soit vide ou non, il n’existe aucune procédure déterministe pour juger si la sortie OCR est réellement fiable.

Cette procédure n’est pas couverte ici. La façon d’appliquer le principe du Symbolic Feedback Loop — le LLM génère → un outil déterministe juge → rétroaction → répétition — à la sortie OCR fera l’objet d’un article/outil séparé.


Liste de vérification pratique

  • Si vous ne traitez que des PDF ordinaires avec couche de texte, l’installation de base markitdown[pdf] suffit. Aucun plugin OCR n’est nécessaire.
  • Si vous devez traiter des PDF scannés ou photographiés, markitdown-ocr plus une clé API OpenAI (ou compatible) sont obligatoires. Il n’existe aucune intégration avec un moteur OCR traditionnel, local et gratuit, comme Tesseract.
  • Si le résultat OCR revient vide, la première chose à vérifier est si llm_client/llm_model ont réellement été passés. L’échec est silencieux, donc rien n’apparaît dans les journaux.
  • L’OCR ne peut pas être activé depuis la seule CLI. Un script Python appelant MarkItDown(enable_plugins=True, llm_client=..., llm_model=...) est nécessaire.
  • Il n’existe pas encore de moyen mécanique de juger de la fiabilité du résultat — pour l’instant, un humain doit vérifier un échantillon.

Tableau récapitulatif

SituationCe qu’il fautNotes
PDF avec couche de textepip install markitdown[pdf]pdfplumber/pdfminer extraient le texte
Certaines images intégrées nécessitent l’OCRmarkitdown-ocr + client LLM Visiondécoupe seulement les images, les intercale avec le texte
Entièrement scanné (toute la page est une image)markitdown-ocr + client LLM Visionrestitue la page entière à 300dpi, puis applique l’OCR
PDF corrompuidentique ci-dessus (repli automatique)réessaie en restituant avec PyMuPDF
llm_client non configuréOCR ignoré silencieusement, sans erreur
Passer --llm-client en CLIimpossible (ce drapeau n’existe pas)uniquement via l’API Python
Juger de la fiabilité du résultat OCR(prévu)sera couvert par un vérificateur séparé

Articles liés


Sources

Journal des modifications

  • 2026-07-09 : Version initiale — ajout du cadrage agent-operable, configuration de l’OCR développée comme seconde moitié, ajout d’une section provisoire pour le vérificateur