文档解析引擎支持哪些格式,各走哪条路
把一堆 PDF、讲义、试卷丢给一个学习类项目,第一件发生的事不是检索,是解析。解析这一层出问题,后面的切块、向量化、出题全都在错的文本上跑,而且报错往往不在解析层,会跑到很后面才炸出来。所以读 DeepTutor 这种吃文档的项目,deeptutor/services/parsing/ 值得比 rag/ 更早看一眼。
下面所有行号、常量名与数字都对应我们采集的仓库快照:HEAD 456f9c2,采集日 2026-08-10,deeptutor/__version__.py 里的版本号是 1.5.11。你自己 clone 之后行号可能漂了,但文件名和常量名是稳的,照着搜即可。
一、这一层多大,自己怎么定位
deeptutor/services/parsing/ 下是 29 个 Python 文件、2863 行。放在 services 的 26 个一级子领域里排第八——前面是 llm 9263 行、rag 7539 行、session 6677 行、memory 6332 行、config 4930 行、subagent 3638 行、partners 3159 行。也就是说它不算大块头,但也不是个几百行的小工具。
它给自己的定位写在 deeptutor/services/parsing/__init__.py:1-6 的模块 docstring 里:一个可插拔引擎的文档解析层,docstring 里直接给了个外号叫 “the bridge”;对外只暴露一个规范 IR ParsedDocument,配一份内容寻址缓存,底下挂一张可插拔引擎的注册表。
这个结构决定了读法:真正需要你搞清楚的不是某个解析器怎么解,而是这张注册表上挂着哪几条路,你现在走的是哪一条。
这一层被谁用?按我们对跨层引用的统计,api 层引用 deeptutor.services.parsing 共 15 处,在 api 引用最多的 services 子模块里排在 config(34)、rag(23)、memory(23)之后,和 llm(15)并列。解析层不是个内部小工具,它是有对外接口的。
二、五个引擎名,两处一一对应
引擎的名字常量定义在 config 层,不在 parsing 目录里——deeptutor/services/config/runtime_settings.py:93-97 一口气定义了五个:
| 引擎名常量取值 | 注册表里对应的类 |
|---|---|
text_only | TextOnlyParser |
mineru | MinerUParser |
docling | DoclingParser |
markitdown | MarkItDownParser |
pymupdf4llm | PyMuPDF4LLMParser |
左列那五个常量随后被收进 _DOCUMENT_PARSING_ENGINES 这个 frozenset(runtime_settings.py:98-106),充当合法取值集合。右列在 deeptutor/services/parsing/engines/factory.py:25-62 的 _ENGINE_LOADERS 字典里,键就是左列那五个常量,值是返回引擎类的加载器;紧接着 KNOWN_ENGINES = frozenset(_ENGINE_LOADERS)(factory.py:64)。
这里有个容易看反的地方:这五个名字的定义并不落在 parsing 目录里,而在 config 层,parsing 这边 _ENGINE_LOADERS 用的就是这五个取值。所以你要确认”到底有几条路”,得同时看两处:config 那边的 frozenset 是校验用的合法集合,parsing 这边的 _ENGINE_LOADERS 才是真正能被实例化出来的映射。两处任何一处多一个少一个,表现出来就是”配置能写进去但跑不起来”或者”代码里有类但选不到”。我们这次核对的快照上,两边都是五个,一一对上。
三、本篇最反直觉的一处:注册表里有名字,不等于这条路能走
factory.py:1-7 的文件头 docstring 写了三句话,第三句是关键:这张注册表镜像了 RAG 的管线工厂(services/rag/factory.py);引擎模块懒加载各自的第三方依赖;因此导入这张注册表本身是廉价的,而且永远不会因为缺少某个可选依赖而失败。
按常识,一张列出五个引擎的注册表,读者的第一反应是”这五种我都能用”。但按这段注释的语义,恰恰相反:导入注册表不失败,正是因为它此刻根本没有去碰任何一个引擎的第三方依赖。_ENGINE_LOADERS 的值不是类本身,是五个”调用时才 import 对应模块”的加载器函数。缺依赖这件事不会在导入期暴露,会被推迟到你真正把某个引擎实例化的时候。
这条设计的直接后果有两个,都很具体:
其一,KNOWN_ENGINES 是个恒定的五元集合,跟你机器上装了什么无关。 任何”列出可用引擎”的地方,只要它读的是这张注册表,列出来的就一定是五个。清单长度不随环境变化,因此不能拿它当”这五条路我都能跑”的证据。
其二,出错时机会往后挪。 排查”换了引擎就不工作”时,别在导入链和注册表上找原因——那里按注释语义就是不会报错的。该看的是引擎真正被加载的那一刻,以及对应的第三方包在不在环境里。
顺带说明边界:各引擎具体接受哪些扩展名、内部怎么判定文件类型,这次我们没有读引擎实现,注册表这一层给出的只有”引擎名 → 类”的映射,没有一张按扩展名分派的表被我们核到,所以”支持哪些格式”这个问题,在注册表这一层能回答的只到”有哪几条路”为止,再往下需要你自己去 parsing/engines/ 下对应引擎的子目录里确认。没核到的部分我们不替它补。
四、新装默认走的是哪一条
runtime_settings.py:109-111 有一条注释把默认选择的理由写明白了:新装用户默认走内置的文本抽取器,原文的说法是解析”开箱即用,不需要可选的解析器包或模型权重”。对应到上面那张表,就是 text_only / TextOnlyParser 这一条。
把这条注释和上一节拼起来看就通了:正因为另外四条路各自绑着第三方包(其中一条还绑着模型权重),默认值才选了那个不依赖任何可选件的。换句话说,新装状态下你走的是能力最保守的那条路,其它四条都需要你主动把依赖补齐才谈得上启用。
这是源码里的默认配置,不是对解析结果的任何保证——五条路各自解出来的文本长什么样、差异有多大,我们没有运行过,不做比较也不给推荐。
五、MinerU 那条支路最特殊:它有三组自己的枚举
五条路里只有 MinerU 在配置层带了一整组专属枚举,都在 runtime_settings.py:88-91:
- 模式:
local/cloud - 模型版本:
pipeline/vlm - 权重下载源:
huggingface/modelscope
三组枚举摆在一起,说明这条支路上的选择比其它四条多一层。有两件事必须如实说清楚:
cloud 这个取值意味着内容会离开本机。 云端这条路在代码里有一组独立的超时常量,deeptutor/services/parsing/engines/mineru/cloud.py:38-41:默认超时 300 秒、提交 60 秒、上传 300 秒、下载 300 秒。有”上传”和”下载”两个独立超时这件事本身就说明了这条路的形态。你的文档里如果有不该外发的内容,选引擎时这一层要先过一遍——这是通用的数据处置常识,不是该项目文档里的建议。
下载源那两个取值对应的是模型权重的来源。 枚举里给了 huggingface 与 modelscope 两个选项,说明这条路在本地模式下需要把权重拉到本机。放在网络受限的环境里,这一项往往才是”为什么卡住”的真正原因,而不是解析逻辑本身。
顺带提一句同一个仓库里的相关事实,免得读者对这个项目的边界产生误判:DeepTutor 的 services/subagent/ 明写是”驱动用户本机 agent CLI 作为子代理”,services/cli_apps/ 是”管理员安装、chat agent 调用的命令行工具”,services/sandbox/ 是沙箱 shell 执行并带可插拔隔离后端。也就是说这个项目在解析层之外,确实存在会在你本机安装和执行外部程序的模块。这几块各有专门篇目在讲,这里只提醒一句:评估解析引擎的依赖时,顺手把这几层的边界一起看了。
六、和 RAG 工厂对照着读
factory.py:1-7 那句”mirrors the RAG pipeline factory”是作者留给读者的一条明路:这两张注册表结构同构,读懂一张就读懂另一张。可以直接对照的点是——解析这边 _ENGINE_LOADERS 五个键、KNOWN_ENGINES 收口;检索那边 rag/factory.py 的管线 provider 常量是六个,_build_pipeline 按名字懒 import 对应管线类。同样是”名字 → 懒加载类”的形状,同样把可选依赖推到调用时。
数量对不上(五 vs 六)是两张表各自的事实,不是谁抄漏了谁;关于 RAG 那张表内部的口径问题,我们另有一篇专门讲,这里不展开。
七、你可以照着核的五步
- 打开
deeptutor/services/config/runtime_settings.py:93-97,把五个引擎名常量抄下来,再看:98-106的_DOCUMENT_PARSING_ENGINES是不是正好收了这五个。 - 打开
deeptutor/services/parsing/engines/factory.py:25-62,逐行比对_ENGINE_LOADERS的键与上一步那五个常量是否一一对上,再看:64的KNOWN_ENGINES是从哪来的。 - 读
factory.py:1-7的文件头 docstring,确认”懒加载第三方依赖、导入注册表不会因缺可选依赖失败”这句话确实在那儿——它是本文第三节全部结论的唯一依据。 - 读
runtime_settings.py:109-111那条关于默认引擎的注释,确认默认值指向的是内置文本抽取器这一条。 - 如果你打算用 MinerU:先看
runtime_settings.py:88-91的三组枚举,再看parsing/engines/mineru/cloud.py:38-41的四个超时常量,确认自己要走的是local还是cloud。
需要说明的边界:本文只读了解析层的模块 docstring、引擎注册表与配置层的常量段,parsing/ 目录下除 __init__.py 与 engines/ 之外的其余模块、以及五个引擎各自的实现,本文都没有读;ParsedDocument 这个 IR 具体有哪些字段、内容寻址缓存按什么算键、解析结果如何交给下游检索,本文一律不下结论。文中所有阈值与默认值都是源码里的默认配置,不是运行结果的保证。
本文依据 DeepTutor 官方仓库(github.com/HKUDS/DeepTutor)的 README、AGENTS.md、
pyproject.toml 与 deeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。
本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API,
因此不涉及生成质量、响应速度与教学效果的任何描述。
参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。