pdf-craft OCR Backend 配置指南:六种 local/vendor 配置对象的字段、运行约束与源码实现
pdf-craft OCR Backend 配置指南:六种 local/vendor 配置对象的字段、运行约束与源码实现
pdf-craft 通过 PDFOptions(ocr=...) 接收 OCR 配置对象,在本地 GPU 与远程供应商服务之间选择识别引擎。本指南以 docs/zh-CN/OCR_BACKENDS.md 为主干,逐项讲解三种 local 与三种 vendor 配置对象的字段语义、默认值、代码示例与运行边界,并结合 pdf_craft/ocr_config.py、pdf_craft/pdf/ocr.py、pdf_craft/pdf/vendor_ocr.py 等源码揭示底层实现。读完你将能独立完成本机 GPU 或远程 OCR 的完整配置、模型预下载、离线运行、GPU 指定、并发控制与错误处理。
先决定运行位置:local OCR 还是 vendor OCR
pdf-craft 的 OCR backend 分成两类,二者在数据流、依赖与运行约束上有本质差异:
- local OCR:模型下载到本地,并直接使用本机 NVIDIA GPU 完成识别。适合希望数据留在本机、已经具备 CUDA 环境,或需要离线运行的场景。依赖支持 CUDA 的 PyTorch、可用的 NVIDIA GPU、足够的显存、模型文件以及 Poppler(用于 PDF 页面渲染)。
- vendor OCR:页面发送到远程供应商服务,由远端资源完成识别。适合没有 CUDA、不希望安装本地模型运行时,或希望把 GPU 运维交给供应商的场景。它需要网络连接、服务凭据,并会产生供应商侧的请求成本(按 token 或按任务计费)。
一次 OCR 运行只配置一个 backend。 模型归属与运行方式可以分开选择:DeepSeek OCR 和 DeepSeek OCR 2 来自 DeepSeek 系列模型,Unlimited OCR 来自百度。模型的下载位置(本机或远端)与模型本身是正交的两个维度,这正是下面六种配置对象存在的原因。
六种配置对象总览
库 API 通过 PDFOptions(ocr=...) 接收配置对象,不读取环境变量。也就是说,库层只认显式传入的配置对象,环境变量方式仅存在于 pdf_craft_tool 工具层(见下文“CLI 工具的环境变量映射”)。pdf_craft/__init__.py 将这六个类全部导出,可直接 from pdf_craft import ... 导入。
| 配置对象 | 模型 | 运行位置 | 适用条件 |
|---|---|---|---|
DeepSeekOCRLocalConfig |
DeepSeek OCR | 本机 GPU | 有 CUDA,使用 DeepSeek OCR 本地模型 |
DeepSeekOCR2LocalConfig |
DeepSeek OCR 2 | 本机 GPU | 有 CUDA,使用 DeepSeek OCR 2 本地模型 |
UnlimitedOCRLocalConfig |
百度 Unlimited OCR | 本机 GPU | 有 CUDA,使用 Unlimited OCR 本地模型 |
DeepSeekOCRVendorConfig |
DeepSeek OCR | 远程服务 | 使用 OpenAI-compatible OCR endpoint |
DeepSeekOCR2VendorConfig |
DeepSeek OCR 2 | 远程服务 | 使用 OpenAI-compatible OCR 2 endpoint |
UnlimitedOCRVendorConfig |
百度 Unlimited OCR | 远程服务 | 使用百度 Unlimited OCR 服务 |
从源码看,这六个 dataclass 定义在 pdf_craft/ocr_config.py 中,并归纳为三个类型别名(同文件 L137-L143):LocalOCRConfig、VendorOCRConfig 与二者的并集 OCRConfig,同时还定义了 OCRMode 字面量类型,供工具层按字符串模式选择 backend。
下面的配置示例都可以传给 PDFCraft(pdf=PDFOptions(...)),再用于 convert_pdf_to_markdown、convert_pdf_to_epub 或 extract_pdf。以同步入口为例,convert_pdf_to_markdown("input.pdf", "output.md") 会把识别结果渲染为 Markdown;extract_pdf("input.pdf", "book.pcex", options) 则先产出可复用的中间格式 .pcex。
Local OCR 配置
三种 local 配置的公共字段
三种 local 配置对象字段完全相同(models_cache_path、local_only、enable_devices_numbers),定义于 pdf_craft/ocr_config.py:
| 字段 | 类型 | 默认值 | 作用 |
|---|---|---|---|
models_cache_path |
路径或 None |
None |
模型缓存目录;None 使用上游模型库的默认缓存位置 |
local_only |
bool |
False |
只使用本地已有模型;设为 True 后禁止运行时下载 |
enable_devices_numbers |
整数可迭代对象或 None |
None |
指定可使用的 GPU 编号;None 使用默认设备映射 |
需要注意两个实现细节:
models_cache_path传入后会经to_path解析为绝对Path对象(pdf_craft/ocr_config.py),enable_devices_numbers会被冻结为不可变tuple。测试 tests/test_ocr_config.py 验证了列表到元组的转换与不可变性。- 这三个类均为
frozen=True的不可变 dataclass,配置对象构造后不可修改,这保证了配置在多线程/多进程复用时的安全性。
安装 local OCR 运行时
local OCR 需要额外安装本地模型运行时:
pip install "pdf-craft[local]"
此外还需要按照设备和操作系统安装支持 CUDA 的 PyTorch,并准备 NVIDIA 驱动、足够的显存和 Poppler。[local] extra 只提供本地 OCR 所需的 Python 运行时(如 Hugging Face、Transformers),不会替你选择或安装适合设备的 CUDA PyTorch wheel。安装后的自检可以运行:
python -c "import torch; print(torch.__version__); print('CUDA available:', torch.cuda.is_available())"
只有输出 CUDA available: True 才满足 local OCR 的运行前提;输出 False 时应改用 vendor OCR,或先修复 PyTorch、驱动与 CUDA 环境。Poppler 的安装与验证细节见 docs/zh-CN/INSTALLATION.md。
基本配置与离线模式
from pdf_craft import DeepSeekOCRLocalConfig, PDFCraft, PDFOptions
ocr = DeepSeekOCRLocalConfig(
models_cache_path="models-cache",
)
craft = PDFCraft(pdf=PDFOptions(ocr=ocr))
craft.convert_pdf_to_markdown("input.pdf", "output.md")
local OCR 需要支持 CUDA 的 PyTorch、可用的 NVIDIA GPU、足够显存和模型文件。首次运行通常会下载模型;下载完成后,可以切换到离线模式:
ocr = DeepSeekOCRLocalConfig(
models_cache_path="models-cache",
local_only=True,
)
离线模式要求对应模型已经完整存在于缓存目录。缓存目录可以在多个运行中复用,但不同模型或不同来源的缓存应分开管理。local_only=True 不会替你下载缺失模型——它只把运行时行为限制为“只读已有缓存”,因此必须先完成下载再开启离线。
指定 GPU
在多 GPU 设备上,可以显式指定可用设备编号:
ocr = DeepSeekOCRLocalConfig(
models_cache_path="models-cache",
enable_devices_numbers=[0, 1],
)
这里的编号由上游 OCR 运行时(doc_page_extractor)解释,通常对应当前进程可见的 CUDA 设备。指定不存在或不可用的设备会在模型加载或 OCR 开始时失败。从测试 tests/test_ocr_config.py 可以看到,PageExtractorNode 会把 model_path(解析后的绝对路径)、local_only 与 enable_devices_numbers 原样透传给 create_deepseek_ocr_page_extractor / create_deepseek_ocr2_page_extractor 工厂函数,load_models() 随后加载对应模型。
三种 local 模型的 preset(ocr_size)
ocr_size 是提取选项(ExtractionOptions 的字段,默认 "gundam"),不是 OCR 配置对象字段。类型定义于 pdf_craft/pdf/types.py:DeepSeekOCRSize = Literal["tiny", "small", "base", "large", "gundam"]。可选值包括 tiny、small、base、large 和 gundam,但不同模型支持的 preset 不完全相同:
- Unlimited OCR local 支持
base和gundam。 - DeepSeek OCR 2 local 的已验证路径使用
base;显式指定tiny会在提取前快速失败。 - 选择其他 backend 时,应以该 backend 的实际支持范围和模型资源为准。
其中“显式指定 tiny 会在提取前快速失败”由 pdf_craft/pdf/page_extractor.py 的 _validate_ocr_size 实现:当配置为 DeepSeekOCR2LocalConfig 且 ocr_size == "tiny" 时直接抛错,提示 deepseek-ocr2-local is not reliable with ocr_size='tiny',应使用 base 走已验证路径。tests/smoke/all_ocr_backends.json 也印证了这一约定:deepseek-ocr2-local 与 unlimited-ocr-local 两条冒烟用例均显式覆盖为 ocr_size: "base"。
例如,下面的完整示例使用 DeepSeek OCR local,并将 preset 设置为 base:
from pdf_craft import (
DeepSeekOCRLocalConfig,
ExtractionOptions,
PDFCraft,
PDFOptions,
)
craft = PDFCraft(pdf=PDFOptions(ocr=DeepSeekOCRLocalConfig(
models_cache_path="models-cache",
)))
options = ExtractionOptions(ocr_size="base")
craft.extract_pdf("input.pdf", "book.pcex", options)
ExtractionOptions 的其余常用字段(见 pdf_craft/craft.py)还包括 page_indexes(只处理指定页码)、dpi(页面渲染 DPI,默认 300)、max_page_image_file_size、max_ocr_tokens / max_ocr_output_tokens(token 预算)、includes_footnotes(脚注二次识别)、ignore_pdf_errors / ignore_ocr_errors、aborted 与 on_ocr_event 回调等。
预下载模型
如果希望把模型下载和实际转换分开(例如在 CI 中先准备模型,运行时全程离线),可以使用 predownload_models:
from pdf_craft import DeepSeekOCRLocalConfig, predownload_models
predownload_models(
ocr=DeepSeekOCRLocalConfig(models_cache_path="models-cache"),
revision=None,
)
转换时使用同一个 models_cache_path,并在确认模型完整后设置 local_only=True。
从源码看,pdf_craft/functions.py 中的 predownload_models 会通过 ensure_ocr_config(ocr, models_cache_path, False) 规范化配置,构造 OCR 对象后调用 recognizer.predownload(revision);OCR.predownload(pdf_craft/pdf/ocr.py)最终委托给 PageExtractorNode.download_models。revision 参数可指定上游模型库的具体版本;None 表示使用默认版本。
Vendor OCR 配置
Vendor OCR 不要求本地 CUDA,但会把页面发送到远程服务。请根据供应商的 endpoint、凭据和模型文档填写配置;pdf-craft 只负责把配置传给对应的 OCR backend。库层不对供应商服务本身做代理或缓存,base_url、模型名及凭据错误会在请求阶段直接失败。
DeepSeek OCR 与 DeepSeek OCR 2
这两个 vendor 配置字段相同,定义于 pdf_craft/ocr_config.py:
| 字段 | 类型 | 默认值 | 作用 |
|---|---|---|---|
base_url |
str |
必填 | OpenAI-compatible 服务的基础 URL |
api_key |
str |
必填 | 服务访问密钥 |
model |
str |
必填 | 服务端模型名 |
temperature |
float 或 None |
None |
传给服务的采样温度 |
top_p |
float 或 None |
None |
传给服务的 nucleus sampling 参数 |
max_tokens |
int |
8000 |
单次请求的最大输出 token 数 |
timeout_seconds |
int |
180 |
单次请求超时时间 |
retry_times |
int |
2 |
可重试网络错误的重试次数 |
retry_interval_seconds |
float |
1.0 |
供应商未给出等待时间时的重试间隔 |
实现层面的两个细节:
api_key使用field(repr=False),repr不会泄露密钥;测试 tests/test_ocr_config.py 明确断言repr中不包含密钥字符串。- 所有 vendor 配置在
__post_init__中调用_validate_vendor_retry(pdf_craft/ocr_config.py):retry_times < 0或retry_interval_seconds < 0会直接抛ValueError,把参数错误提前到构造阶段。
from pdf_craft import DeepSeekOCRVendorConfig, PDFCraft, PDFOptions
ocr = DeepSeekOCRVendorConfig(
base_url="https://example.com/v1",
api_key="your-api-key",
model="deepseek-ocr",
timeout_seconds=180,
)
craft = PDFCraft(pdf=PDFOptions(ocr=ocr))
使用 DeepSeek OCR 2 时,将配置对象和服务端模型名替换为 OCR 2 对应值:
from pdf_craft import DeepSeekOCR2VendorConfig
ocr = DeepSeekOCR2VendorConfig(
base_url="https://example.com/v1",
api_key="your-api-key",
model="deepseek-ocr2",
)
请求构造位于 pdf_craft/pdf/vendor_ocr.py 的 _deepseek_payload:页面以 PNG base64 的 image_url 内容块形式发送,携带 max_tokens、stream: False,并仅在显式给出时才加入 temperature / top_p。请求 URL 通过 _chat_completions_url 规范化(同文件 L675-L679):base_url 以 /v1 结尾时拼接 /chat/completions,否则补 /v1/chat/completions。
百度 Unlimited OCR
Unlimited OCR vendor 配置使用百度服务凭据,定义于 pdf_craft/ocr_config.py:
| 字段 | 类型 | 默认值 | 作用 |
|---|---|---|---|
ak |
str |
必填 | 百度 Access Key |
sk |
str |
必填 | 百度 Secret Key |
base_url |
str |
https://aip.baidubce.com |
百度服务基础 URL |
poll_interval_seconds |
float |
2.0 |
轮询异步任务的间隔 |
timeout_seconds |
int |
180 |
OCR 任务超时时间 |
retry_times |
int |
2 |
可重试网络错误的重试次数 |
retry_interval_seconds |
float |
1.0 |
供应商未给出等待时间时的重试间隔 |
与 DeepSeek vendor 不同,ak / sk 均为必填且同样使用 field(repr=False) 隐藏凭据。Unlimited OCR 是异步任务模型:先提交任务拿 task_id,再轮询 query 直到 success,最后下载解析结果——这四个阶段在 pdf_craft/pdf/vendor_ocr.py 中依次实现,OAuth token 通过 base_url + "/oauth/2.0/token" 换取并缓存复用(同文件 L503-L545)。
from pdf_craft import (
ConcurrentExecutor, FixedCapacity, UnlimitedOCRVendorConfig,
PDFCraft, PDFOptions,
)
ocr = UnlimitedOCRVendorConfig(
ak="your-access-key",
sk="your-secret-key",
)
craft = PDFCraft(pdf=PDFOptions(
ocr=ocr,
ocr_executor=ConcurrentExecutor(FixedCapacity(8)),
))
ocr_executor 是 PDFOptions 的独立字段(pdf_craft/craft.py),用于控制 vendor 请求的并发容量。ConcurrentExecutor(FixedCapacity(8)) 表示最多 8 个远程 operation 并行。注意:只有 vendor 远程请求会占用该容量,PDF 渲染与后续处理仍保持串行(见 pdf_craft/pdf/ocr.py 的注释 “Keep PDF rendering serial while vendor page calls share async capacity”)。
CLI 工具的环境变量映射
库 API 本身不读环境变量,但配套的 pdf_craft_tool 工具层提供了环境变量到配置对象的映射(pdf_craft_tool/runtime.py)。主要变量如下:
PDF_CRAFT_OCR_MODE:backend 模式,取值与六种配置对象一一对应,如deepseek-ocr-local、deepseek-ocr2-local、unlimited-ocr-local、deepseek-ocr-vendor、deepseek-ocr2-vendor、unlimited-ocr-vendor,默认deepseek-ocr-local。- local 系列:
PDF_CRAFT_*_MODELS_CACHE_PATH(默认models-cache)、PDF_CRAFT_*_LOCAL_ONLY(工具层默认True,与库层默认False不同)、PDF_CRAFT_*_ENABLE_DEVICES_NUMBERS。 - DeepSeek vendor 系列:
PDF_CRAFT_DEEPSEEK_OCR_BASE_URL、PDF_CRAFT_DEEPSEEK_OCR_API_KEY、PDF_CRAFT_DEEPSEEK_OCR_MODEL、PDF_CRAFT_DEEPSEEK_OCR_MAX_TOKENS(默认 8000)、PDF_CRAFT_DEEPSEEK_OCR_TIMEOUT_SECONDS(默认 180)等,OCR 2 使用PDF_CRAFT_DEEPSEEK_OCR2_*前缀。 - Unlimited vendor 系列:
PDF_CRAFT_UNLIMITED_OCR_ACCESS_KEY、PDF_CRAFT_UNLIMITED_OCR_SECRET_KEY、PDF_CRAFT_UNLIMITED_OCR_BASE_URL(默认https://aip.baidubce.com)、PDF_CRAFT_UNLIMITED_OCR_POLL_INTERVAL_SECONDS(默认 2.0)。
工具层的冒烟配置(tests/smoke/all_ocr_backends.json)用同一份清单覆盖了全部六种 backend,可作为选择与验证配置的参考。
PDFOptions 的配置边界
PDFOptions 还提供 models_cache_path 和 local_only 两个便捷字段,用于在不显式传入 ocr 时创建默认的 DeepSeekOCRLocalConfig。如果已经传入了 ocr 配置对象,不能同时再传 models_cache_path 或 local_only,否则会抛出 ValueError。
该约束由 pdf_craft/ocr_config.py 的 ensure_ocr_config 实现:当 ocr 非空且 models_cache_path 或 local_only 任一非默认值时,抛出 ValueError("ocr cannot be combined with models_cache_path or local_only.")。测试 tests/test_ocr_config.py 通过 assertRaisesRegex(ValueError, "ocr cannot be combined") 验证了这一行为。
推荐明确选择一种写法,不要混用:
# 推荐:显式使用一个完整 OCR 配置对象。
PDFCraft(pdf=PDFOptions(ocr=DeepSeekOCRVendorConfig(
base_url="https://example.com/v1",
api_key="your-api-key",
model="deepseek-ocr",
)))
# 便捷写法:不传 ocr 时,使用默认的 DeepSeek local OCR 配置。
PDFCraft(pdf=PDFOptions(
models_cache_path="models-cache",
local_only=True,
))
常见运行边界
以下是实际运行中需要重点注意的边界条件,每条都有对应的实现佐证:
- 运行环境边界:local OCR 依赖本地 CUDA、GPU 显存和模型缓存;没有这些条件时选择 vendor OCR。vendor OCR 依赖网络和有效凭据,
base_url、模型名及凭据错误会在请求阶段失败(见_checked_response对 4xx/5xx 的分类处理,pdf_craft/pdf/vendor_ocr.py)。 - 离线模式的边界:
local_only=True不会替你下载缺失模型;请先完成模型下载。 - 并发边界:vendor OCR 只把远程供应商请求交给
ocr_executor并发;PDF 渲染与后续处理仍保持串行。 - 重试与容量释放:retry 会先释放
ocr_executor容量,等待结束后每次 HTTP 尝试重新申请容量。在 pdf_craft/pdf/vendor_ocr.py 中,_retry_request_after_error在重试前先asyncio.sleep(delay)再重新走_request_once,而_request_once内部的网络操作才通过executor.run占槽。延迟优先取RateLimitedError.retry_after(即 HTTPRetry-After头),否则用retry_interval_seconds。 - Unlimited OCR 的 operation 拆分:Unlimited OCR 的 token 获取、任务提交、每次轮询和结果下载分别算一个远程 operation;轮询间隔不占槽,查询或下载重试也不会重新提交页面任务。access token 失效时会刷新 token 后重试当前 operation(
_UnlimitedTokenExpiredError触发 token 失效与刷新,pdf_craft/pdf/vendor_ocr.py 与 L431-L435);供应商明确报告任务处理失败时,才会重新提交当前页面(_UnlimitedResubmitError,错误码 282000 触发,同文件 L882-L885)。 - 限流与配额:HTTP 429 默认按限流重试(
RateLimitedError);如果响应体明确表示余额或配额耗尽(_is_quota_response识别insufficient_quota、quota_exceeded、billing_not_active等),则立即抛OCRBillingError并关闭共享执行器。余额、配额、认证(401/403)和权限错误不可忽略,其他页面错误仍可交给ignore_ocr_errors。回调收到的OCRError会在异常 cause 链中保留供应商原始异常及可用的 HTTP response。Unlimited OCR query 以task_error报告“额度不够”时同样属于不可忽略的配额终止错误(_raise_unlimited_task_error对额度不够/额度不足/配额/余额不足/quota标记的处理,同文件 L889-L911)。 - token 预算:设置任一累计 OCR token 预算(
max_ocr_tokens/max_ocr_output_tokens)后,vendor 请求会逐页结算并将精确的剩余预算传给下一页;预算耗尽即抛TokenLimitError(pdf_craft/pdf/ocr.py)。local OCR 路径同样逐页扣减剩余预算(同文件 L632-L633、L764-L769)。 - preset 与 backend 的关系:
ocr_size由提取选项控制,不能用来改变 vendor/local backend;backend 由 OCR 配置对象决定。 - 页面级断点续跑:识别结果按页写入
page_N.xml,完成页在done文件标记;存在.failed文件或缺失页时会在下次运行继续补齐,page_pixel_sizes.json保存每页几何信息以支持缓存命中(pdf_craft/pdf/ocr.py)。 - 翻译与 OCR 的独立性:翻译所需的文本 LLM 与 OCR backend 是两套独立配置。OCR 只负责把页面图像变成结构化文本,翻译由章节级 transformer 调用独立的 LLM 完成,本指南不讨论翻译配置。
小结
pdf-craft 的 OCR backend 设计把“模型选择”与“运行位置”解耦为六种显式配置对象:local 三件套共享缓存目录、离线开关与 GPU 编号字段,vendor 三件套分别面向 OpenAI-compatible endpoint 与百度异步任务接口。库层 API 通过 PDFOptions(ocr=...) 显式传参、不读环境变量,且用 ensure_ocr_config 强制“显式配置与便捷字段二选一”;工具层才提供 PDF_CRAFT_OCR_MODE 等环境变量映射。掌握每个字段的默认值、ocr_size preset 约束、并发/重试/配额边界,就能根据本机硬件与供应商资源做出正确的 backend 选择,并写出可复现、可离线、可断点续跑的 OCR 流程。