首页
/ Docling Slim 重构解析:用 uv Workspace 把 Docling 拆成最小依赖包与完整功能包

Docling Slim 重构解析:用 uv Workspace 把 Docling 拆成最小依赖包与完整功能包

2026-09-06 23:00:10作者:钟日瑜

本文基于仓库中的重构规划文档 .plans/active/docling-slim.md 展开,详解 Docling 如何将单一发行版拆分为最小依赖的 docling-slim 与全功能的 docling 两个包:包括 uv workspace 目录设计、细粒度 extras 依赖分层、版本精确钉住的发布流水线、导入审计方法,以及面向用户的迁移指南;读完后你将掌握“共享同一份源码、只拆分发行元数据”这一 Python 包拆分方案的完整落地思路。

一、重构背景与核心目标

Docling 的原始发行方式是一个"大而全"的包:安装 docling 会连带 PyTorch、模型推理、OCR 引擎等全部依赖,磁盘占用约 2.8GB。对于只需要核心数据模型(DoclingDocumentConversionResult 等)或只处理某一种格式的库级用户,这是巨大的浪费。

规划文档给出的目标非常明确:

  • 当前包docling(规划撰写时版本 2.85.0);
  • 拆分目标docling-slim(最小依赖)+ docling(保持现状的默认依赖集)。

文档列出了 8 条关键约束,是整个方案的"宪法":

  1. 不移动源码 —— 源码保留在仓库根的 docling/ 目录,只有元数据(pyproject.toml)移动;
  2. uv workspace 结构 —— packages/ 下建立真实的包目录,采用标准 workspace 布局;
  3. docling 依赖 docling-slim —— 全功能包通过依赖 slim 的 standard extra 复现现有行为;
  4. 精确版本钉住 —— docling 依赖 docling-slim==X.Y.Z(完全相等的版本约束);
  5. CLI 只在完整包中提供 —— 命令行工具属于全功能包,slim 不含 CLI;
  6. 细粒度 extras —— 把功能组件拆成可自由组合的细选项;
  7. CI/CD 双包发布 —— 从各自的包目录分别构建、发布;
  8. 本地开发 —— 通过 workspace 实现可编辑安装(editable install)。

二、目标仓库结构:只挪元数据,源码原地不动

规划给出的目标目录结构如下:

docling/  (当前仓库)
├── pyproject.toml                    # Workspace 根配置
├── packages/
│   ├── docling-slim/
│   │   └── pyproject.toml           # docling-slim 包元数据
│   └── docling/
│       └── pyproject.toml           # docling 包元数据(依赖 slim)
├── docling/                          # 源码(位置不变)
│   ├── __init__.py
│   ├── document_converter.py
│   ├── cli/                         # CLI 代码仍在这里
│   ├── datamodel/                   # 数据模型
│   └── ...
├── tests/                            # 测试(不变)
├── docs/                             # 文档(不变)
└── .github/workflows/
    ├── ci.yml                        # 修改:同时测试两个包
    └── pypi.yml                     # 修改:双包发布(先 slim 后 docling)

这个设计有几个值得注意的关键点:

  • 两个包的 pyproject.toml 都通过 packages = ["docling"] 指向仓库根部的同一份源码;
  • packages/ 子目录里只有元数据文件,不含任何 Python 源码;
  • 遵循标准 uv workspace 模式,用共享源码支撑两个发行包。

三、docling-slim 基础依赖:8 个包,约 50MB

slim 包的基础依赖被压缩到 8 个,构成"库优先"(Library-First)的最小集:

dependencies = [
    'pydantic>=2.0.0,<3.0.0',
    'docling-core>=2.70.0,<3.0.0',
    'pydantic-settings>=2.3.0,<3.0.0',
    'filetype>=1.2.0,<2.0.0',
    'requests>=2.32.2,<3.0.0',
    'certifi>=2024.7.4',
    'pluggy>=1.0.0,<2.0.0',
    'tqdm>=4.65.0,<5.0.0',
]

这 8 个依赖只支撑:核心数据模型(DoclingDocumentConversionResult)、文档格式定义、基础 I/O 工具。不包含任何 PDF 解析器、模型推理引擎或 CLI 组件。

当前仓库根的 pyproject.toml(即 docling-slim 的元数据)中,这 8 个基础依赖仍然完整保留(docling-core 的下限已随版本演进提高到 2.91.0),说明"8 个基础包"的边界设计在实施后被原样保持了。

四、细粒度 extras:按需拼装依赖的核心设计

