上篇我们把高级 RAG 的全景图画完了——Query 改写、多路检索、上下文压缩、自纠错。但有个扎心的事实得先说破这些全建立在喂进去的文档已经被正确解析成文本这个前提下。现实里企业文档全是带表格的 PDF、图文混排的产品手册、扫描盖章的合同。解析这关过不去垃圾进垃圾出谁也救不了。这篇就专门拆文档解析这道上游工序。一、为什么文档解析是 RAG 的隐形天花板新手做 RAG精力全花在选向量库、调 Prompt 上结果一跑答案里表格数据全错位、图片内容完全丢失、扫描件干脆空空如也。问题不在下游在源头。我把复杂文档分成三类难度递增类型典型场景核心难点粗暴解析的后果结构化文本含表格财报、产品说明书、API 文档表格跨页、单元格合并、多级标题表格被压成一段乱序文字数值对不上行图文混排技术白皮书、PPT 导出的 PDF图注与正文分离、公式、双栏排版切分把图和说明拆开语义断裂扫描件/图片盖章合同、历史档案、手写单本质是图片无文本层直接拿到空字符串检索全失效痛点很清楚文本型 PDF 丢结构扫描型 PDF 丢内容。下面一段段给可落地的 Java 方案。二、统一解析入口先把文档抽象成一个接口不管什么来源我们最终要的是一块块带结构的文本。先定义统一契约后续不同文件类型各自实现上层检索逻辑不感知差异。// 统一的文档解析接口所有文件类型都实现它 // 依赖JDK 17 public interface DocumentParser { // 解析结果保留结构块而不是一坨纯文本 ListDocChunk parse(InputStream source, ParseContext ctx) throws ParseException; boolean supports(String fileExtension); } // 结构块区别于普通字符串带类型和层级方便后续切分与重排 public record DocChunk( String id, ChunkType type, // TEXT / TABLE / IMAGE_CAPTION / FORMULA int pageNo, String content, // 表格已是 Markdown 表格串图片是 OCR/多模态描述 MapString, Object meta ) {} public enum ChunkType { TEXT, TABLE, IMAGE_CAPTION, FORMULA } public record ParseContext( boolean extractTable, // 是否抽表格 boolean ocrFallback, // 无文本层时是否走 OCR String lang // 语种影响 OCR 模型选择 ) {}这个抽象的价值检索层永远拿ListDocChunk后面换 PDFBox、换视觉模型、换 OCR 引擎都不用动上面。我见过太多项目把 PDFBox 代码直接写死在 Controller 里半年后想加扫描件支持得重写一遍。三、表格提取让财报里的数字对得上行很多 PDF 表格没有真正的表格线文字是靠坐标排成表格的。直接用PDFTextStripper抽数字会乱序。正确做法是用区域化抽取 列边界推断。tabula-java基于 PDFBox是工业级选择。// Maven 依赖 // dependency // groupIdtechnology.tabula/groupId // artifactIdtabula/artifactId // version1.0.5/version // /dependency public class PdfTableParser implements DocumentParser { private final ObjectExtractor extractor new ObjectExtractor(); Override public ListDocChunk parse(InputStream source, ParseContext ctx) throws ParseException { ListDocChunk chunks new ArrayList(); try (PDDocument doc PDDocument.load(source)) { for (int p 1; p doc.getNumberOfPages(); p) { PageArea page extractor.extract(doc, p); // 启发式探测表格区域基于空格列边界识别 ListTable tables page.getTables(); for (Table t : tables) { String md tableToMarkdown(t); // 转成 Markdown 表格 chunks.add(new DocChunk( p p -tbl t.getIndex(), ChunkType.TABLE, p, md, Map.of(rows, t.getRowCount(), cols, t.getColCount()) )); } // 表格之外的正文单独成块 String text page.getTextWithoutTables(); if (!text.isBlank()) { chunks.add(new DocChunk(p p -txt, ChunkType.TEXT, p, text, Map.of())); } } } catch (IOException e) { throw new ParseException(PDF 解析失败: e.getMessage(), e); } return chunks; } // 把 tabula 的 Table 转成 Markdown保留行列结构检索时数值不会错位 private String tableToMarkdown(Table t) { StringBuilder sb new StringBuilder(); ListListString grid t.getData(); for (int r 0; r grid.size(); r) { sb.append(| ).append(String.join( | , grid.get(r))).append( |\n); if (r 0) sb.append(|).append(---|.repeat(grid.get(r).size())).append(\n); } return sb.toString(); } Override public boolean supports(String ext) { return pdf.equalsIgnoreCase(ext); } }关键点表格转成Markdown 表格串再入库。这样 Embedding 时行和列关系保留用户问2024 年 Q3 营收多少向量能命中对应行而不是在一坨乱序文字里大海捞针。我踩过的坑曾经直接把表格当纯文本切结果营收 1200 万和成本 800 万被切到两个 chunk大模型算净利润直接瞎编。四、扫描件与图文混排OCR 与多模态的分级处理扫描件无文本层和带图文档光靠 PDFBox 拿不到内容必须上 OCR 或多模态视觉模型。这里有个成本分级的取舍别无脑上大模型那会烧穿预算。// 解析策略选择器根据文件特征选最划算的路径 // 低成本本地 Tesseract OCR表格少、清晰扫描件 // 高保真视觉大模型直接读图图文混排、公式、复杂版面 public class ParseStrategySelector { public ParsePlan select(DetectResult detect) { // 1. 有文本层且表格少 → 纯文本解析几乎零成本 if (detect.hasTextLayer() detect.tableRatio() 0.1) { return ParsePlan.TEXT_ONLY; } // 2. 无文本层扫描件但页面规整 → 本地 OCR单页几分钱 if (!detect.hasTextLayer() detect.isCleanScan()) { return ParsePlan.LOCAL_OCR; // TesseractCPU 即可 } // 3. 图文混排 / 多栏 / 含公式 → 上视觉大模型保真但贵 return ParsePlan.VISION_LLM; // 如 Qwen-VL / GPT-4V按图计费 } // 扫描件 OCR 兜底实现Tesseract 4.x // 依赖net.sourceforge.tess4j:tess4j:5.9.0 public String ocrScan(InputStream img, String lang) { Tesseract t new Tesseract(); t.setDatapath(/opt/tessdata); // 语言包路径 t.setLanguage(lang); // chi_simeng try { return t.doOCR(ImageIO.read(img)); } catch (TesseractException | IOException e) { throw new ParseException(OCR 失败, e); } } }成本取舍的经验值仅供参考按时价本地 OCR 单页几分钱、CPU 可跑视觉大模型单图几毛到一块但能直接理解图表含义、把柱状图读成Q3 营收环比下降 12%。所以我的判据是——能本地 OCR 解决的绝不调大模型只有版面复杂、含非文本信息时再上视觉模型。一个 200 页的历史合同扫描库全用视觉模型跑一遍账单能吓你一跳。五、多模态块把图翻译成可检索的描述图文混排文档里图本身没有文本。我们不直接把图片塞进向量库除非用多模态 Embedding更务实的做法是用视觉模型生成一段结构化描述作为该块的content让它参与检索。// 用视觉大模型为图片块生成可检索描述保留图注上下文 // 依赖Spring AI 1.0 ChatClient 多模态消息 public class ImageChunker { private final ChatClient visionClient; // 注入支持视觉的模型 public DocChunk describeImage(BufferedImage img, String nearbyCaption) { String b64 encodeBase64(img); String prompt 请描述这张图的关键信息若含图表请转述核心数据趋势 若含流程图请说明步骤。图注上下文%s。 输出不超过 120 字不要编造图中没有的数字。 .formatted(nearbyCaption); String desc visionClient.prompt() .user(u - u.text(prompt) .media(MimeType.valueOf(image/png), new ByteArrayResource(Base64.getDecoder().decode(b64)))) .call().content(); return new DocChunk( img- UUID.randomUUID(), ChunkType.IMAGE_CAPTION, -1, desc, Map.of(source, vision-llm) ); } }注意 prompt 里那句不要编造图中没有的数字——这是真实教训。视觉模型幻觉起来比文本模型还离谱让它转述数据必须加约束并且和 Day 98 讲的自纠错链路打通检索到图片描述块后生成答案时让它引用图中原话再让RetrievalGrader校验。六、整体流水线解析 → 结构块 → 切分 → 入库把上面几块串起来一条干净的解析流水线长这样原始文档 (PDF/扫描件/图片) │ ▼ [探测器] 有没有文本层表格占比是否扫描件 │ ├─有文本层──────► [PdfTableParser] 抽文本表格 → Markdown 块 │ ├─扫描件────────► [ParseStrategySelector] │ ├─清晰规整 → 本地 OCR便宜 │ └─复杂版面 → 视觉大模型贵但准 │ └─图文混排──────► [ImageChunker] 图→视觉描述块 │ ▼ [ListDocChunk] 结构块TEXT/TABLE/IMAGE_CAPTION │ ▼ [语义切分] 表格整块不切断正文按 512 token 滑窗 │ ▼ [Embedding 入库] → 进入 Day98 的高级 RAG 检索链路这条线的核心纪律表格整块不切断图片必须转成文字描述再入库。守这两条下游检索质量的提升比换一个更贵的向量库明显得多。七、实战建议先搞清楚你的文档长什么样再选技术。别一上来就接视觉大模型。我建议先用探测脚本统计一批真实文档纯文本占比多少、含表格多少页、扫描件多少。80% 的复杂文档其实只是普通 PDF 带几张表PDFBox tabula 就够了根本用不着 OCR。OCR/视觉模型的输出一定要留原图可追溯。把图片块 → 原图路径的映射存下来。哪天用户质疑答案你能一键调出原图人工复核而不是对着一段 AI 生成的描述干瞪眼。这也是 Day 96 讲的可靠性工程在解析侧的延伸。解析质量必须进回归集。把 Day 95 的离线回归集用起来挑 20 份典型文档人工标注这段表格应该抽到什么每次改解析逻辑就跑一遍看表格抽取准确率有没有掉。解析器是最容易改一处崩一片的模块没有量化守住迟早翻车。八、下篇预告RAG 的精度上限往往在文档被塞进向量库之前的那个 parse 函数里就定了——下游再花哨也救不回一个把好表格拆成乱码的解析器。下篇预告Day 100我们聊 RAG 的记忆与状态——多轮对话里的会话记忆、长期用户画像怎么存、如何让同一个知识库对不同用户给出不同口径的回答。这关系到 RAG 从能答到懂你的最后一跃。
企业数字化 ERP 产品动态
相关推荐
串口波特率误差的完整计算与工程验证方法 一、误差从哪里来,先把公式钉死
UART 异步通信没有时钟线,收发双方各自靠本地时钟分频出采样节拍,约定在相同的时刻对数据位做判决。既然没有共享时钟,双方波特率的任何偏差都会累积成采样点的相位漂移,误差的定义因此… · 2026/11/5 7:54:51
Docker Container -- 容器命令详解 目录
1. docker run
2. docker ps
3. docker create
4. docker logs
5. docker attach
6. docker exec
7. docker start
8. docker stop
9. docker restart
10. docker kill
11. docker top
12.docker stats
13. docker container inspect
15. docker port
15. … · 2026/11/5 7:54:51
上线之后怎么验收:延迟限流与异步索引 上篇博客讲了怎么把个人项目挂到公网:浏览器能登录、能上传、能问答,算是把主链路跑通了。 这篇主要是讲上线后的工程质量验收:护栏会不会拒、会不会误伤;异步索引是不是真在跑;慢的时候大概慢在哪。例子还是用我的开源… · 2026/11/5 7:54:51
OpenClaw智能体集群:工业级部署与优化实践 1. 项目概述:智能体集群的工业级实践 在自动化运维和分布式计算领域,智能体集群正成为企业基础设施的新标配。OpenClaw作为轻量级智能体管理框架,其核心价值在于将复杂的集群管理简化为可编程的标准化操作。我在金融行业的基础架构升级项目中… · 2026/11/5 8:31:12
CH340/CH341驱动带签名原理与Windows内核签名验证实战 1. 项目概述:为什么一个USB转串口驱动需要“带签名”这个标签?CH340/CH341驱动(带签名)PC版——这短短十几个字,背后藏着Windows系统十年来最顽固的底层安全机制与硬件开发者最日常的交付痛点。我第一次在某高校嵌入式… · 2026/11/5 8:31:12
Mermaid流程图转Word工具对比与实战指南 1. 带Mermaid流程图的AI内容转Word痛点解析 作为一名经常需要将AI生成内容整理成正式文档的技术写作者,我深刻理解Mermaid流程图在Word转换过程中的痛点。AI生成的文档往往包含大量Mermaid代码块,这些代码块在Markdown环境下可以完美渲染,但一… · 2026/11/5 8:31:12
小米/红米手机官方刷机全攻略:从解锁到线刷的完整指南 手机用久了卡顿、系统更新后耗电变快、想降回某个用着顺手的旧版本,这类需求在玩机圈里一直不少见。 给小米/红米手机刷机 这件事,很多人一听就觉得门槛高、风险大,其实只要全程使用官方工具和官方刷机包,把该注意的细节做到位&… · 2026/11/5 8:31:12
网络安全自学路线:从基础到进阶的完整指南 1. 网络安全自学路径的常见误区我见过太多初学者一上来就扎进渗透测试工具的学习,结果连基础的网络协议都说不清楚。这种本末倒置的学习方式,往往导致后续知识体系出现严重断层。就像盖楼不打地基,表面看起来进展很快,实则随时可能… · 2026/11/5 8:31:12
MC9S12DG128 AD采集实验:寄存器级原理与实时显示实现 简介:本资源是面向嵌入式开发初学者与高校电类专业学生的MC9S12DG128单片机AD采集实战项目,聚焦模拟信号采集与实时可视化这一核心能力训练。实验基于飞思卡尔S12系列经典芯片,完整实现多通道8位AD转换配置、中断触发数据读取,并通… · 2026/11/5 8:30:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38