Docling Slim 重构解析:用 uv Workspace 把 Docling 拆成最小依赖包与完整功能包
本文基于仓库中的重构规划文档 .plans/active/docling-slim.md 展开,详解 Docling 如何将单一发行版拆分为最小依赖的 docling-slim 与全功能的 docling 两个包:包括 uv workspace 目录设计、细粒度 extras 依赖分层、版本精确钉住的发布流水线、导入审计方法,以及面向用户的迁移指南;读完后你将掌握“共享同一份源码、只拆分发行元数据”这一 Python 包拆分方案的完整落地思路。
一、重构背景与核心目标
Docling 的原始发行方式是一个"大而全"的包:安装 docling 会连带 PyTorch、模型推理、OCR 引擎等全部依赖,磁盘占用约 2.8GB。对于只需要核心数据模型(DoclingDocument、ConversionResult 等)或只处理某一种格式的库级用户,这是巨大的浪费。
规划文档给出的目标非常明确:
- 当前包:
docling(规划撰写时版本 2.85.0); - 拆分目标:
docling-slim(最小依赖)+docling(保持现状的默认依赖集)。
文档列出了 8 条关键约束,是整个方案的"宪法":
- 不移动源码 —— 源码保留在仓库根的
docling/目录,只有元数据(pyproject.toml)移动; - uv workspace 结构 ——
packages/下建立真实的包目录,采用标准 workspace 布局; - docling 依赖 docling-slim —— 全功能包通过依赖 slim 的
standardextra 复现现有行为; - 精确版本钉住 ——
docling依赖docling-slim==X.Y.Z(完全相等的版本约束); - CLI 只在完整包中提供 —— 命令行工具属于全功能包,slim 不含 CLI;
- 细粒度 extras —— 把功能组件拆成可自由组合的细选项;
- CI/CD 双包发布 —— 从各自的包目录分别构建、发布;
- 本地开发 —— 通过 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 个依赖只支撑:核心数据模型(DoclingDocument、ConversionResult)、文档格式定义、基础 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.0、numpy、pillow |
基础 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.0、torchvision、docling-ibm-models>=3.13.0,<4、accelerate、huggingface_hub、defusedxml;[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]:transformers、accelerate、mlx-vlm(仅限 Apple Silicon macOS)、qwen-vl-utils;[asr]:mlx-whisper(Apple Silicon 限定)、openai-whisper、numba;[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 入口点(docling、docling-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之上叠加vlm、asr、htmlrender、xbrl、remote-serving、onnxruntime、ocr-easyocr、ocr-tesserocr、ocr-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-audio、format-video、format-email、format-iwork、format-opendocument 等格式项。核心的分层思想——"细粒度单项 + 组合便捷包"——被完整保留。
五、docling 元包:精确钉住 + 依赖-only wheel
docling 包被设计为一个 meta-package,自身只声明对 slim 的依赖:
dependencies = [
'docling-slim[standard]==2.90.0', # 规划中为占位版本,实际发布时自动同步
]
关键点:
- CLI 已包含在
standardextra 中(经由 slim 的cliextra),用户pip install docling后即可获得 CLI; - 精确钉住(
==)确保docling与docling-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:
build-and-publish-slim:检出代码 →uv build→ 通过pypa/gh-action-pypi-publish发布(带attestations: true供应链签名);build-and-publish-full:needs: 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.toml、packages/docling/pyproject.toml和根pyproject.toml的project.version; - 同步更新
docling对docling-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;docling对docling-slim的依赖被解析到本地 workspace 成员,不需要访问 PyPI;- 源码改动即时可见;元数据改动需
uv sync刷新环境; - 要验证真实构建产物时,用
uv build生成 wheel 后uv pip install dist/*.whl在干净环境测试。
当前仓库的实际布局有一个演化值得说明:从源码结构看,docling-slim 的发行元数据最终直接放在了仓库根的 pyproject.toml(name = "docling-slim",[tool.hatch.build.targets.wheel] packages = ["docling"] 指向根源码目录),workspace 成员变为 packages/docling 与 packages/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 的版本解析会依次尝试 docling 与 docling-slim 两个分发的元数据(因为 slim 单独安装时没有 docling 元包),找不到才回退 "unknown"——这正是 slim 独立可导入的直接证据;而 pyproject.toml 中 [tool.ty.analysis] allowed-unresolved-imports 把 torchvision、pypdfium2、easyocr、transformers 等 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.toml 中 easyocr、tesserocr、vlm、rapidocr、asr、onnxruntime、xbrl 等旧 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)。
十、实施路线图与关键决策
规划把落地分为六个阶段:
- Phase 1 仓库结构:创建
packages/双包目录与元数据、根 workspace 配置,验证源码仍在docling/; - Phase 2 包配置:slim 只含基础依赖与全部库功能 extras;docling 依赖
docling-slim[standard]并追加 CLI 依赖;两包都配置[tool.hatch.build.targets.wheel]指向共享源码; - Phase 3 构建与发布:
build-packages.sh、双包 pypi.yml、release.sh 双文件版本同步; - Phase 4 导入审计(关键):模块级导入排查、延迟导入改造、try/except 守卫、CLI 隔离、最小依赖导入测试;
- Phase 5 测试与文档:ci.yml 覆盖双包、workspace 开发流验证、README 安装选项更新、test.pypi.org 预发布;
- 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.toml:docling-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 项目,这套"元数据搬家、源码不动"的做法都值得参考。
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 StartedRust0624
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