规划文档把可选功能拆成五大类 extras,每一类都可独立安装、自由组合。这是整个方案最有价值的部分。

4.1 PDF 后端(按后端细拆)

Extra 依赖 说明
[backend-pypdfium2] pypdfium2>=4.30.0,!=4.30.1,<6.0.0numpypillow 基础 PDF 解析
[backend-docling-parse] 上述 + docling-parse>=5.3.2,<6.0.0 高级 PDF 解析
[parse] 组合上述两者 完整解析便捷包

注意 pypdfium2!=4.30.1 排除钉住(pin exclusion)——这类写法用于规避已知有问题的具体版本。

4.2 模型依赖(两级拆分)

  • [models-core](2 个包):scipy(数学运算)+ rtree(空间索引);
  • [models-inference](6 个包,约 2GB):torch>=2.2.2,<3.0.0torchvisiondocling-ibm-models>=3.13.0,<4acceleratehuggingface_hubdefusedxml
  • [models](便捷包):docling-slim[parse,models-core,models-inference]

把 2GB 级的推理依赖与轻量数值库分开,让只需要几何/后处理能力的用户不必装 PyTorch。

4.3 OCR 引擎(每个引擎一个 extra)

Extra 依赖
[ocr-rapidocr] rapidocr>=3.3,<4.0.0
[ocr-rapidocr-onnx] 上述 + onnxruntime>=1.7.0,<2.0.0 ; python_version < "3.14"
[ocr-easyocr] easyocr>=1.7,<2.0
[ocr-tesserocr] tesserocr>=2.7.1,<3.0.0 + pandas>=2.1.4,<4.0.0
[ocr-mac] ocrmac>=1.0.0,<2.0.0 ; sys_platform == "darwin"

环境标记(sys_platform == "darwin" 等)让 macOS 专属引擎只在 darwin 上生效。

4.4 输入格式支持

Extra 依赖 覆盖格式
[format-docx] python-docx>=1.1.2,<2.0.0 Word
[format-pptx] python-pptx>=1.0.2,<2.0.0 PowerPoint
[format-xlsx] openpyxl>=3.1.5,<4.0.0 Excel
[format-office] 组合三个 Office 格式 Office 全家桶
[format-html] beautifulsoup4 + lxml HTML
[format-markdown] marko>=2.1.2,<3.0.0 Markdown
[format-web] 组合 html + markdown 网页类
[format-latex] pylatexenc>=2.10,<3.0 LaTeX
[format-xbrl] arelle-release>=2.38.17,<3.0.0 财务报告 XBRL

这些 extras 与源码中 docling/backend/ 目录下的后端文件一一对应(如 docx 后端latex 后端XBRL 后端)——每个后端模块在缺少对应依赖时应延迟导入并给出清晰报错,这正是第六节导入审计要验证的行为。

4.5 高级功能

  • [vlm]transformersacceleratemlx-vlm(仅限 Apple Silicon macOS)、qwen-vl-utils
  • [asr]mlx-whisper(Apple Silicon 限定)、openai-whispernumba
  • [htmlrender]playwright>=1.58.0(动态网页渲染);
  • [remote-serving]tritonclient[grpc]>=2.65.0,<3.0.0(远程模型推理);
  • [onnxruntime]:按平台区分的 onnxruntime / onnxruntime-gpu 版本钉住;
  • [cli]typer>=0.12.5,<0.22.0 + rich>=13.0.0。CLI 入口点(doclingdocling-tools)声明在 docling-slim 的元数据中,包装脚本会检测 typer/rich 是否可用,缺失时给出安装指引;
  • [chunking]docling-core[chunking]>=2.70.0,<3.0.0(RAG 文档分块);
  • [extraction]polyfactory>=2.22.2(信息抽取)。

4.6 便捷组合包

  • [standard]:对齐当前 docling 的默认依赖集,如 docling-slim[format-pdf,models-local,ocr-rapidocr,format-office,format-web,format-latex,chunking,extract-core,service-client,cli]
  • [all]:在 standard 之上叠加 vlmasrhtmlrenderxbrlremote-servingonnxruntimeocr-easyocrocr-tesserocrocr-mac,实现"一键全装"。

从当前仓库元数据看,这套 extras 在实施后经历了命名演化:models 变为 models-local / models-remote / models-onnxruntime / models-vlm-inline,OCR 类加了 feat- 前缀(feat-ocr-rapidocr 等),PDF 后端改名为 format-pdf-pypdfium2 / format-pdf-docling,并新增了 format-audioformat-videoformat-emailformat-iworkformat-opendocument 等格式项。核心的分层思想——"细粒度单项 + 组合便捷包"——被完整保留。

