用Docling+Spring AI搭建PDF解析与RAG入库管道:表格、Metadata和质量门禁

用Docling+Spring AI搭建PDF解析与RAG入库管道:表格、Metadata和质量门禁

文章摘要

Spring AI提供DocumentReader、DocumentTransformer和VectorStore等ETL能力,但复杂PDF的版面、表格和OCR往往需要更专业的解析工具。Docling可以将PDF解析成包含页面、标题、段落、表格和图片信息的结构化文档。本文通过“Python Docling解析服务+Spring Boot入库服务”的方式,实现PDF转换、表格导出、统一Chunk模型、质量校验和Spring AI VectorStore写入。

一、为什么采用两段式架构

Docling主要使用Python生态,Spring AI主要面向Java和Spring Boot。

推荐:

文件上传 → Docling解析服务 → 标准化Document JSON → Spring Boot质量检查 → Chunk → Embedding → VectorStore

而不是强行在Java中复刻所有PDF版面分析能力。

两段式的优势:

  • 解析能力独立升级;
  • Java业务服务保持稳定;
  • 可以替换解析引擎;
  • 解析失败可单独重试;
  • 更容易保存中间产物;
  • 表格和图片可以单独处理。

二、统一的解析结果协议

定义:

{"document_id":"DOC-001","filename":"产品手册.pdf","status":"SUCCEEDED","pages":20,"elements":[{"element_id":"E-001","type":"SECTION_HEADER","page":1,"text":"第一章 产品介绍","metadata":{}},{"element_id":"E-002","type":"PARAGRAPH","page":1,"text":"……","metadata":{"section_path":["第一章 产品介绍"]}}],"quality":{"empty_page_ratio":0,"ocr_page_ratio":0.15,"table_count":4}}

Java端只依赖该协议,不直接依赖Docling内部对象。

三、安装Docling

python-mvenv .venvsource.venv/bin/activate pipinstalldocling fastapi uvicorn python-multipart pandas

首次运行可能下载版面、表格或OCR模型,应在部署前预热,不要让生产第一个请求临时下载模型。

四、基础PDF转换

frompathlibimportPathfromdocling.document_converterimportDocumentConverter converter=DocumentConverter()result=converter.convert(Path("产品手册.pdf"))document=result.document markdown=document.export_to_markdown()Path("output.md").write_text(markdown,encoding="utf-8")

Docling输出的不只是Markdown,还可以访问结构化文档元素。

五、导出表格

frompathlibimportPathdefexport_tables(document,output_dir:Path)->list[dict]:output_dir.mkdir(parents=True,exist_ok=True)tables=[]forindex,tableinenumerate(document.tables):dataframe=table.export_to_dataframe()table_id=f"T-{index+1:04d}"csv_path=output_dir/f"{table_id}.csv"html_path=output_dir/f"{table_id}.html"dataframe.to_csv(csv_path,index=False)dataframe.to_html(html_path,index=False)tables.append({"table_id":table_id,"headers":list(dataframe.columns),"rows":dataframe.fillna("").to_dict(orient="records"),"markdown":dataframe.to_markdown(index=False)})returntables

表格应同时保存:

  • Markdown;
  • CSV;
  • 行列JSON;
  • 页面位置;
  • 标题和单位。

六、构建FastAPI解析服务

frompathlibimportPathfromtempfileimportNamedTemporaryFilefromfastapiimportFastAPI,UploadFilefromdocling.document_converterimportDocumentConverter app=FastAPI()converter=DocumentConverter()@app.post("/api/parse")asyncdefparse(file:UploadFile):suffix=Path(file.filenameor"upload.pdf").suffixwithNamedTemporaryFile(suffix=suffix,delete=False)astemp:content=awaitfile.read()temp.write(content)temp_path=Path(temp.name)try:result=converter.convert(temp_path)document=result.documentreturn{"filename":file.filename,"status":"SUCCEEDED","markdown":document.export_to_markdown(),"tables":export_tables_to_json(document)}finally:temp_path.unlink(missing_ok=True)

生产环境还要限制:

文件大小 页数 格式 超时 并发 临时目录 恶意文件

七、不要只返回一个Markdown字符串

Markdown适合展示,但企业RAG需要Metadata。

建议解析服务返回元素:

{"type":"PARAGRAPH","text":"平台支持批次效期管理。","page":8,"bbox":[100,200,500,260],"section_path":["第三章 仓储管理","3.2 批次管理"],"content_hash":"..."}

这样可以支持:

  • 页面引用;
  • 章节分块;
  • 相邻元素合并;
  • 表格和正文区分;
  • 解析质量追踪。

八、Spring Boot解析客户端

