markitdown:把 PDF、Word、Excel 统一转成 Markdown 喂给 LLM
微软开源的 Python 工具,把 PDF、Word、Excel 等文档转成 Markdown,为 RAG 与文档问答做前置清洗。
RAG 系统里最脏的活,从来不是检索排序,也不是 prompt 调优,而是把一堆格式各异的文件变成模型能读的文本。PDF 里套着表格,Word 里嵌着图片,Excel 有多张 sheet,PPT 的正文散落在文本框里——这些结构一旦被粗暴地抽成纯文本,标题层级、列表、表格行列关系全部塌掉,后面再怎么切 chunk、怎么召回,都是在垃圾上做精排。
markitdown 就是冲着这一层来的。它不碰检索,不碰编排,只做一件事:把各种文件转成 Markdown,并且尽量保留文档结构。README 里对标的对象是 textract,但它明确写了差异点——focus on preserving important document structure and content as Markdown(including: headings, lists, tables, links, etc.)。这个定位很克制,也很关键。
为什么偏偏是 Markdown
README 里给的理由值得单独拎出来:Markdown 极接近纯文本,标记极少,但仍能表达标题、列表、表格、链接这些结构。更实际的一点是,主流 LLM(README 点名了 GPT-4o)本身就"说" Markdown,经常在回答里不请自来地用 Markdown 格式,说明训练数据里有大量 Markdown 文本,模型理解它没什么障碍。附带好处是 token 效率高。
所以 markitdown 的目标读者不是人,是文本分析工具。README 写得很直白:输出通常还算可读、对人类友好,但它是给 text analysis tools 消费的,不是做高保真文档转换的最佳选择。这句话决定了它的能力边界——它不会为了排版还原去死磕,而是为了"模型能读懂结构"做取舍。
架构:一个分发器 + 一组格式转换器 + 三层可选增强
README 没有给架构图,但从它的功能描述、CLI 参数、Python API 和可选依赖的组织方式,可以清楚归纳出它的分层。核心是一个转换分发逻辑:你丢进来一个文件,它判断类型,挑一个能处理该类型的 converter,跑完返回一个带 markdown 属性的结果对象。
第一层:入口层。 两条路径,CLI 和 Python API。CLI 是 markitdown path-to-file.pdf,支持 -o 输出到文件,也支持管道(cat file.pdf | markitdown)。Python 侧是 MarkItDown() 实例加 md.convert(...)。入口层还负责解析一堆开关:--use-plugins、--list-plugins、-d(Document Intelligence)、--use-cu(Content Understanding)等。README 的安全提示也落在这一层:MarkItDown 以当前进程的权限做 I/O,和 open()、requests.get() 一样能访问进程能访问的资源,所以在不可信环境里要自己清洗输入,并且**调用范围最窄的 convert_* 函数**(比如 convert_stream() 或 convert_local()),而不是无差别地丢任意路径进去。
第二层:converter 层(内置转换器)。 这是项目的主体。README 列出的支持格式覆盖了 PDF、PowerPoint、Word、Excel、Images(EXIF 元数据 + OCR)、Audio(EXIF 元数据 + 语音转写)、HTML、文本类格式(CSV/JSON/XML)、ZIP(遍历内部内容)、YouTube URL、EPub 等。每个格式对应一个 converter,各自带自己的解析依赖。这一层是离线、本地计算的,也是默认路径。
第三层:可选增强层。 这一层是 markitdown 真正的架构看点,它把一个"格式转换库"扩展成了"可插拔的转换管线",有四个并行的增强入口:
| 增强入口 | 触发方式 | 作用 | 依赖 |
|---|---|---|---|
| 可选依赖(extras) | pip install 'markitdown[pdf, docx, pptx]' | 按格式装依赖,控制体积 | 各格式独立 |
| 第三方插件 | --use-plugins / enable_plugins=True | 扩展 converter 能力,默认关闭 | 插件包 |
| LLM 视觉 | llm_client + llm_model | 图片描述,目前仅 pptx 与图片文件 | OpenAI 兼容客户端 |
| 云端转换 | docintel_endpoint / cu_endpoint | 更高精度的版面分析、结构化字段、音视频 | Azure 服务(计费) |
这四层不是互斥的,是叠加的。一个 PDF 可以走内置 converter,也可以被 cu_file_types 限定路由到 Content Understanding;一个带图的 PDF 可以同时挂 markitdown-ocr 插件和 llm_client。
数据流转大致是:文件 → 类型判定 → 选 converter(内置 / 插件 / 云端,取决于开关)→ 转换 → 返回结果对象 → 取 .markdown。失败时的行为也写清楚了:如果 LLM 客户端重试耗尽或遇到不可重试错误,MarkItDown 会尝试其他适用的 converter,只有全部失败才抛 FileConversionException。这是一个 fallback 链的设计,不是单点失败即终止。
可选依赖的粒度
值得单独说,因为这是它控制安装体积的手段。[all] 全装,也可以按需:
[pptx]/[docx]/[xlsx]/[xls]/[pdf]/[outlook]:各格式独立[az-doc-intel]/[az-content-understanding]:Azure 集成[audio-transcription]:wav 和 mp3 的语音转写[youtube-transcription]:抓 YouTube 视频转写
只处理 PDF 和 Office 三件套的话,pip install 'markitdown[pdf, docx, pptx]' 就够了,不用背全部依赖。
跑起来:从装到出 Markdown
安装:
bashpip install 'markitdown[all]'
或者从源码装:
bashgit clone git@github.com:microsoft/markitdown.git cd markitdown pip install -e 'packages/markitdown[all]'
CLI 最小用法,三种写法都行:
bashmarkitdown path-to-file.pdf > document.md markitdown path-to-file.pdf -o document.md cat path-to-file.pdf | markitdown
Python API 最小用法:
pythonfrom markitdown import MarkItDown md = MarkItDown(enable_plugins=False) # Set to True to enable plugins result = md.convert("test.xlsx") print(result.markdown)
注意 enable_plugins=False 是默认值——插件默认关闭,要用得显式打开。
挂 LLM 做图片描述(目前只支持 pptx 和图片文件):
pythonfrom markitdown import MarkItDown from openai import OpenAI client = OpenAI(max_retries=5) md = MarkItDown(llm_client=client, llm_model="gpt-4o", llm_prompt="optional custom prompt") result = md.convert("example.jpg") print(result.markdown)
max_retries 控制 OpenAI 客户端的自动重试,README 说默认是 2,设成 5 意味着最多六次尝试带退避。
Docker 方式:
shdocker build -t markitdown:latest . docker run --rm -i markitdown:latest < ~/your-file.pdf > output.md
插件机制:OCR 是怎么"零依赖"加进来的
插件默认关闭,markitdown --list-plugins 列出已装的,markitdown --use-plugins path-to-file.pdf 启用。找插件搜 GitHub 的 #markitdown-plugin 标签,开发插件看 packages/markitdown-sample-plugin。
README 里详细展开的是官方 markitdown-ocr 插件,它很能说明这套架构的复用逻辑:给 PDF、DOCX、PPTX、XLSX 四个 converter 加上 OCR,从嵌入图片里抽文本,用的是 LLM Vision——也就是 markitdown 本来就用于图片描述的同一套 llm_client / llm_model 模式。它明确写了 no new ML libraries or binary dependencies required,不引入新的 ML 库或二进制依赖。
bashpip install markitdown-ocr pip install openai # or any OpenAI-compatible client
pythonfrom markitdown import MarkItDown from openai import OpenAI md = MarkItDown( enable_plugins=True, llm_client=OpenAI(), llm_model="gpt-4o", ) result = md.convert("document_with_images.pdf") print(result.markdown)
一个细节值得注意:如果没提供 llm_client,插件仍然加载,但 OCR 会被静默跳过,回退到内置的标准 converter。这种"软降级"设计和前面说的 fallback 链是一致的——它优先保证转换能出结果,而不是因为某个增强能力缺失就整体失败。
三档转换质量:内置 / Doc Intel / Content Understanding
这是 markitdown 最容易被低估的部分。它不是只有"本地转换"一档,而是给了三档,README 用一张表把差异摆得很清楚:
| 能力 | 内置 converter | Azure Document Intelligence | Azure Content Understanding |
|---|---|---|---|
| 文档转换 | 离线,按格式抽取 | 云端版面抽取 | 云端多模态抽取 |
| 结构化字段 | 不支持 | 该集成未暴露 | analyzer 字段输出为 YAML front matter |
| 自定义 analyzer | 不支持 | 该集成不可配置 | 支持 cu_analyzer_id |
| 音频与视频 | 基础音频,无视频 | 不支持 | 音频与视频 analyzer |
| 成本 | 仅本地计算 | Azure API 计费 | Azure API 计费 |
三档的定位差异是清晰的:
- 内置:离线、免费、按格式抽取。覆盖广,但扫描版 PDF、复杂表格、多页文档的抽取质量有限,也没有结构化字段。
- Document Intelligence:云端版面抽取,CLI 用
-d -e "<endpoint>",或设MARKITDOWN_DOCINTEL_ENDPOINT环境变量后只传-d。Python 侧是MarkItDown(docintel_endpoint=...)。它提升了版面质量,但这个集成不暴露结构化字段,也不支持自定义 analyzer。 - Content Understanding:README 说它是唯一能处理视频的选项,也是音频更高质量的云端选项。它支持 prebuilt 或自定义 analyzer,把领域字段(发票金额、收据日期、合同条款)抽出来序列化成 YAML front matter。一个
cu_endpoint处理文档、图片、音频、视频,自动路由 analyzer。
Content Understanding 的零配置用法:
pythonfrom markitdown import MarkItDown md = MarkItDown(cu_endpoint="<content_understanding_endpoint>") result = md.convert("report.pdf") # documents → prebuilt-documentSearch result = md.convert("meeting.mp4") # video → prebuilt-videoSearch result = md.convert("call.wav") # audio → prebuilt-audioSearch print(result.markdown)
自定义 analyzer 抽字段:
pythonmd = MarkItDown( cu_endpoint="<content_understanding_endpoint>", cu_analyzer_id="my-invoice-analyzer", ) result = md.convert("invoice.pdf") print(result.markdown)
输出会带上 YAML front matter,形如:
---
contentType: document
fields:
VendorName: CONTOSO LTD.
InvoiceDate: '2019-11-15'
---
<!-- page 1 -->
...
CLI 侧对应 --use-cu --cu-endpoint "<endpoint>",endpoint 也可以先 export MARKITDOWN_CU_ENDPOINT=... 再只传 --use-cu。
两个设计细节值得记:当设了 cu_analyzer_id,converter 会按 analyzer 的模态自动把它限定到兼容的文件类型,不兼容的(比如用文档 analyzer 处理音频)自动路由到默认 prebuilt analyzer。另外,每个走 CU 的 convert() 都是一次计费的 Azure 调用,所以提供了 cu_file_types 来限制哪些格式走 CU:
pythonfrom markitdown.converters import ContentUnderstandingFileType md = MarkItDown( cu_endpoint="<content_understanding_endpoint>", cu_file_types=[ContentUnderstandingFileType.PDF], # only PDFs use CU )
这个参数的存在本身就说明维护者清楚成本是真实约束,不是所有文件都值得上云。
和同主题项目比,它站在哪一格
把 markitdown 放进这批对照项目里,定位差异一目了然——它们大多在下游,markitdown 在上游。
- headroom 在内容送进模型前压缩 tool 输出、日志、文件和 RAG chunk 来省 token。它和 markitdown 是接力关系:markitdown 负责把文档变成结构化的 Markdown,headroom 负责把已经变成文本的东西再压瘦。一个管"能不能读",一个管"读得贵不贵"。
- litellm 是 AI Gateway,统一接口调 100 多个 LLM API,做路由、负载均衡、成本追踪。markitdown 里的
llm_client恰恰可以是任意 OpenAI 兼容客户端,两者不在一个层面,但可以组合——markitdown 用 litellm 背后的模型做图片描述或 OCR。 - Langchain-Chatchat 是本地知识库问答应用,解决私有文档接入 LLM。它需要前置的文档清洗,markitdown 正是这一步的候选工具:先转 Markdown,再建库、检索、作答。README 的 topics 里直接列了
langchain和autogen,说明它被设计成能嵌进这类编排栈。 - langfuse 做追踪、评估、调试,关心上线后回答为什么变差。它不解决输入格式问题,但转换质量差会直接反映在它的评估指标上。
- deepagents 是 LangChain 的 agent harness,内置任务规划、文件操作、子 agent。agent 要读文件,就需要一个把各种格式变成可读文本的层,markitdown 补的正是这个空。
一句话概括:这批项目里,markitdown 是唯一专注"输入侧格式归一"的那个。它不做编排、不做网关、不做评估,只把脏输入洗干净,交给下游。
它适合谁,以及几个必须知道的约束
适合:搭 RAG 或文档问答、需要把 PDF/Office/HTML/EPub 等混合资料统一成 Markdown 再切 chunk 的团队;需要把图片、音频、YouTube 转写也纳入同一管线的场景(走插件或 CU);想在本地离线跑、不想为每次转换付费的场合(内置 converter)。
必须知道的约束:
- 不是高保真转换器。README 明说输出是给文本分析工具消费的,人类阅读的高保真转换不是它的目标。
- 安全边界等于进程边界。它以当前进程权限做 I/O,不可信环境要清洗输入,并调用最窄的
convert_*函数(convert_stream()、convert_local()),别把任意路径直接喂进去。 - 插件默认关闭,要用得显式开;
markitdown-ocr在缺llm_client时会静默跳过 OCR,回退内置 converter——如果你以为 OCR 生效了但其实没有,输出里不会报错。 - 云端转换计费。Doc Intel 和 CU 都是按调用计费的 Azure API,CU 还提供了
cu_file_types让你限定范围。 - Python 版本要求 3.10 到 3.14,README 建议用虚拟环境避免依赖冲突。
项目基本面
| 项 | 值 |
|---|---|
| 源码 | github.com/microsoft/markitdown |
| 许可 | MIT |
| 语言 | Python |
| 星标 | 190788 |
| forks | 14191 |
| open issues | 737 |
| topics | autogen, autogen-extension, langchain, markdown, microsoft-office, openai, pdf |
| 仓库创建 | 2024-11-13 |
| 最近推送 | 2026-10-04 |
官网地址:素材中未提供,故不列出。
MIT 许可、微软维护、近 19 万星标——这些数字说明它已经成了"文档转 Markdown 喂 LLM"这个细分动作的事实默认选项。但真正决定它价值的不是星标,而是那套分层设计:内置 converter 保证离线可用和格式广度,插件机制让 OCR 这类能力以零新依赖的方式接入,LLM 视觉和 Azure 云端转换则把质量上限往上抬了两档。你可以只用第一层,也可以按需叠到第四层,而 fallback 链保证任何一层出问题时转换仍尽量出结果。
对一个前置清洗工具来说,这种"默认能用、按需增强、失败可降级"的结构,比任何单点精度都更重要。