五、docling 元包:精确钉住 + 依赖-only wheel

docling 包被设计为一个 meta-package,自身只声明对 slim 的依赖:

dependencies = [
    'docling-slim[standard]==2.90.0',  # 规划中为占位版本,实际发布时自动同步
]

关键点:

  • CLI 已包含在 standard extra 中(经由 slim 的 cli extra),用户 pip install docling 后即可获得 CLI;
  • 精确钉住==)确保 doclingdocling-slim 版本严格一致,杜绝"新版 docling 配旧版 slim"的不兼容组合。

当前仓库中的 packages/docling/pyproject.toml 印证了这一点:版本已演进到 2.124.0,依赖为 docling-slim[standard]==2.124.0,并且通过 [tool.uv.sources]docling-slim = { workspace = true } 让本地开发时解析到 workspace 成员。该文件还有一个规划之外的关键细节:[tool.hatch.build.targets.wheel] 使用 bypass-selection = true,使 docling 的 wheel 不携带任何 Python 模块(依赖-only wheel),源码全部由 docling-slim 的 wheel 提供——注释中说明这是为了修复"两个 wheel 都打包同一份 docling/ 模块、安装时相互冲突"的旧 bug。此外它还重新声明了 docling / docling-tools 两个入口点,注释解释这是为了让 uv tool install docling 能把脚本链接进 ~/.local/bin(uv 只会暴露被点名包自身的 scripts,不暴露依赖包的)。

六、构建与发布流水线:先 slim,后 docling

6.1 本地构建脚本

两个包从各自的包目录构建,输出到统一 dist/

# build-packages.sh
#!/bin/bash
set -e

# 从包目录构建 docling-slim
cd packages/docling-slim
uv build --out-dir ../../dist
cd ../..

# 从包目录构建 docling
cd packages/docling
uv build --out-dir ../../dist
cd ../..

ls -lh dist/

6.2 PyPI 发布工作流

规划中的 .github/workflows/pypi.yml 核心是两个有序 job

  1. build-and-publish-slim:检出代码 → uv build → 通过 pypa/gh-action-pypi-publish 发布(带 attestations: true 供应链签名);
  2. build-and-publish-fullneeds: build-and-publish-slim,发布前有一段轮询等待逻辑——通过 grep '^version = ' packages/docling-slim/pyproject.toml 读出目标版本,循环最多 50 次(每次 sleep 10 秒)用 pip index versions docling-slim 检查该版本是否已在 PyPI 可用,确保全功能包发布时其精确钉住的依赖必然存在。

这个顺序约束源于第四节说的精确版本钉住:若 docling 先发布,安装它会因找不到 docling-slim==X 而失败。

6.3 版本同步脚本

规划中的 .github/scripts/release.sh 在发布时做三件事:

  • uvx --from=toml-cli toml set 同时更新 packages/docling-slim/pyproject.tomlpackages/docling/pyproject.toml 和根 pyproject.tomlproject.version
  • 同步更新 doclingdocling-slim 的依赖钉住及所有形如 docling-slim[extra]==X 的可选依赖(用内嵌 Python 脚本 + tomllib/tomli_w 遍历 optional-dependencies,正则提取 extra 名后重写版本);
  • UV_FROZEN=0 uv lock --upgrade-package docling-slim 重新锁定,随后提交并打 tag 触发发布。

由于 docling 元包对 slim 的多处可选依赖都携带版本钉住,版本 bump 必须是"全文件一致性"操作,这也是脚本用程序化方式而非手工改 TOML 的原因。

七、本地开发工作流:uv Workspace 的免构建体验

workspace 在根 pyproject.toml 中声明:

[tool.uv.workspace]
members = ["packages/docling-slim", "packages/docling"]

由此带来的开发体验(规划文档"Local Development Workflow"一节):

git clone <repo> && cd docling
uv sync          # 两个包都以 editable 模式安装,成员间依赖本地解析

# 修改 docling/*.py 源码 → 立即生效,无需重建
uv run pytest
uv run docling <your-args>

# 修改任意 pyproject.toml → 重新同步
uv sync

