第 3 课
用 HybridChunker 切块做 RAG,再把 Docling 接成 MCP 工具
用命令行 chunks 输出直接得到带标题、页码的 JSONL;在 Python 里用 HybridChunker 指定分词器与 token 上限,对比 128 与 512 上限的块数;最后用 uvx 以 stdio 本机模式拉起 docling-mcp 3.5.0,用官方 mcp SDK 列出 21 个工具并完成一次转换。
课程目录第 3 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
用 HybridChunker 切块做 RAG,再把 Docling 接成 MCP 工具
本课对照的官方页面:Chunking 概念 、Hybrid chunking 示例 、Docling MCP 用法 、GitHub:docling-project/docling-mcp 。测试环境同前:2026-11-08,Linux x86_64 CPU,Python 3.12.15,docling 2.148.0,docling-mcp 3.5.0。对照结果均为真实输出节选;MCP 部分用官方 mcp Python SDK 写了一个最小客户端实测,没有接入 Claude Desktop 等图形客户端。
做 RAG 时,不能把整本文档塞给向量模型,要先切成小块。按字数硬切会把一张表、一个段落从中间劈开。Docling 的 HybridChunker 先按文档结构(标题、段落、表格、列表)切,再按向量模型的 token 上限细分或合并,每块还带着所属标题和页码。本课先用命令行一步出块,再用 Python 控制 token 上限,最后把 Docling 接成 MCP 服务,让 AI 助手直接调用。
第 1 步:命令行直接输出 chunks
输出格式选 chunks,Docling 会在转换后直接切块,写成 JSONL(每行一块):
docling convert 2408.09869.pdf --page-range 1-2 --no-ocr --to chunks --chunks-max-tokens 256 --output out3
ls -lh out3
对照结果:
INFO docling.document_converter: Finished converting document 2408.09869.pdf in 1.98 sec.
-rw-r--r-- 1 box box 14K Nov 8 00:09 2408.09869.chunks.jsonl
看看每块长什么样:
python - <<'PY'
import json
rows = [json.loads(l) for l in open("out3/2408.09869.chunks.jsonl")]
print("chunks:", len(rows))
print(sorted(rows[0].keys()))
for r in rows[:4]:
print(r["chunk_index"], r["num_tokens"], r["headings"], r["page_numbers"], r["text"][:50].replace("\n", " / "))
PY
对照结果:
chunks: 8
['captions', 'chunk_index', 'doc_items', 'filename', 'headings', 'metadata', 'num_tokens', 'page_numbers', 'raw_text', 'text']
0 110 ['Version 1.0'] [1] Version 1.0 / Christoph Auer MaksymLysak Ahmed Nas
1 90 ['Abstract'] [1] Abstract / This technical report introduces Docli
2 252 ['1 Introduction'] [1, 2] 1 Introduction / Converting PDF documents back
3 122 ['1 Introduction'] [2] 1 Introduction / - Converts PDF documents to JSON
读法:
text已经在开头拼上了所属标题(「1 Introduction」),这就是官方说的 contextualize 文本,直接拿去做向量;raw_text是不带标题的原文。page_numbers记录块来自哪几页,第 2 块跨了第 1、2 页。回答时可以据此标注出处。num_tokens都没超过设的 256。默认分词器是 sentence-transformers 的 all-MiniLM-L6-v2,第一次运行会自动下载。
第 2 步:在 Python 里控制分词器和 token 上限
官方提醒:切块用的分词器要和你的向量模型一致,否则 token 数对不上。新建 chunk_for_rag.py,直接读上一课保存的 scratch/report.json,不必重新跑 PDF 模型:
from docling.chunking import HybridChunker
from docling_core.transforms.chunker.tokenizer.huggingface import HuggingFaceTokenizer
from docling_core.types.doc import DoclingDocument
from transformers import AutoTokenizer
doc = DoclingDocument.load_from_json("scratch/report.json")
EMBED_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2"
tokenizer = HuggingFaceTokenizer(
tokenizer=AutoTokenizer.from_pretrained(EMBED_MODEL_ID),
max_tokens=128, # 和向量模型保持一致;这里故意设小,方便观察
)
chunker = HybridChunker(tokenizer=tokenizer, merge_peers=True)
chunks = list(chunker.chunk(dl_doc=doc))
print("chunks:", len(chunks))
for i, chunk in enumerate(chunks[:4]):
enriched = chunker.contextualize(chunk=chunk)
pages = sorted({p.page_no for it in chunk.meta.doc_items for p in it.prov})
print(f"=== {i} === headings={chunk.meta.headings} pages={pages} "
f"text_tokens={tokenizer.count_tokens(chunk.text)} enriched_tokens={tokenizer.count_tokens(enriched)}")
print(repr(enriched[:120]))
运行:
python chunk_for_rag.py
对照结果:
chunks: 43
=== 0 === headings=['Version 1.0'] pages=[1] text_tokens=106 enriched_tokens=110
'Version 1.0\nChristoph Auer MaksymLysak Ahmed Nassar Michele Dolfi Nikolaos Livathinos Panos Vagenas Cesar Berrospi Ramis'
=== 1 === headings=['Abstract'] pages=[1] text_tokens=89 enriched_tokens=90
'Abstract\nThis technical report introduces Docling, an easy to use, self-contained, MITlicensed open-source package for P'
=== 2 === headings=['1 Introduction'] pages=[1] text_tokens=121 enriched_tokens=123
'1 Introduction\nConverting PDF documents back into a machine-processable format has been a major challenge for decades du'
=== 3 === headings=['1 Introduction'] pages=[1] text_tokens=121 enriched_tokens=123
'1 Introduction\nAs of today, only a handful of open-source tools cover PDF conversion, leaving a significant feature and '
对照要点:
- 同样是前 5 页,上限 128 时切成 43 块;不传分词器、用默认上限(MiniLM 的 512)时,本机实测是 24 块。上限越小,块越多。
enriched_tokens比text_tokens多出的几个 token 就是拼上去的标题。HybridChunker 保证的是拼完标题后不超上限。merge_peers=True(默认)会把同一标题下过短的相邻块合并,避免出现一两句话的碎块。- 运行时如果看到 transformers 提示「sequence length is longer than the specified maximum」,官方说明这是误报,可以忽略。
拿到 enriched 文本后,按你用的向量库写入即可。官方 Examples 里有 LangChain、LlamaIndex、Haystack、Milvus、Qdrant 等完整 RAG 示例,本课不展开。
第 3 步:把 Docling 接成 MCP 服务
docling-mcp 把转换、导出、按锚点检索段落等能力包装成 MCP 工具。注意 3.x 版的默认模式是「remote」(转发给 docling-serve 服务)。想在本机转换,要装 local 扩展并把模式环境变量设成 local。先看帮助:
uvx --from docling-mcp docling-mcp-server --help
对照结果(节选):
Usage: docling-mcp-server [OPTIONS]
[tools]:<conversion|generation|manipulation|llama-index-rag|llama-stack-rag|llama-stack-ie>...
Arguments: tools The default list is conversion, generation, manipulation
Options:
--transport <stdio|sse|streamable-http> [default: streamable-http]
--host <str> [default: localhost]
--port <int> [default: 8000]
命令行默认传输方式是 streamable-http,而 Claude Desktop、LM Studio 这类桌面客户端用 stdio,所以配置里要显式写上 stdio。本机转换模式的客户端配置(Claude Desktop 的 claude_desktop_config.json、LM Studio 的 mcp.json 结构相同):
{
"mcpServers": {
"docling": {
"command": "uvx",
"args": ["--from", "docling-mcp[local]", "docling-mcp-server", "--transport", "stdio"],
"env": { "DOCLING_MCP_CONVERSION_MODE": "local" }
}
}
}
官方 MCP 用法页和 docling-mcp README 里的最简配置没有写传输方式和转换模式。按 3.5.0 的帮助输出,默认传输是 streamable-http,README 的环境变量表里默认模式是 remote;在本机给桌面客户端用,建议照上面这份显式写全。
第 4 步:用一个最小客户端验证 MCP 工具
没有图形客户端也能验证。新建 mcp_check.py,用官方 mcp Python SDK 以 stdio 方式拉起服务,列出工具,再转换本地 PDF 并导出 Markdown:
import asyncio, json, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
PDF = os.path.abspath("2408.09869.pdf")
params = StdioServerParameters(
command="uvx",
args=["--from", "docling-mcp[local]", "docling-mcp-server", "--transport", "stdio"],
env={**os.environ, "DOCLING_MCP_CONVERSION_MODE": "local"},
)
async def main():
async with stdio_client(params) as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
tools = (await s.list_tools()).tools
print("tools:", len(tools))
for t in tools[:5]:
print("-", t.name)
res = await s.call_tool("convert_document_into_docling_document", {"source": PDF})
key = json.loads(res.content[0].text)["document_key"]
print("document_key:", key)
md = await s.call_tool("export_docling_document_to_markdown", {"document_key": key, "max_size": 300})
print(json.loads(md.content[0].text)["markdown"][:200])
asyncio.run(main())
运行(uv run 临时装上 mcp SDK,不污染虚拟环境):
uv run --with mcp python mcp_check.py
对照结果(节选,日志已省略):
tools: 21
- check_document_in_local_cache
- remove_document_from_local_cache
- convert_document_into_docling_document
- convert_directory_files_into_docling_document
- list_cached_documents
document_key: a9ed18d7db88b067159d0eb03cf625a1
<!-- image -->
## Docling Technical Report
## Version 1.0
Christoph Auer MaksymLysak Ahmed Nassar Michele Dolfi Nikolaos Livathinos ...
默认加载的 21 个工具分三组:转换与缓存(convert、list_cached、save 等)、生成(新建文档、加标题段落表格)、修改与检索(按锚点概览、搜索、取文、改文、删除)。MCP 里的转换走完整默认流水线(开 OCR),9 页 PDF 本机耗时 42.51 秒,和课时 1 的默认跑法相当;转好的文档留在服务缓存里,后续导出、检索不再重复转换。接进 Claude Desktop 后,你可以直接说「把这个 PDF 转成 Markdown,并找出所有提到 TableFormer 的段落」,助手会依次调用这些工具。
常见坑
- MCP 一直报连不上 docling-serve:3.x 默认 remote 模式。本机用请装 local 扩展并设置模式环境变量,见第 3 步配置。
- 第一次调用很慢:要下载版面模型和表格模型,之后走缓存。
- 向量模型换了:记得把分词器 ID 和 token 上限一起换,命令行用分词器参数(见下方代码块)。
docling convert 2408.09869.pdf --page-range 1-2 --no-ocr --to chunks --chunks-tokenizer BAAI/bge-m3 --chunks-max-tokens 512 --output out-chunks
实测:同样前 2 页,换成 BAAI/bge-m3 分词器、上限 512 后切成 6 块,各块 token 数为 104、100、425、361、184、153(MiniLM 上限 256 时是 8 块)。首次会自动下载 bge-m3 的分词器文件。
小结
三课下来,你用 Docling 2.148 完成了:本机安装、PDF 转 Markdown、关 OCR 提速、只转指定页、导出 JSON 与表格 CSV、转 HTML,用 HybridChunker 按结构和 token 上限切块,最后把 Docling 接成 MCP 服务并用最小客户端验证了 21 个工具。接下来可以把 chunks.jsonl 写进你的向量库,或者把 MCP 配置贴进 Claude Desktop 实际用起来。
