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

📅 2026/7/22 18:12:05
用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内部对象。三、安装Doclingpython-mvenv .venvsource.venv/bin/activate pipinstalldocling fastapi uvicorn python-multipart pandas首次运行可能下载版面、表格或OCR模型应在部署前预热不要让生产第一个请求临时下载模型。四、基础PDF转换frompathlibimportPathfromdocling.document_converterimportDocumentConverter converterDocumentConverter()resultconverter.convert(Path(产品手册.pdf))documentresult.document markdowndocument.export_to_markdown()Path(output.md).write_text(markdown,encodingutf-8)Docling输出的不只是Markdown还可以访问结构化文档元素。五、导出表格frompathlibimportPathdefexport_tables(document,output_dir:Path)-list[dict]:output_dir.mkdir(parentsTrue,exist_okTrue)tables[]forindex,tableinenumerate(document.tables):dataframetable.export_to_dataframe()table_idfT-{index1:04d}csv_pathoutput_dir/f{table_id}.csvhtml_pathoutput_dir/f{table_id}.htmldataframe.to_csv(csv_path,indexFalse)dataframe.to_html(html_path,indexFalse)tables.append({table_id:table_id,headers:list(dataframe.columns),rows:dataframe.fillna().to_dict(orientrecords),markdown:dataframe.to_markdown(indexFalse)})returntables表格应同时保存MarkdownCSV行列JSON页面位置标题和单位。六、构建FastAPI解析服务frompathlibimportPathfromtempfileimportNamedTemporaryFilefromfastapiimportFastAPI,UploadFilefromdocling.document_converterimportDocumentConverter appFastAPI()converterDocumentConverter()app.post(/api/parse)asyncdefparse(file:UploadFile):suffixPath(file.filenameorupload.pdf).suffixwithNamedTemporaryFile(suffixsuffix,deleteFalse)astemp:contentawaitfile.read()temp.write(content)temp_pathPath(temp.name)try:resultconverter.convert(temp_path)documentresult.documentreturn{filename:file.filename,status:SUCCEEDED,markdown:document.export_to_markdown(),tables:export_tables_to_json(document)}finally:temp_path.unlink(missing_okTrue)生产环境还要限制文件大小 页数 格式 超时 并发 临时目录 恶意文件七、不要只返回一个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.webClientbuilder.baseUrl(baseUrl).build();}OverridepublicParsedDocumentparse(Resourceresource,Stringfilename){MultipartBodyBuilderbodynewMultipartBodyBuilder();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,ListStringsectionPath,MapString,Objectmetadata){}publicrecordParsedDocument(Stringfilename,Stringstatus,intpages,ListParsedElementelements,MapString,Objectquality){}十、质量检查器ComponentpublicclassParsedDocumentValidator{publicvoidvalidate(ParsedDocumentdocument){if(!SUCCEEDED.equals(document.status())){thrownewIllegalStateException(文档解析失败);}longvalidElementsdocument.elements().stream().filter(element-element.text()!null!element.text().isBlank()).count();if(validElements0){thrownewIllegalStateException(解析结果没有有效文本);}}}还应检查空白页比例乱码率OCR置信度表格列数页面数量语言重复行。十一、将元素转换成Spring AI DocumentpublicListDocumentconvert(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控制上限。十三、写入VectorStoreServicepublicclassKnowledgeIndexService{privatefinalVectorStorevectorStore;publicKnowledgeIndexService(VectorStorevectorStore){this.vectorStorevectorStore;}publicvoidindex(ListDocumentchunks){vectorStore.add(chunks);}}大批量文档要分批控制Token处理限流记录失败批次支持断点续传使用稳定Chunk ID。十四、幂等与版本文档Metadatadocument_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、分块和向量库保留空间。