
MarkItDown 的插件机制拆到底OCR、音频转写、云端识别是怎么挂上去的【免费下载链接】markitdownPython tool for converting files and office documents to Markdown.项目地址: https://gitcode.com/GitHub_Trending/ma/markitdownMarkItDown 在 2026 年已经收获 10 万 Star社区里铺天盖地的教程都在讲一条命令把 PDF 转成 Markdown。但多数教程停在 CLI 用法很少有人回答一个更本质的问题一个号称支持 20 格式的工具为什么能把 OCR、音频转写、Azure 云端识别这些完全不同的能力拼进同一条转换链路而且彼此还能互相替换答案藏在它的插件体系里。本文直接进入源码从pyproject.toml的 extras 设计、entry_points发现机制、优先级抢占到_image_to_html钩子覆写和云端 converter 注册把 MarkItDown 的插件机制一层层拆开。拆完之后你会发现写一个属于自己的转换插件只需要三个文件、两个方法。从 pyproject.toml 说起extras 按需安装背后的模块化设计MarkItDown 的第一层插件机制其实藏在依赖管理里。看 packages/markitdown/pyproject.toml核心依赖只有六个beautifulsoup4、requests、markdownify、magika、charset-normalizer、defusedxml。也就是说裸装 MarkItDown 连 PDF 都读不了——真正的格式支持全部放在[project.optional-dependencies]里按需选装[project.optional-dependencies] pptx [python-pptx] docx [mammoth~1.11.0, lxml] xlsx [pandas, openpyxl] xls [pandas, xlrd] pdf [pdfminer.six20251230, pdfplumber0.11.9] outlook [olefile] audio-transcription [pydub, SpeechRecognition] youtube-transcription [youtube-transcript-api~1.2.3] az-doc-intel [azure-ai-documentintelligence, azure-identity] az-content-understanding [azure-ai-contentunderstanding1.2.0b1, azure-identity]这套设计不是随手为之。每个 converter 模块都遵循同一种延迟报错模式启动时用try/except ImportError捕获缺失依赖并把异常栈存进模块级变量转换时再抛出。以_audio_converter.py引用的 packages/markitdown/src/markitdown/converters/_transcribe_audio.py 为例_dependency_exc_info None try: import speech_recognition as sr import pydub except ImportError: _dependency_exc_info sys.exc_info()转换被真正触发时packages/markitdown/src/markitdown/_exceptions.py 中定义的MissingDependencyException会带着一段自解释的提示语抛出{converter} recognized the input as a potential {extension} file, but the dependencies needed to read {extension} files have not been installed. To resolve this error, include the optional dependency [{feature}] or [all] when installing MarkItDown.而_exceptions.py的注释点明了这种设计的用途依赖缺失不一定是致命错误——调度器会跳过这个 converter继续尝试下一个只有没有任何 converter 能处理时才会报UnsupportedFormatException。这正是所有能力能松散耦合、独立选装的地基每个 converter 对自己依赖的东西负责缺了就退场不影响别人。核心调度器一次 convert背后是优先级排队要把可选的能力变成可插拔的能力还需要一个统一的注册与调度中心就是 packages/markitdown/src/markitdown/_markitdown.py 里的MarkItDown类和_base_converter.py里的两个抽象契约DocumentConverter所有转换器的抽象基类子类必须实现accepts()根据StreamInfo判断是否接手和convert()产出DocumentConverterResult。StreamInfo携带mimetype、extension、charset、filename、url等元信息的不可变对象是accepts()做裁决的唯一依据。MarkItDown.__init__里会一次性注册 20 个内置 converter而convert()内部最终收敛到_convert()它的调度逻辑是整条链路的枢纽sorted_registrations sorted(self._converters, keylambda x: x.priority) for stream_info in stream_info_guesses [StreamInfo()]: for converter_registration in sorted_registrations: converter converter_registration.converter _accepts converter.accepts(file_stream, stream_info, **_kwargs) if _accepts: try: res converter.convert(file_stream, stream_info, **_kwargs) except Exception: failed_attempts.append(FailedConversionAttempt(...)) if res is not None: return res if len(failed_attempts) 0: raise FileConversionException(attemptsfailed_attempts) raise UnsupportedFormatException(...)这里有两个关键细节。其一转换按priority升序尝试排序是稳定的其二register_converter()实现为self._converters.insert(0, ...)注释明确写着后注册的先尝试。配合两个预置优先级常量PRIORITY_SPECIFIC_FILE_FORMAT 0.0 # 具体格式如 .docx、.pdf PRIORITY_GENERIC_FILE_FORMAT 10.0 # 兜底格式如 text/*PlainTextConverter、HtmlConverter、ZipConverter这类准通吃的 converter 排在 10.0具体格式的 converter 排在 0.0——越具体的越先被尝试。这套注册即生效、优先级定顺序的机制是后面所有替换内置行为操作的前提。OCR 插件负优先级抢占 钩子覆写社区教程里反复提到的扫描版 PDF OCR在 MarkItDown 里并不是内置功能而是一个独立的第三方包 packages/markitdown-ocr。它的接入方式最能体现插件机制的完整闭环。第一步通过 entry point 声明自己。在 packages/markitdown-ocr/pyproject.toml 里[project.entry-points.markitdown.plugin] ocr markitdown_ocr第二步实现register_converters()并注册。核心在 packages/markitdown-ocr/src/markitdown_ocr/_plugin.py。它接收 MarkItDown 实例读出用户传入的llm_client/llm_model这与内置ImageConverter做图片描述的参数完全一致构造 OCR 服务然后以priority-1.0注册四个增强 converterPRIORITY_OCR_ENHANCED -1.0 markitdown.register_converter( PdfConverterWithOCR(ocr_serviceocr_service), priorityPRIORITY_OCR_ENHANCED ) markitdown.register_converter( DocxConverterWithOCR(ocr_serviceocr_service), priorityPRIORITY_OCR_ENHANCED ) markitdown.register_converter( PptxConverterWithOCR(ocr_serviceocr_service), priorityPRIORITY_OCR_ENHANCED ) markitdown.register_converter( XlsxConverterWithOCR(ocr_serviceocr_service), priorityPRIORITY_OCR_ENHANCED )负优先级是关键一招内置 converter 都是 0.0-1.0 意味着这些 OCR 版本永远排在内置版本前面只要插件启用就自动接管不需要改任何内置代码。这就是插件替换内置行为的官方姿势。第三步PDF 走独立的三段式识别链路。packages/markitdown-ocr/src/markitdown_ocr/_pdf_converter_with_ocr.py 展示了 PDF 场景的完整兜底策略嵌入式图片 OCR用pdfplumber从页面提取图片对象依次尝试page.images、页面 XObjects、全对象过滤三种方法把图片流转成 PNG调用视觉 LLM 识别同时按字符的top/x0坐标把文本聚成行与 OCR 结果按 Y 坐标排序后交错输出保住从上到下的阅读顺序整页 OCR 兜底如果整份 PDF 提取不到任何文本典型的扫描件把每一页以 300 DPI 渲染成图片整页交给 LLMPyMuPDF 兜底连pdfplumber都打不开的损坏 PDF如截断的 EOF改用fitz渲染页面继续抢救。第四步Office 格式走钩子覆写。DOCX、PPTX、XLSX 的 OCR 实现方式和 PDF 完全不同靠的是覆写核心 converter 预留的_image_to_html钩子。核心的 packages/markitdown/src/markitdown/converters/_docx_converter.py 中这个钩子默认返回None保留原生图片但转换管线里有一行关键检测if type(self)._image_to_html is not DocxConverter._image_to_html: image_adapter _DocxImages(self._image_to_html, kwargs)子类覆写钩子后mammoth 提取出的每张嵌入图片都会流经_image_to_html返回的 HTML 片段OCR 文本被转义后包进pem[Image OCR]...br...[End OCR]/em/p被插入文档 HTML 流再走共享的 HTML→Markdown 渲染器。PPTX 端还做了 LLM 描述优先、OCR 兜底的顺序见 packages/markitdown/src/markitdown/converters/_pptx_converter.py 的_convert_picture_to_markdown。三个 Office 增强 converter如 packages/markitdown-ocr/src/markitdown_ocr/_docx_converter_with_ocr.py结构完全一致继承核心类、重写_image_to_html、用图片字节的 SHA-256 做单文档内去重缓存。识别本身则由 packages/markitdown-ocr/src/markitdown_ocr/_ocr_service.py 的LLMVisionOCRService完成图片转 base64 data URI走 OpenAI 兼容的chat.completions接口。插件只约定了OpenAI 兼容客户端这一个前提OpenAI、AzureOpenAI乃至 Gemini 兼容层都能用这也是它能蹭上 MarkItDown 既有llm_client参数体系的原因。云端识别Document Intelligence 与 Content Understanding 的接入点如果说 OCR 插件展示的是第三方如何抢占那么 Azure 两条云服务线展示的就是官方如何安插。两者都不是 entry point 插件而是内置 converter但它们的注册方式在 packages/markitdown/src/markitdown/_markitdown.py 的enable_builtins()里有讲究——只有传入 endpoint 时才注册且注册在最后栈顶docintel_endpoint kwargs.get(docintel_endpoint) if docintel_endpoint is not None: ... self.register_converter(DocumentIntelligenceConverter(**docintel_args)) cu_endpoint kwargs.get(cu_endpoint) if cu_endpoint is not None: ... self.register_converter(ContentUnderstandingConverter(**cu_args))由于register_converter是insert(0)且同优先级下后注册先尝试这两个云端 converter 天然排在内置 0.0 优先级之前一旦配置了 endpoint 就整体接管对应格式。packages/markitdown/src/markitdown/converters/_doc_intel_converter.py的DocumentIntelligenceConverter把 Azure Document Intelligence 包装成一个标准 converteraccepts()按file_types白名单裁决convert()固定调用prebuilt-layout模型对 Office 类型关闭 OCR features对 PDF/图片类型打开FORMULAS、OCR_HIGH_RESOLUTION、STYLE_FONT三个分析特性并直接用output_content_formatmarkdown让云端返回 Markdown最后用正则清掉!-- --注释。packages/markitdown/src/markitdown/converters/_cu_converter.py的ContentUnderstandingConverter更进一步——它把模态引入了路由PDF/DOCX/TXT/EML 等归为 documentJPEG/PNG/TIFF/HEIF 归为 image还有 video 和 audio 两个模态覆盖.mp4、.mov、.wav、.flac等几十种扩展名。转换时按模态自动选预置 analyzerdocument/image 用prebuilt-documentSearchvideo 用prebuilt-videoSearchaudio 用prebuilt-audioSearch若用户指定了自定义 analyzer初始化时还会通过get_analyzer()校验其基础模态是否与文件模态兼容不兼容就回落预置。最终结果经 CU SDK 的to_llm_input()序列化结构化字段以 YAML front matter 形式挂在 Markdown 前。两条云端链路在 CLI 上都有对应开关packages/markitdown/src/markitdown/main.py--use-docintel配合-e/--endpoint或MARKITDOWN_DOCINTEL_ENDPOINT和--use-cu配合--cu-endpoint、--cu-analyzer、--cu-file-types。本地、插件、云端三层能力在同一个调度器里共存互不感知。自己写一个 Converter 插件有多难扩展点全梳理官方在 packages/markitdown-sample-plugin 里提供了一个最小插件做参考。写一个插件只需要三样东西1. entry point 声明pyproject.toml[project.entry-points.markitdown.plugin] sample_plugin markitdown_sample_plugin2. 一个带register_converters(markitdown, **kwargs)的模块packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.pydef register_converters(markitdown: MarkItDown, **kwargs): markitdown.register_converter(RtfConverter())3. 一个实现accepts()和convert()的DocumentConverter子类class RtfConverter(DocumentConverter): def accepts(self, file_stream, stream_info, **kwargs): extension (stream_info.extension or ).lower() if extension in ACCEPTED_FILE_EXTENSIONS: return True mimetype (stream_info.mimetype or ).lower() for prefix in ACCEPTED_MIME_TYPE_PREFIXES: if mimetype.startswith(prefix): return True return False def convert(self, file_stream, stream_info, **kwargs): encoding stream_info.charset or locale.getpreferredencoding() stream_data file_stream.read().decode(encoding) return DocumentConverterResult(markdownrtf_to_text(stream_data))背后是调度器提供的全套基建。插件的发现由 packages/markitdown/src/markitdown/_markitdown.py 的_load_plugins()完成通过importlib.metadata.entry_points(groupmarkitdown.plugin)懒加载单个插件加载失败只warn跳过绝不影响整体enable_plugins()把用户所有 kwargs 转发给每个插件的register_converters所以 OCR 插件能拿到llm_client其他插件也能拿到自定义选项CLI 的--use-plugins开启、--list-plugins列出已装插件。accepts()的实现还有一条纪律如果需要读流做判断如 Outlook 的复合容器格式读完必须把指针复位因为accepts()返回后convert()会立刻在同一位置开始读。优先级是你可用的最后一个自由度三种典型用法抢占替换priority-1.0跑在所有内置 converter 前面OCR 插件的做法精准兜底priority9.0在PlainTextConverter10.0之前、具体格式0.0之后只兜住没人接手的格式新格式接入默认 0.0与既有具体格式公平竞争靠后注册先尝试获得优先。写在最后MarkItDown 的插件机制其实只有三个设计决策extras 把依赖做成可选项entry point 把能力做成可发现priority 把顺序做成可抢占。其余的一切——StreamInfo的类型感知、_image_to_html的渲染钩子、云端 converter 的有 endpoint 才注册——都是这三个决策的延伸。理解了这条链路你再看社区里那些MarkItDown 配合 OCR 处理扫描件接入 Azure CU 提升精度的教程本质上都是在同一套机制上做排列组合。而当你需要接入一种仓库不支持的格式时不需要 fork 主项目写一个DocumentConverter子类、声明一个 entry point挂上去即可——这就是一个成熟开源项目该有的生态位设计。【免费下载链接】markitdownPython tool for converting files and office documents to Markdown.项目地址: https://gitcode.com/GitHub_Trending/ma/markitdown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考