Docling Service Client 深度解析:将文档转换卸载到 docling-serve 远程服务的完整指南
Docling Service Client(docling.service_client)是 Docling 提供的客户端 SDK,它把文档解析与转换工作交给远程运行的 docling-serve 服务执行,而不是在本地加载任何模型。读完本文,你将掌握 Service Client 的安装与配置、单文档/并发/分块等核心 API、异步与 Job 生命周期管理、docling convert-remote 命令行用法,并能结合仓库源码理解其重试、结果目标(result targets)与错误处理的底层机制。
Service Client 是什么,何时使用
Service Client 的核心价值在于把推理从"调用方机器"剥离出去,典型适用场景有三类:
- 低延迟与规模:服务端保持模型常驻(warm)状态,并可并发处理大量文档,免去每次冷启动加载模型的开销;
- 零本地 ML 依赖:调用机器不需要 torch、OCR 引擎或 GPU,只需安装
docling-slim[service-client]这个轻量 extra; - 托管运维:只需一个服务 URL 和 API Key 即可开工。
从接口设计看,Service Client 与本地 DocumentConverter 保持相似的调用形态(同样是"给来源、拿结果、导出 Markdown/JSON"),因此从本地 SDK 迁移的代码基本可以平移。
服务从哪里来:自托管或托管
你永远是针对一个已在运行的 docling-serve 实例进行转换。获取实例有两条路:
- 自托管
docling-serve:自行运行开源 API 服务器(容器化部署在自己的基础设施上),获得完全控制权和自有硬件; - 托管服务:由提供方替你运行
docling-serve,你只需要服务 URL 和 API Key。托管方案适合不想运维基础设施、不想配置 GPU 的场景。
无论哪种,客户端代码完全一致——区别仅在 URL 与 API Key。
安装
pip install "docling-slim[service-client]"
# 完整 docling 包也已包含该 extra
仓库中的 docling-slim 包 定义了该 extra 的依赖闭包。命令行侧,cli/main.py 只有在检测到 service-client 依赖可用时才会注册 convert-remote 子命令,否则跳过注册并保留其余子命令可导入——这意味着不装该 extra 时 docling convert-remote 不会出现,但 docling convert 不受影响。
配置:环境变量与 .env
客户端库与 docling convert-remote CLI 读取同一套配置,来源为环境变量或工作目录下的 .env 文件:
DOCLING_SERVICE_URL=https://your-docling-service.example.com
DOCLING_SERVICE_API_KEY=your-api-key # 服务无鉴权时可省略
CLI 的凭证解析优先级为:命令行 flag > 环境变量 > .env 文件。.env 的加载实现在 cli/remote.py 的 _load_dotenv() 中:它用 python-dotenv 的 load_dotenv(find_dotenv(usecwd=True), override=False) 从用户当前工作目录查找 .env,且 override=False 保证真实环境变量始终优先于 .env 中的值;python-dotenv 未安装时该步骤静默跳过,属于 best-effort 行为。
转换单个文档
最小可用示例:
from docling.service_client import DoclingServiceClient
client = DoclingServiceClient(url=..., api_key=...) # 或依赖 env / .env
result = client.convert(source="path/to/report.pdf") # 本地路径或 http(s) URL
print(result.document.export_to_markdown())
默认行为(启用 OCR、表格结构分析、Markdown 输出)与本地 DocumentConverter 一致,仅在需要时用 ConvertDocumentsOptions 覆盖:
from docling.datamodel.service.options import ConvertDocumentsOptions
result = client.convert(
source="report.pdf",
options=ConvertDocumentsOptions(...), # 每个请求的 pipeline 覆盖
)
convert() 的完整参数(来自源码签名)
结合 service_client/client.py 中的定义,convert() 的签名为:
def convert(
self,
source: SourceType, # Path | str | DocumentStream | HttpSourceRequest
headers: dict[str, str] | None = None,
max_num_pages: int | None = None, # 客户端级页数限制(preflight 校验)
max_file_size: int | None = None, # 客户端级文件大小限制(字节)
page_range: PageRange | None = None,
options: ConvertDocumentsRequestOptions | None = None,
raises_on_error: bool = True,
) -> ConversionResult
几个值得注意的实现细节:
- 来源归一化:字符串若可解析为合法 http(s) URL,会被包成
HttpSourceRequest直接发给服务(服务端自行拉取,客户端不下载);否则按本地Path走 multipart 上传。DocumentStream(内存流)同样受支持。 - options 合并语义:从源码
_resolve_options()看,客户端只覆盖调用方显式设置的字段(基于model_fields_set),其余保留客户端默认值;且显式设为None可以清除客户端默认值。page_range/max_num_pages/max_file_size会合成DocumentLimits用于本地 preflight——例如文件超过max_file_size时,客户端在发送前就返回一个SKIPPED状态的失败结果,不浪费一次网络往返。 - 返回与异常:默认
raises_on_error=True时,状态非SUCCESS/PARTIAL_SUCCESS的结果会抛出ConversionError;置为False则直接返回带status与errors的ConversionResult,方便批量场景自行判定。
客户端构造参数与默认值
DoclingServiceClient 构造函数(service_client/client.py)提供了一组可调的运行参数,源码中的默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
url |
必填 | 服务基地址,必须是绝对 http(s) URL,且不能带 /v1 后缀 |
api_key |
"" |
通过 X-Api-Key 请求头与 WebSocket 查询参数发送 |
options |
None |
客户端级默认 ConvertDocumentsOptions(深拷贝保存) |
status_watcher |
WEBSOCKET |
任务状态跟踪方式:websocket 或 polling |
ws_fallback_to_poll |
True |
WebSocket 失败时回退到轮询 |
poll_server_wait |
5.0 秒 |
轮询时服务端侧等待时长 |
poll_client_interval |
同上 | 客户端两次轮询的间隔,未设置时等于 poll_server_wait |
job_timeout |
300.0 秒 |
等待单个任务完成的客户端超时 |
max_concurrency |
8 |
并发上限(convert_all 使用);硬上限为 512 |
http_retries |
3 |
可重试错误的重试次数 |
http_connect_timeout / http_read_timeout |
10.0 / 60.0 秒 |
HTTP 超时 |
artifact_download_timeout |
60.0 秒 |
预签名产物下载超时 |
max_artifact_download_bytes |
512 MiB |
产物下载体积上限(流式累计校验) |
此外,客户端在每次请求中附带 Accept-Docling-Document-Version 头,告知服务端本端 docling-core 可读取的最新 DoclingDocument 版本,服务端需要时会向下投影兼容——这是客户端与服务端版本解耦的关键机制。
并发转换多个文档
for result in client.convert_all(
source=["a.pdf", "b.pdf", "https://example.com/c.pdf"],
max_concurrency=4,
):
print(result.input.file.name, result.status)
convert_all() 接收可迭代来源集合(本地路径、内存流、http(s) URL 混合),逐个产出 ConversionResult。其底层实现(_convert_all_async())值得理解:
- 同步版本内部构造了一个私有事件循环,把并发扇出委托给原生异步客户端
submit_and_retrieve_each(),因此无额外线程开销; max_concurrency控制同时在途的任务数,默认取客户端级的max_concurrency(默认 8),单次调用可用参数覆盖,取值必须在1..512之间;- 结果按输入顺序产出(
ordered=True):慢的任务会"占位",前面的结果先补齐再 yield; - 单个来源失败不会中断整个批次——失败项被构造为带错误信息的
FAILURE状态ConversionResult,其余文档继续处理。
远程分块(RAG 场景)
from docling.service_client import ChunkerKind
response = client.chunk(source="report.pdf", chunker=ChunkerKind.HYBRID)
# 可选:ChunkerKind.HYBRID 或 ChunkerKind.HIERARCHICAL
chunk() 是"提交任务 + 阻塞取结果"的组合糖:它内部调用 submit_chunk() 获得任务句柄,再以客户端 job_timeout 等待并取回 ChunkDocumentResponse。从 client.py 的 _submit_chunk_task() 看,两种 chunker 分别对应服务端的 /v1/chunk/hybrid/{file,source}/async 与 /v1/chunk/hierarchical/{file,source}/async 端点,分块参数(HybridChunkerOptions / HierarchicalChunkerOptions)随请求一并提交,且默认 include_converted_doc=False、结果走 InBody 目标。需要自定义分块参数(如 max_tokens、tokenizer)时,可参考 CLI 中 --chunks-max-tokens、--chunks-tokenizer 的映射思路,通过 options 中的分块选项传递。
异步客户端与 Job 生命周期
两个进阶能力:
- 异步客户端:
AsyncDoclingServiceClient(service_client/_async_client.py)以await镜像同步 API,适用于 FastAPI 等异步服务;同步客户端的批量接口内部也是复用它实现的。 - 显式任务句柄:当需要任务生命周期控制、结果目标(result targets,如预签名 URL / S3)、逐项扇出时,使用
submit*系列方法。它们返回ConversionJob句柄,而不是阻塞等结果。
ConversionJob 句柄
service_client/job.py 定义了同步 ConversionJob 与异步 AsyncConversionJob,二者共享同一身份属性与操作:
job = client.submit(source="report.pdf", target=S3Target(bucket=..., key_prefix=...))
print(job.task_id, job.submitted_at) # 任务身份
print(job.status, job.queue_position) # "pending" 或任务状态;队列位置
print(job.done) # 是否到达终态
job.poll(wait=0.0) # 主动拉一次状态(wait 为服务端侧等待秒数)
job.watch(timeout=...) # 迭代状态更新(WebSocket 或轮询)
result = job.result(timeout=300) # 等待终态并取回结果
result(timeout) 语义:若任务未达终态则阻塞等待至 timeout(默认取客户端 job_timeout),超时抛出 TaskTimeoutError;到达终态后从 /v1/result/{task_id} 取回按 target 类型解析的结果对象。
提交方法族与结果目标
submit / submit_batch / submit_and_retrieve_each 从 service_client/init.py 统一导出,配套的批处理来源/目标类型为 BatchSourceRequestInput / BatchSourceRequestItem / BatchTargetRequestInput(定义在 datamodel/service/requests.py),存储目标类型 S3Target、PresignedUrlTarget 等定义在 datamodel/service/targets.py。支持的提交目标(SubmitTarget 类型别名)包括:
| 目标类型 | 结果形态 | 说明 |
|---|---|---|
PresignedUrlTarget |
PresignedUrlConvertResponse |
服务端返回预签名下载 URL,客户端按需下载 |
InBodyTarget |
ConvertDocumentResponse |
结果内联在 HTTP 响应体中(JSON 文档随响应返回) |
ZipTarget |
RawServiceResult |
结果是 zip 原始字节 |
S3Target / AzureBlobTarget / GoogleCloudStorageTarget / GoogleDriveTarget |
预签名文档响应 | 服务端直接把产物写入你的对象存储 |
GenericTargetRequest |
插件目标 | 服务端插件提供的自定义存储目标 |
两个重要的自动行为(源码注释与 _submit_conversion_job_with_auto_target() 可印证):
submit()不带target时默认走"预签名优先":先以PresignedUrlTarget提交;若服务端因未配置 artifact 存储而拒绝(400/422 且 detail 命中特征),自动回退到InBodyTarget重新提交;- 高层
convert()/convert_all()强制追加 JSON 输出格式:因为客户端要重建DoclingDocument,无论服务端返回预签名产物还是内联结果,都需要 JSON 文档载荷。
submit_and_retrieve_each(items, max_in_flight, ordered, target) 则提供"提交 N 个 ConversionItem、逐项产出 (item, 结果或异常) 元组"的扇出能力——每个 item 可携带独立的 source、options、headers 与 metadata。注意:同步客户端的这类批量方法不能在活跃的 asyncio 事件循环内调用(_ensure_sync_bridge_allowed() 会显式抛 RuntimeError),异步场景应直接使用 AsyncDoclingServiceClient。
产物下载的安全性
高层 convert() 拿到预签名 URL 后会下载产物并在本地重建文档。从 client.py 看,这条"物化"(materialization)路径内置了多重防护:
- SSRF 防护:每个下载 hop 都校验目标必须是公网可达地址(
_is_safe_artifact_url()),拒绝指向内网/回环/链路本地地址的预签名 URL;重定向手动跟随且逐跳重校验(上限 5 次); - 体积上限:流式下载累计超过
max_artifact_download_bytes(默认 512 MiB)即中止; - API Key 隔离:产物下载使用独立 httpx 客户端,不会把服务的
X-Api-Key头泄露给外部存储端点; - zip-slip 与路径逃逸防护:解压 resource_bundle 时校验成员路径,且文档 JSON 中引用的相对图片 URI 被限制在解压目录内;
- 优雅降级:下载/重建失败不抛异常,而是转为
FAILURE状态的ConversionResult,保证convert_all()能继续处理后续文档。
错误处理:类型化异常族
客户端抛出全部继承自 DoclingServiceClientError 的类型化异常(service_client/exceptions.py),捕获基类即可兜住所有:
from docling.service_client import (
DoclingServiceClientError,
ConversionError,
ServiceUnavailableError,
TaskTimeoutError,
UsageLimitExceededError,
ResultExpiredError,
)
| 异常 | 触发场景(源码可查的映射) |
|---|---|
ConversionError |
单次转换以失败状态结束(raises_on_error=True 时) |
ServiceUnavailableError |
服务 5xx 重试耗尽、传输层错误(如 GET/HEAD/OPTIONS 的 TransportError)重试耗尽 |
ServiceError |
其他 4xx 不可重试错误,携带 status_code 与服务端 detail |
ResponseSchemaMismatchError |
2xx 响应无法反序列化为预期模型(客户端/服务端版本漂移的信号) |
TaskTimeoutError |
等待任务终态超过 job_timeout |
TaskNotFoundError |
轮询/取结果时任务 ID 不存在(404 + "Task not found.") |
ResultNotReadyError / ResultExpiredError |
结果 404 但任务未到终态 / 已终态但结果已被清理 |
TaskExecutionError |
服务端任务级编排失败(TaskFailureResult),携带 PublicFailureInfo |
UsageLimitExceededError |
HTTP 402:用量配额耗尽,附 current_usage 与 limit 字段 |
ArtifactDownloadError |
预签名产物下载失败/超限(通常内部消化为 FAILURE 结果,不直接透出) |
重试策略同样在源码中可验证:500/502 按指数退避重试(基数 1 秒,1.0 * 2**attempt),429/503 优先遵循服务端 Retry-After 头(支持秒数与 HTTP-date 两种格式),均受 http_retries(默认 3)约束。
CLI:docling convert-remote
不写代码的一次性远程转换:
docling convert-remote report.pdf \
--service-url https://docling.example.com --to md --output /tmp/
该命令内部同样驱动同步 DoclingServiceClient,并复用与本地 docling convert 完全相同的导出器,因此落盘产物一致。完整实现见 cli/remote.py,可用 flag 一览(仅暴露服务端真正生效的选项;--device、--threads 等本地执行类 flag 被有意省略):
| Flag | 默认 | 说明 |
|---|---|---|
--service-url |
env DOCLING_SERVICE_URL |
服务基地址(必填,三者取其一) |
--api-key |
env DOCLING_SERVICE_API_KEY |
鉴权密钥,无鉴权服务可省略 |
--from |
全部受支持格式 | 输入格式白名单,同时用于过滤目录遍历 |
--to(可重复) |
md |
输出格式:md/json/chunks/html/dclx/doctags 等 |
--chunks-type |
hybrid |
--to chunks 的分块器:hybrid 或 hierarchical |
--chunks-max-tokens |
无 | 每个 chunk 的最大 token 数 |
--chunks-tokenizer |
sentence-transformers/all-MiniLM-L6-v2 |
HF tokenizer 名称/路径,仅 hybrid 生效 |
--ocr / --no-ocr |
开 | 是否对位图内容执行 OCR |
--force-ocr |
关 | 用 OCR 文本替换已有文本层 |
--ocr-lang |
无 | 逗号分隔的 OCR 语言列表(引擎特定名称) |
--tables / --no-tables |
开 | 是否做表格结构分析 |
--pipeline |
standard |
服务处理 PDF/图片所用 pipeline |
--enrich-code / --enrich-formula / --enrich-picture-classes / --enrich-picture-description / --enrich-chart-extraction |
关 | 各类富化模型开关 |
--image-export-mode |
服务默认 | 图片导出模式:embedded/placeholder/referenced |
--page-range |
无 | 仅转换指定页范围,如 1-4(从 1 计) |
--document-timeout |
无 | 服务端处理每个文档的超时(秒) |
--abort-on-error / --no-abort-on-error |
关 | 出错即中止整批 |
--max-concurrency |
8 |
客户端并发文档数 |
--timeout |
300 秒 |
客户端等待每个 Job 完成的超时 |
--watcher |
websocket |
状态跟踪方式:websocket 或 polling |
--output |
. |
结果输出目录(不存在会自动创建) |
-v / -vv |
无 | 日志级别 info / debug |
退出码约定:0 成功;1 运行时/连接失败(服务不可达、转换错误);2 用法/配置错误(无法解析出服务 URL)。命令执行前会先调用 client.health()(对应 GET /health)做快速失败检查,连不通直接以退出码 1 报错,避免逐文档重试的漫长等待。
几个实用例子(摘自该命令的 epilog):
# 单文件,凭证来自 env / .env
docling convert-remote report.pdf
# 显式端点与密钥,Markdown + JSON 双格式输出
docling convert-remote --service-url https://docling.example.com \
--api-key "$KEY" --to md --to json --output ./out report.pdf
# 整个目录,只取 PDF 和 DOCX,关闭 OCR
docling convert-remote --from pdf --from docx --no-ocr ./inbox
来源可以是本地文件、本地目录(按 --from 过滤遍历)或 http(s) URL;URL 不会被客户端预下载,而是作为 HTTP source 请求交给服务端拉取。
小结
Service Client 把 Docling 的转换能力封装成一组"提交任务—跟踪状态—取回结果"的远程原语:高层 convert()/convert_all() 屏蔽了任务细节并自动处理预签名产物下载与 InBody 回退;submit* 家族 + ConversionJob 则暴露完整任务生命周期,配合 S3Target、PresignedUrlTarget、BatchTargetRequestInput 等目标类型覆盖大规模、跨存储的转换与分块流水线;类型化异常与指数退避重试让生产环境下的故障可诊断、可恢复。仓库内 tests/test_service_client_* 系列测试与 examples/service_client/ 下的 convert.py、chunk.py、batch.py 示例,是进一步验证上述行为与编写自己流水线的最佳起点。
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