Docling 插件系统深度解析:基于 pluggy 扩展 OCR、布局与表格结构引擎
Docling 通过基于 pluggy 与 setuptools entrypoint 的插件机制,允许第三方在不改动主包的前提下向转换管线注入新的 OCR 引擎、布局引擎与表格结构识别引擎。本文完整讲解插件的注册方式(覆盖 pyproject.toml、poetry、setup.cfg、setup.py 四种打包配置)、三类工厂(factory)约定的接口与继承要求、allow_external_plugins 安全开关的源码实现,以及 docling CLI 中 --allow-external-plugins、--show-external-plugins 等参数的用法,带你掌握从打包发布到运行时选型的完整插件开发链路。
插件加载机制:pluggy + setuptools entrypoint
Docling 的插件体系建立在一套两层机制之上:
- 插件发现层:Docling 使用 pluggy 的
PluginManager扫描已安装包在 setuptools 中声明的、组名为docling的 entrypoint,每个 entrypoint 指向一个 Python 模块; - 插件注册层:每个被发现的模块内定义一个与工厂同名的可调用函数(如
ocr_engines()、layout_engines()、table_structure_engines()),返回包含引擎类列表的字典,由对应的BaseFactory子类完成登记。
插件发现的核心实现位于 BaseFactory.load_from_plugins:
def load_from_plugins(
self, plugin_name: Optional[str] = None, allow_external_plugins: bool = False
):
plugin_name = plugin_name or self.plugin_name
plugin_manager = PluginManager(plugin_name)
plugin_manager.load_setuptools_entrypoints(plugin_name)
for plugin_name, plugin_module in plugin_manager.list_name_plugin():
plugin_module_name = str(plugin_module.__name__)
if not allow_external_plugins and not plugin_module_name.startswith(
"docling."
):
logger.warning(
f"The plugin {plugin_name} will not be loaded because Docling "
"is being executed with allow_external_plugins=false."
)
continue
attr = getattr(plugin_module, self.plugin_attr_name, None)
if callable(attr):
config = attr()
self.process_plugin(config, plugin_name, plugin_module_name)
从源码结构看,加载流程可以归纳为四步:
PluginManager("docling")创建以docling为组名的插件管理器,load_setuptools_entrypoints("docling")从已安装包的 entrypoint 元数据中加载插件模块;- 逐一对每个
(插件名, 模块)做外部插件过滤:模块名不以docling.开头且未开启allow_external_plugins时直接跳过(仅打印 warning),这是第三方插件默认不可见的原因; - 用
getattr(plugin_module, self.plugin_attr_name)在模块上取与工厂匹配的函数(OCR 工厂取ocr_engines,布局工厂取layout_engines,表格工厂取table_structure_engines); - 若该属性可调用,则执行它拿到配置字典,交由
process_plugin把列表中的每个类调用register登记。
登记逻辑 BaseFactory.register 以 cls.get_options_type()(即引擎类声明的配置类)为键存入 _classes,并记录 FactoryMeta(kind, plugin_name, module) 元信息;若同一配置类已被注册则抛出 ValueError,外层 process_plugin 捕获后仅告警 %r already registered,因此插件名在生态内必须唯一、同一 kind 只允许存在一个引擎实现。工厂还通过 get_enum 用已注册的 kind 动态生成字符串枚举(如 OcrEngine),create_instance 则按 type(options) 查找引擎类并实例化——这就是"选项类决定用哪个引擎"的绑定方式。
Docling 主包自带的默认插件集中定义在 docling/models/plugins/defaults.py,是编写第三方插件时最好的参考起点:
def ocr_engines():
from docling.models.stages.ocr.auto_ocr_model import OcrAutoModel
# ... Tesseract、EasyOCR、RapidOCR、Nemotron、KserveV2、macOS Vision 等
return {
"ocr_engines": [
OcrAutoModel,
EasyOcrModel,
# ...
]
}
声明插件:entrypoint 的四种打包写法
entrypoint 组名固定为 docling,值为你包内负责插件注册的模块(dotted path)。your_plugin_name 是插件名,必须在全 Docling 生态中唯一;your_package.module 指向定义注册函数的模块。不同打包系统的声明方式如下。
pyproject.toml(PEP 621 / setuptools):
[project.entry-points."docling"]
your_plugin_name = "your_package.module"
poetry v1 的 pyproject.toml:
[tool.poetry.plugins."docling"]
your_plugin_name = "your_package.module"
setup.cfg:
[options.entry_points]
docling =
your_plugin_name = your_package.module
setup.py:
from setuptools import setup
setup(
# ...,
entry_points = {
'docling': [
'your_plugin_name = "your_package.module"'
]
}
)
无论使用哪种写法,最终效果一致:包安装后,pkg_resources/importlib.metadata 都能在 docling 组下找到 your_plugin_name -> your_package.module 的映射,供 PluginManager.load_setuptools_entrypoints 发现。
插件工厂与接口约定
Docling 当前提供三类可插拔工厂,各自封装在 docling/models/factories/ 目录中,均继承自 BaseFactory 并在构造时传入 plugin_attr_name(即插件模块中注册函数的名字):
- OcrFactory:
super().__init__("ocr_engines"),面向BaseOcrModel; - LayoutFactory:
super().__init__("layout_engines"),面向BaseLayoutModel; - TableStructureFactory:
super().__init__("table_structure_engines"),面向BaseTableStructureModel。
工厂实例通过 docling/models/factories/init.py 中的 get_ocr_factory()、get_layout_factory()、get_table_structure_factory() 等函数创建,并用 @lru_cache 按 allow_external_plugins 取值缓存——这意味着同一进程内开关状态变化时,需以不同参数调用才会触发重新加载。
OCR 工厂
OCR 工厂允许向 Docling 用户提供更多 OCR 引擎。插件模块 your_package.module 中的注册代码形如:
# Factory registration
def ocr_engines():
return {
"ocr_engines": [
YourOcrModel,
]
}
其中 YourOcrModel 必须:
- 实现 BaseOcrModel(抽象页面处理模型,其
__call__(conv_res, page_batch)接收转换结果与页面批次并返回处理后的页面); - 提供从 OcrOptions 派生的选项类,并通过类方法
get_options_type()暴露该配置类型(该协议定义在 BaseModelWithOptions:get_options_type()返回类型 + 以options关键字参数构造)。
OcrOptions 携带 kind 字段(如 tesseract、easyocr 等),工厂即以 kind 生成引擎枚举成员,用户在管线中通过选项类选择具体引擎。
Layout 引擎工厂
布局引擎工厂用于提供新的版面分析引擎:
# Factory registration
def layout_engines():
return {
"layout_engines": [
YourLayoutModel,
]
}
YourLayoutModel 必须实现 BaseLayoutModel,并提供从 BaseLayoutOptions 派生的选项类。默认插件(defaults.py 中 layout_engines())注册了 LayoutModel、LayoutObjectDetectionModel 以及实验性的 TableCropsLayoutModel。
Table structure 引擎工厂
表格结构工厂用于提供新的表格结构识别引擎:
# Factory registration
def table_structure_engines():
return {
"table_structure_engines": [
YourTableStructureModel,
]
}
YourTableStructureModel 必须实现 BaseTableStructureModel,并提供从 BaseTableStructureOptions 派生的选项类。默认实现包括 TableStructureModel、TableStructureModelV2 与 GraniteVisionTableStructureModel。
此外,从 defaults.py 的源码结构看,插件注册函数并非只有文档所述三类——同一机制还驱动着 picture_description() 工厂(PictureDescriptionFactory,注册函数名为 picture_description),说明该插件框架是通用可扩展的,新增一类引擎只需新增一个 BaseFactory 子类与对应的注册函数约定。
启用第三方插件:allow_external_plugins
出于安全与可预测性考虑,非 docling 包自身的插件默认不会加载(见上文 load_from_plugins 中 startswith("docling.") 的过滤逻辑)。第三方插件必须通过 allow_external_plugins 显式开启。该选项声明在 PdfPipelineOptions 中:
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions
from docling.document_converter import DocumentConverter, PdfFormatOption
pipeline_options = PdfPipelineOptions()
pipeline_options.allow_external_plugins = True # <-- enable external plugins
pipeline_options.ocr_options = YourOptions # <-- your OCR options here
pipeline_options.layout_options = YourLayoutOptions # <-- your layout options here
pipeline_options.table_structure_options = YourTableStructureOptions # <-- your table structure options here
doc_converter = DocumentConverter(
format_options={
InputFormat.PDF: PdfFormatOption(
pipeline_options=pipeline_options
)
}
)
使用要点:
allow_external_plugins = True只是解除过滤开关,具体选用哪个引擎仍由ocr_options/layout_options/table_structure_options传入的选项实例决定,选项类的kind必须与插件注册时的配置类一致,否则BaseFactory.create_instance会抛出RuntimeError并列出全部已知 class 供排查(见 _err_msg_on_class_not_found);- 三个选项位对应三个工厂,可只启用其一,例如仅替换 OCR 引擎而沿用默认布局与表格结构实现。
使用 docling CLI
CLI 侧同样需要先开启外部插件才能选择新引擎,相关参数定义在 docling/cli/main.py(allow_external_plugins、show_external_plugins 两个选项):
# Show the external plugins
docling --show-external-plugins
# Run docling with a custom OCR engine
docling --allow-external-plugins --ocr-engine=NAME
# Run docling with a custom layout engine
docling --allow-external-plugins --layout-engine=NAME
# Run docling with a custom table structure engine
docling --allow-external-plugins --table-structure-engine=NAME
其中 --show-external-plugins 的实现 show_external_plugins_callback 会分别以 allow_external_plugins=True 获取 OCR、布局、表格结构三个工厂并打印各自 registered_kind(即所有可见引擎的 kind 列表),而 --ocr-engine、--layout-engine、--table-structure-engine 传入的 NAME 正是各工厂枚举成员名(也就是选项类的 kind)。CLI 内部在默认路径下仍以 allow_external_plugins=False 构建工厂(见 main.py),仅在用户显式传参后才切换,行为与 API 路径完全对称。
小结与开发检查清单
结合文档约定与 base_factory.py 的源码实现,开发并验证一个 Docling 引擎插件可对照以下清单:
- 接口:引擎类继承对应基类(
BaseOcrModel/BaseLayoutModel/BaseTableStructureModel),选项类继承对应*Options并定义唯一kind; - 注册:模块内提供与工厂同名的注册函数,返回
{"<工厂键>": [引擎类]}字典,不要注册已被占用的kind(会被静默跳过并告警); - 打包:在 entrypoint 的
docling组声明插件,插件名保持生态内唯一; - 验证:
docling --show-external-plugins确认 kind 出现在列表中,再用--allow-external-plugins --<engine>=NAME跑通转换; - 参考实现:以 docling/models/plugins/defaults.py 中 Tesseract、EasyOCR 等内置引擎的注册方式作为模板。
需要说明的适用前提:以上机制与参数均以当前仓库版本为准,插件发现依赖包已通过 pip 等标准方式安装(entrypoint 元数据可用);外部插件加载始终需要用户显式授权(allow_external_plugins/--allow-external-plugins),未开启时任何第三方引擎都不会进入工厂注册表。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00