要点:

  • uv sync 读取 workspace 配置,把两个包都装成 editable;
  • doclingdocling-slim 的依赖被解析到本地 workspace 成员,不需要访问 PyPI;
  • 源码改动即时可见;元数据改动需 uv sync 刷新环境;
  • 要验证真实构建产物时,用 uv build 生成 wheel 后 uv pip install dist/*.whl 在干净环境测试。

当前仓库的实际布局有一个演化值得说明:从源码结构看docling-slim 的发行元数据最终直接放在了仓库根的 pyproject.tomlname = "docling-slim"[tool.hatch.build.targets.wheel] packages = ["docling"] 指向根源码目录),workspace 成员变为 packages/doclingpackages/docling-client/pyproject.toml(一个依赖 docling-slim[service-client] 的独立客户端元包,同样是 bypass-selection = true 的依赖-only wheel)。相比规划的"根为 workspace 壳",实际形态是"根即 slim 包",结构更扁平,但共享源码、元数据分离、精确钉住三大原则完全一致。

八、导入审计:slim 可用性的前置关卡

拆分后最大的风险是:docling 源码中存在对可选依赖的模块级导入,导致只装了 8 个基础依赖的 slim 环境连 import docling 都失败。规划文档把"Import Audit"标为 CRITICAL,给出了完整方法。

8.1 审计清单

  • 基础导入测试:仅装基础依赖时 import docling 必须成功;
  • 模块级导入检查:顶层 __init__.py 不得导入可选模块;
  • 延迟导入:可选后端/模型必须用运行时导入,而非模块级;
  • 守卫模式:所有可选功能须有 try/except 或条件导入;
  • CLI 隔离:库模式下不得触发 CLI 代码的导入。

8.2 三种典型问题与修法

模块级导入可选依赖

# BAD - torch 未安装即崩溃
import torch
from docling_ibm_models import LayoutModel

# GOOD - 延迟导入
def get_layout_model():
    import torch
    from docling_ibm_models import LayoutModel
    return LayoutModel()

未守卫的可选导入

# BAD
from docling.models.layout import LayoutModel

# GOOD
try:
    from docling.models.layout import LayoutModel
except ImportError:
    LayoutModel = None

库代码中混入 CLI 导入

# BAD - typer 在模块级导入
import typer

# GOOD - 只在 CLI 入口点导入
def main():
    import typer
    app = typer.Typer()

8.3 验证测试策略

在干净虚拟环境复现最小安装并逐项验证:

uv venv test-env && source test-env/bin/activate
uv pip install pydantic docling-core pydantic-settings filetype requests certifi pluggy tqdm

python -c "import docling"
python -c "from docling.datamodel import DoclingDocument"
python -c "from docling import DocumentConverter"  # 应可用或优雅降级
python -c "import docling.models"   # 可选模块缺失时不得崩溃
python -c "import docling.backend"

验收标准:slim 基础版仅凭 8 个依赖安装成功;import docling 无可选依赖也能通过;核心数据模型可访问;可选功能缺失时以清晰错误信息优雅降级;不存在导入期因缺失可选依赖而失败的路径。

从源码现状看,这一审计已实际落地:docling/init.py 的版本解析会依次尝试 doclingdocling-slim 两个分发的元数据(因为 slim 单独安装时没有 docling 元包),找不到才回退 "unknown"——这正是 slim 独立可导入的直接证据;而 pyproject.toml[tool.ty.analysis] allowed-unresolved-importstorchvisionpypdfium2easyocrtransformers 等 20 余个可选依赖显式列入"允许未解析"清单,说明静态检查层面也在持续守护"可选依赖不进顶层导入"这一纪律。

九、迁移指南、功能矩阵与体积对比

9.1 现有用户:零改动

pip install docling   # 行为不变,仍安装与之前相同的依赖集

9.2 新用户:按需最小安装

pip install docling-slim                              # 仅数据模型,~50MB
pip install docling-slim[parse]                       # + PDF 解析,~200MB
pip install docling-slim[backend-docling-parse]       # 或指定高级 PDF 后端
pip install docling-slim[backend-docling-parse,format-docx]  # PDF + Word
pip install docling-slim[backend-docling-parse,models]       # + 本地推理,~2.5GB
pip install docling-slim[standard]                  # 等价于 pip install docling

9.3 功能矩阵(规划文档原文)

功能 docling-slim 基础 所需 extra docling 包
核心数据模型 -
PDF 解析(基础) [backend-pypdfium2]
PDF 解析(高级) [backend-docling-parse][parse]
空间索引 [parse-spatial]
本地模型推理 [models]
RapidOCR [ocr-rapidocr]
EasyOCR [ocr-easyocr] ❌(extra)
Word [format-docx][format-office]
Excel [format-xlsx][format-office]
PowerPoint [format-pptx][format-office]
HTML [format-html][format-web]
Markdown [format-markdown][format-web]
LaTeX [latex]
XBRL 财务 [xbrl] ❌(extra)
CLI 工具 安装 docling
信息抽取 [polyfactory]
VLM 支持 [vlm] ❌(extra)

注意 docling 元包也通过重导出 slim extras 保留向后兼容(当前 packages/docling/pyproject.tomleasyocrtesserocrvlmrapidocrasronnxruntimexbrl 等旧 extra 名都映射到对应 slim extra,如 vlm = ['docling-slim[models-vlm-inline]==...'])。

9.4 体积估算(规划文档原文)

依赖数 磁盘体积 适用场景
docling-slim(基础) 8 ~50MB 仅数据模型
[backend-pypdfium2] 11 ~150MB + 基础 PDF
[backend-docling-parse] 12 ~180MB + 高级 PDF
[parse] 13 ~200MB 完整解析
[parse,format-docx] 14 ~220MB + Word
[models] 19 ~2.5GB + 本地 ML 推理
[standard] 27 ~2.8GB 当前默认集
docling 27 ~2.8GB 全功能(不变)

最小安装相对全功能缩小约 95%(50MB 对 2.8GB)。

十、实施路线图与关键决策

规划把落地分为六个阶段:

  1. Phase 1 仓库结构:创建 packages/ 双包目录与元数据、根 workspace 配置,验证源码仍在 docling/
  2. Phase 2 包配置:slim 只含基础依赖与全部库功能 extras;docling 依赖 docling-slim[standard] 并追加 CLI 依赖;两包都配置 [tool.hatch.build.targets.wheel] 指向共享源码;
  3. Phase 3 构建与发布build-packages.sh、双包 pypi.yml、release.sh 双文件版本同步;
  4. Phase 4 导入审计(关键):模块级导入排查、延迟导入改造、try/except 守卫、CLI 隔离、最小依赖导入测试;
  5. Phase 5 测试与文档:ci.yml 覆盖双包、workspace 开发流验证、README 安装选项更新、test.pypi.org 预发布;
  6. Phase 6 正式发布:向后兼容复查后发布 PyPI。

沉淀下来的关键决策(Key Decisions):真实 workspace 成员目录;源码留在仓库根被两包共享;根 pyproject 标准 uv workspace;精确版本钉住;CLI 只在完整包;docling 依赖集等于现状默认(slim[standard] + typer);extras 细粒度可组合;从包目录构建。

十一、总结:从规划到当前仓库的演化验证

把规划文档与当前仓库状态对照,可以看到一次"高完成度但非照搬"的落地:

规划要点 当前仓库证据
8 个基础依赖、~50MB pyproject.toml 根元数据中 dependencies 完全一致(docling-core 下限升至 2.91.0)
源码不动、两包共享 docling/ slim 在根 pyproject.toml 通过 packages = ["docling"] 打包
docling 是 meta-package、精确钉住 slim packages/docling/pyproject.tomldocling-slim[standard]==2.124.0
docling 不携带 Python 模块(防 wheel 冲突) bypass-selection = true + 注释说明历史冲突 bug
CLI 入口声明在 slim 元数据、cli extra 提供 typer/rich pyproject.toml[project.scripts]cli extra
uv workspace 本地开发 [tool.uv.workspace] members = ["packages/docling", "packages/docling-client"],各包 [tool.uv.sources] 指向 workspace = true
extras 细粒度组合 format-pdf-* / feat-ocr-* / models-* / standard / all 分层结构保留,命名按实施需要演化
双包同步版本 三个 pyproject 的 # DO NOT EDIT, updated automatically 版本注释统一为 2.124.0

演化中的新增能力也值得关注:docling-client 独立元包(packages/docling-client/pyproject.toml)让"只想调用远程 Docling Serve、不装任何本地依赖"的用户有了比 service-client extra 更轻量的入口;packages/docling-slim/README.md 则为 slim 用户提供了按功能查 extras 的完整速查表。

总体而言,这次重构示范了一个可复用的模式:用 uv workspace + 共享源码目录 + 依赖-only meta wheel,在不改动一行 Python 源码的前提下,把"一个重包"拆成"最小内核 + 精确钉住的全功能门面",并以导入审计作为拆分可行性的硬关卡。对任何依赖树庞大、希望支持最小化安装的 Python 项目,这套"元数据搬家、源码不动"的做法都值得参考。

登录后查看全文
热门项目推荐
相关项目推荐