Image: AI generated
Basta
pip install markitdownpara transformar PDFs, documentos do Word, planilhas do Excel e apresentações do PowerPoint em markdown. Mas dê a ele um PDF que não seja nada além de imagens digitalizadas — um contrato escaneado, por exemplo — e o resultado é um documento vazio. Este artigo segue duas linhas: por que e como usar o MarkItDown, e como acoplar OCR exatamente no ponto em que essa confiança se rompe — PDFs que contêm apenas imagens.
Por que usar o MarkItDown — a condição agent-operable
O MarkItDown é a biblioteca de CLI/Python de código aberto da Microsoft que converte arquivos PDF, Word, Excel, PowerPoint, HTML, imagem e áudio em markdown.
Comece pelo conceito de agent-operable (um estado com o qual um agente consegue trabalhar). Para que um agente lide com um documento de forma autônoma, esse documento precisa estar em um estado que uma máquina consiga analisar estruturalmente — não apenas em um estado que um humano ache legível. Essa não é uma condição que se aplica só ao código. Uma regra de negócio enterrada dentro de um PDF ou de uma planilha do Excel pode muito bem não existir para um agente, já que um humano precisa abri-la para saber o que ela diz. Esta é a primeira de três condições — legível, verificável, persistente — apresentadas em Construindo sistemas agent-operable: “legível sem ruído”.
Então por que o MarkItDown especificamente? Você poderia construir a mesma conversão sozinho, combinando parsers de terceiros (PyPDF2, python-docx). A diferença está em quem construiu. docx, xlsx e pptx são todos OOXML — uma especificação que a própria Microsoft possui. Quando a empresa dona da especificação constrói o conversor para o seu próprio formato, casos extremos (células mescladas, tabelas aninhadas, notas de rodapé) tendem estruturalmente a se manter mais confiáveis do que com um parser de terceiros. Se a conversão em si não for confiável, tudo o que se constrói em cima dela rumo a um estado agent-operable perde o sentido. Esse é o motivo para usar o MarkItDown — para os formatos próprios da Microsoft, essa confiança já vem embutida por padrão.
O problema é que essa confiança não se estende a PDFs, especialmente os que contêm apenas imagens. A segunda metade deste artigo se concentra em fechar essa lacuna com OCR.
Instalação
Instalação básica
pip install markitdown
Somente a instalação básica lida apenas com formatos leves, como texto simples e HTML. Para converter documentos PDF e do Office, são necessários os 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.
Se este artigo só precisar de PDF, markitdown[pdf] já é 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 Python
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("example.pdf")
print(result.markdown)
Ambas as abordagens selecionam automaticamente o conversor com base na extensão do arquivo ou no tipo MIME. Quando não há informação de extensão — ao ler de stdin, por exemplo — é possível passar uma dica com -x (--extension) ou -m (--mime-type).
O que a conversão de PDF realmente faz — ela só lê a camada de texto
O conversor de PDF do MarkItDown (packages/markitdown/src/markitdown/converters/_pdf_converter.py) primeiro usa o pdfplumber para detectar estrutura de tabela/formulário em cada página, e recorre a page.extract_text() quando uma página não é uma tabela. Se o pdfplumber falhar, ele recorre ao pdfminer.six.
Ambas as bibliotecas são ferramentas que leem uma camada de texto que já existe dentro do PDF. Se uma página consiste apenas em uma imagem digitalizada sem camada de texto, extract_text() retorna uma string vazia, e a saída do MarkItDown acaba efetivamente vazia. Não existe nenhum caminho de código no pacote base que renderize uma imagem ou reconheça caracteres nela.
Em outras palavras, são dois problemas diferentes:
- Um PDF com camada de texto (exportado do Word, a maioria dos contratos eletrônicos) → a instalação básica funciona bem. A mesma confiança que sustenta os formatos próprios da Microsoft se estende até aqui também.
- Um PDF sem camada de texto (um documento passado por um scanner, uma foto incorporada como imagem) → a instalação básica retorna um resultado vazio. É aqui que a condição “legível” do agent-operable se rompe.
Configurando o OCR — onde a confiança quebrada é reparada
É aqui que começa a segunda linha do artigo. A confiança que o MarkItDown garante para os formatos próprios da Microsoft precisa ser reconstruída separadamente para PDFs que contêm apenas imagens.
1. Instale o plugin
O suporte a OCR vive em um pacote separado, o markitdown-ocr. Ele está no mesmo repositório (microsoft/markitdown), mas não está incluído em pip install markitdown.
pip install markitdown-ocr
pip install openai # an OpenAI-compatible client — this plugin has no OCR model of its own
Depois de instalar, verifique se o plugin é reconhecido.
markitdown --list-plugins
# * ocr (package: markitdown_ocr)
2. O que é inegociável — um cliente de LLM Vision
O markitdown-ocr não embute um motor de OCR tradicional como Tesseract ou PaddleOCR. Tudo o que ele realmente faz é codificar a imagem em base64 e pedir a uma API chat.completions compatível com OpenAI (por exemplo, gpt-4o) para “extrair o texto desta imagem” (LLMVisionOCRService em _ocr_service.py). Por isso, uma chave de API capaz de chamar um modelo de visão é um requisito obrigatório para que o OCR funcione, ponto final.
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)
Qualquer cliente compatível com OpenAI — incluindo o Azure OpenAI — pode ser passado da mesma 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. Não funciona pela CLI — verificado no código real
O README do markitdown-ocr apresenta este comando como exemplo de uso:
markitdown document.pdf --use-plugins --llm-client openai --llm-model gpt-4o
Mas ao verificar diretamente as definições de argumentos da CLI do núcleo do MarkItDown (packages/markitdown/src/markitdown/__main__.py), fica claro que essas flags não existem — não há --llm-client nem --llm-model. A CLI suporta -o (arquivo de saída), -x (dica de extensão), -m (dica de MIME), -c (charset), -d/--use-docintel, --use-cu, -p/--use-plugins, --list-plugins e --keep-data-uris, e é só isso — o construtor de MarkItDown só recebe enable_plugins=args.use_plugins. Em outras palavras, o exemplo de CLI do README é um erro de documentação; ele simplesmente não funciona.
O OCR só funciona pela API Python — llm_client/llm_model precisam ser passados diretamente. Não há como entregar uma chave de API para a CLI.
4. Como funciona de fato — OCR de imagens incorporadas e fallback de OCR de página inteira
O PdfConverterWithOCR (_pdf_converter_with_ocr.py) opera em dois estágios.
- Imagens incorporadas primeiro: quando uma página mistura texto com imagens (digamos, uma assinatura digitalizada colocada no meio do corpo do texto), apenas a imagem é recortada, enviada ao LLM Vision, e o resultado é intercalado de volta com o texto ao redor pela sua posição Y original, preservando a ordem de leitura.
- Fallback de OCR de página inteira: se a extração de texto voltar completamente vazia (um PDF totalmente digitalizado), a página inteira é renderizada como PNG a 300dpi e enviada ao LLM Vision uma página de cada vez (
_ocr_full_pages).
O texto extraído é sempre envolvido neste formato:
*[Image OCR]
<extracted text>
[End OCR]*
5. Ajustando a precisão com um prompt personalizado
O prompt padrão é “extraia todo o texto desta imagem, preservando o layout e a ordem originais”. Para documentos cheios de tabelas, texto vertical, ou carimbos e assinaturas, sobrescrevê-lo com llm_prompt pode melhorar a precisão.
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. Processamento em lote de vários arquivos
Ao rodar uma pasta inteira de PDFs digitalizados de uma vez, cada arquivo dispara sua própria chamada de API — é preciso envolver cada conversão para que uma única falha não derrube o lote inteiro.
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. E se você instalar o plugin sem um llm_client?
O plugin carrega, mas o OCR se desliga silenciosamente. register_converters() em _plugin.py só constrói um LLMVisionOCRService quando llm_client e llm_model estão presentes juntos; caso contrário, registra os conversores com ocr_service=None. Nesse estado, ele recorre silenciosamente à extração apenas da camada de texto — sem erro, sem aviso. A maioria dos problemas de “instalei o plugin mas o OCR não funciona” remonta exatamente a esse ponto.
8. Ele até tenta recuperar PDFs corrompidos
Quando o pdfplumber e o pdfminer não conseguem abrir um PDF de jeito nenhum — um EOF truncado, digamos — o markitdown-ocr recorre a renderizar as páginas diretamente com o PyMuPDF (fitz) e tenta de novo. Se esse caminho também falhar, ele deixa *[Error: Could not process scanned PDF]* no resultado.
9. Custo e velocidade
Como envia páginas inteiras para um modelo de visão, o custo e a latência se acumulam proporcionalmente ao número de páginas do documento digitalizado. Uma renderização a 300dpi não é barata em tokens por imagem, então é mais seguro amostrar algumas páginas primeiro para avaliar qualidade e custo antes de rodar um documento inteiro com centenas de páginas.
Verificando o resultado do OCR — em breve, separadamente
Tudo o que foi visto até aqui trata de como “ligar” o OCR. Ligá-lo não é o mesmo que poder confiar no resultado — sob o design atual, em que tudo passa silenciosamente independentemente de llm_client estar configurado ou não, e independentemente de a página estar em branco ou não, não existe um procedimento determinístico para julgar se a saída do OCR é realmente confiável.
Esse procedimento não é abordado aqui. Como aplicar o princípio do Symbolic Feedback Loop — o LLM gera → uma ferramenta determinística julga → retroalimentação → repetir — à saída do OCR é assunto para um artigo/ferramenta separado.
Checklist prático
- Se você só lida com PDFs comuns que têm camada de texto, a instalação básica
markitdown[pdf]é tudo que você precisa. Nenhum plugin de OCR é necessário. - Se você precisa lidar com PDFs digitalizados ou fotografados,
markitdown-ocrmais uma chave de API da OpenAI (ou compatível) são obrigatórios. Não há integração com um motor de OCR tradicional, local e gratuito, como o Tesseract. - Se o resultado do OCR voltar vazio, a primeira coisa a verificar é se
llm_client/llm_modelforam realmente passados. Ele falha silenciosamente, então nada aparece nos logs. - O OCR não pode ser ativado só pela CLI. É necessário um script Python chamando
MarkItDown(enable_plugins=True, llm_client=..., llm_model=...). - Ainda não há uma forma mecânica de julgar quão confiável é o resultado — por enquanto, um humano precisa verificar uma amostra.
Tabela resumo
| Situação | O que você precisa | Notas |
|---|---|---|
| PDF com camada de texto | pip install markitdown[pdf] | pdfplumber/pdfminer extraem o texto |
| Algumas imagens incorporadas precisam de OCR | markitdown-ocr + cliente de LLM Vision | recorta só as imagens, intercala com o texto |
| Totalmente digitalizado (a página inteira é uma imagem) | markitdown-ocr + cliente de LLM Vision | renderiza a página inteira a 300dpi, depois aplica OCR |
| PDF corrompido | igual ao acima (fallback automático) | tenta de novo renderizando com PyMuPDF |
llm_client não configurado | — | OCR ignorado silenciosamente, sem erro |
Passar --llm-client na CLI | não é possível (essa flag não existe) | apenas API Python |
| Julgar a confiabilidade do resultado do OCR | (planejado) | será coberto por um verificador separado |
Artigos relacionados
- Construindo sistemas agent-operable — as três condições (legível, verificável, persistente), e onde o MarkItDown se encaixa
- Agent Operable Codebase — o mesmo princípio aplicado ao código
Fontes
- microsoft/markitdown — raiz do repositório
_pdf_converter.py— o conversor de PDF base (só extração da camada de texto)__main__.py— definições de argumentos da CLI (confirma que--llm-client/--llm-modelnão existem)markitdown-ocrREADME_pdf_converter_with_ocr.py_ocr_service.py_plugin.py- markitdown pyproject.toml — lista de extras
Changelog
- 2026-07-09: Versão inicial — adicionado o enquadramento agent-operable, configuração de OCR ampliada como segunda metade, adicionada uma seção reservada para o verificador