publicinterfaceDocumentParseClient{ParsedDocumentparse(Resourceresource,Stringfilename);}

WebClient实现:

@ComponentpublicclassDoclingParseClientimplementsDocumentParseClient{privatefinalWebClientwebClient;publicDoclingParseClient(WebClient.Builderbuilder,@Value("${docling.base-url}")StringbaseUrl){this.webClient=builder.baseUrl(baseUrl).build();}@OverridepublicParsedDocumentparse(Resourceresource,Stringfilename){MultipartBodyBuilderbody=newMultipartBodyBuilder();body.part("file",resource).filename(filename);returnwebClient.post().uri("/api/parse").bodyValue(body.build()).retrieve().bodyToMono(ParsedDocument.class).timeout(Duration.ofMinutes(5)).block();}}

生产环境应避免无限block,并配置连接池、超时和重试策略。

九、Java领域模型

publicrecordParsedElement(StringelementId,Stringtype,intpage,Stringtext,List<String>sectionPath,Map<String,Object>metadata){}
publicrecordParsedDocument(Stringfilename,Stringstatus,intpages,List<ParsedElement>elements,Map<String,Object>quality){}

十、质量检查器

@ComponentpublicclassParsedDocumentValidator{publicvoidvalidate(ParsedDocumentdocument){if(!"SUCCEEDED".equals(document.status())){thrownewIllegalStateException("文档解析失败");}longvalidElements=document.elements().stream().filter(element->element.text()!=null&&!element.text().isBlank()).count();if(validElements==0){thrownewIllegalStateException("解析结果没有有效文本");}}}

还应检查:

  • 空白页比例;
  • 乱码率;
  • OCR置信度;
  • 表格列数;
  • 页面数量;
  • 语言;
  • 重复行。

十一、将元素转换成Spring AI Document

publicList<Document>convert(StringdocumentId,StringtenantId,ParsedDocumentparsed){returnparsed.elements().stream().filter(this::isIndexable).map(element->newDocument(element.text(),Map.of("document_id",documentId,"tenant_id",tenantId,"element_id",element.elementId(),"content_type",element.type(),"page",element.page(),"section_path",String.join(" > ",element.sectionPath())))).toList();}

不建议把:

  • 页码;
  • 空文本;
  • 装饰元素;
  • 重复页眉页脚;

直接入库。

十二、结构化分块

同一章节中的短段落可以合并:

标题 +段落1 +段落2

遇到以下元素时建立边界:

SECTION_HEADER TABLE CODE FORMULA LIST

表格单独处理,不交给普通TokenTextSplitter破坏。

普通长段落再使用Spring AI TokenTextSplitter控制上限。

十三、写入VectorStore

@ServicepublicclassKnowledgeIndexService{privatefinalVectorStorevectorStore;publicKnowledgeIndexService(VectorStorevectorStore){this.vectorStore=vectorStore;}publicvoidindex(List<Document>chunks){vectorStore.add(chunks);}}

大批量文档要:

  • 分批;
  • 控制Token;
  • 处理限流;
  • 记录失败批次;
  • 支持断点续传;
  • 使用稳定Chunk ID。

十四、幂等与版本

文档Metadata:

document_id document_version content_hash parser_version chunk_strategy_version embedding_model

同一文档重新上传时:

计算Hash → 相同则跳过 → 不同则创建新版本 → 新版本入库 → 验证成功 → 旧版本失效

不要先删除旧版本再解析新文件,否则失败时知识库为空。

十五、解析失败如何降级

Docling标准解析 → 结果质量低 → 启用OCR → 仍失败 → 切换备用解析器 → 人工审核

状态:

UPLOADED PARSING PARSED PARTIAL OCR_REQUIRED MANUAL_REVIEW INDEXED FAILED

十六、建议记录的指标

parse_duration_ms pages ocr_pages element_count table_count empty_page_ratio garbled_ratio chunk_count embedding_duration_ms index_duration_ms failed_batch_count

解析质量和检索质量要关联分析。

十七、部署注意事项

Docling解析服务通常比普通Web接口消耗更多CPU、内存和模型资源。

建议:

  • 独立容器;
  • 限制并发;
  • 使用任务队列;
  • 文件落对象存储;
  • 结果异步回调;
  • 模型预下载;
  • 临时文件定期清理;
  • 大文件设置页数上限。

总结

Docling和Spring AI的合理分工是:

Docling → 理解PDF版面、表格和文档结构 Spring AI → 管理Document、Chunk、Embedding和VectorStore

通过统一解析协议和质量门禁,可以避免解析引擎与业务代码强耦合,并为后续替换OCR、分块和向量库保留空间。