一张被切成两半的表格不再是表格,一个失去标题的图表不再有说服力。你以为在优化RAG,实际上只是在制造更高级的碎片垃圾。
想象一个场景:
你上传了一份技术白皮书给你的AI知识库系统。用户提问:“第三页的表格里,Q3的毛利率是多少?”
系统开始检索——它找到了“Q3”,找到了“毛利率”,甚至还匹配到了几个数字。它自信满满地返回了一个答案。
答案是错的。 因为那个表格被字符分割器拦腰斩断了。Q3在上半截,毛利率的数据在下半截。你的向量数据库里存着的,是两个毫无意义的碎片。
这就是朴素分块(Naive Chunking)对RAG系统造成的灾难。它不仅仅是技术的瑕疵——它从根本上破坏了你文档的意义结构。
今天这篇文章,我们深入拆解为什么传统分块方式会让PDF检索彻底翻车,以及一套全新的四阶段结构感知管线是如何解决这个问题的。
一、朴素分块:你以为是技术问题,其实是认知问题
技术性PDF之所以让RAG系统崩溃,不是因为它们太长。而是因为绝大多数分块系统会撕裂赋予文档意义的结构。
标准提取器按照页面长度或字符数来切割文档,然后从左到右、从上到下地把它们读成纯文本——哪怕这份文档的设计初衷根本就不是这样读的。
几张图让你秒懂灾难现场:
- 表格分裂
:一个跨越多个分块的表格,列与行失去对应关系,数据变成随机排列的数字森林。 - 公式解体
:数学公式被从布局中剥离,变成一堆乱七八糟的符号,LaTeX语法被拦腰斩断。 - 图表失联
:一个没有标题的图表不再是证据——它只是一张没有上下文的图片。
最终的结果是:构建在这些碎片之上的检索系统,返回的不是完整信息,而是——
半行标题 错位的列数据 孤立的公式片段 被抽离语境的段落
用户拿着这些碎片去生成答案,然后来质问你:“你这个AI怎么胡说八道?”
真正的问题不在于你选择的分块大小是500还是1000个字符。而在于你是否在切割之前,先读懂了文档的空间结构。
二、四阶段结构感知管线:从PDF到答案的完整旅程
要解决刚才那个灾难,我们需要一个能够从源头保留空间坐标、阅读顺序和多模态关系的管线。整个工作流被切分为四个明确的阶段:解析(Parse)、富化(Enrich)、入库(Ingest)、检索(Retrieve)。
阶段一:解析——先画地图,再出门
在提取任何文字之前,这个阶段先给文档做一次“空间测绘”。
它使用PP-DocLayout-V3模型对页面进行布局检测。这个模型会给表格、图片和段落分配带有坐标的边界框(bounding boxes)。一旦这些空间区域被圈定,OCR模型GLM-OCR就会严格按照框内坐标提取文字。
这一步的价值是什么?
侧边栏的文字不会被混入正文段落 多栏排版的阅读顺序被显式保留 图片和其说明文字的物理绑定关系被记录
输出的是一个结构化JSON文件——里面有原始文字,以及每个元素精确的边界框坐标。
这就好比你在进入一座陌生城市之前,先拿到了一张精确的街区地图。没有地图,你所有的移动都是瞎撞。
阶段二:富化——让“不可检索”变得可检索
文本是可检索的,但图片、表格和公式本身不是。
这个阶段从结构化JSON中提取出被圈定的图片、表格和公式,把它们传给一个视觉语言模型(qwen2.5vl:7b,通过Ollama本地运行)。这个模型的工作很简单:给每一个视觉元素写一段文字描述。
比如一张架构图,它的视觉标题从未出现在原文中。但模型可以生成:“这是一个包含编码器、解码器和注意力机制的系统架构图。”
于是,一张图片就从“一个无法被文本查询命中的黑洞”变成了“一段可以用关键词检索的描述文本”。
输出是一个富化版JSON——包含原始结构坐标、原始文本,以及新生成的图片描述。
阶段三:入库——把结构变成向量
有了富化数据,接下来要把它存储起来以便检索。
qwen3-embedding:4b模型将文本和图片描述转化为向量嵌入(用于相似度搜索的数值数组)。这些数值向量,连同原始文本块和边界框坐标,一起存入Qdrant——一个为向量检索优化的本地数据库。
关键在于:存入数据库的不仅仅是文本,还有结构信息和模态标签。这为后续的精准检索埋下了伏笔。
阶段四:检索——多策略联合召回
当用户提交查询时,系统不再是简单地做一次向量匹配。
它执行一套四步组合拳:
- 初始向量搜索
:快速召回前20个候选块 - 模态增强
:检测查询中的视觉关键词(如“图”、“流程图”、“架构”等),对图片类块给予35%的分数加成 - 交叉编码器重排序
:对文本和表格查询,使用交叉编码器精细重排前20个候选,保留排名最高的前4个 - 返回排序上下文
:将最终胜出的文本、图片和表格块作为上下文,喂给答案生成阶段
这套多策略机制的精妙之处,我们接下来逐层拆解。
三、检索的魔法时刻:关键词增强与重排序
让我们聚焦阶段四的两个核心武器。
模态增强:当用户问“图在哪里”时
这是一个高频但常被忽略的痛点场景:
用户提问:“这个架构图展示了什么?”
标准向量搜索会去匹配语义相似的文本。但问题是——模型在富化阶段给这张图写的描述可能是:“一个包含相互连接节点的系统架构。”
语义上是相关的,但存在词汇鸿沟:用户明确提到了“图”这个视觉格式,而描述文本里只体现了内容。
结果是:标准搜索往往把这张图排到第7、第8位,而用户大概率只看到前3个结果。这张图被埋没了。
解决方案是模态增强:当系统在查询中检测到“图”、“流程图”、“架构图”等视觉关键词时,自动对所有图像类块施加1.35倍的分数加成。
在测试中,仅这一个调整,就把目标图片块从排名第7直接拉升到排名第1。胜率从“用户根本看不到”变成了“一击即中”。
交叉编码器重排序:初筛靠速度,精选靠脑力
初始向量搜索追求的是“快”——在海量数据中快速圈定一个大的候选池。
但它有个弱点:对语义相似度的判断颗粒度不够细。排名第1和第20之间,可能存在用户真正需要的东西被挤到了后面。
这就是交叉编码器出场的时候。它不追求速度,它追求精准。
系统将向量搜索返回的前20个候选块,连同用户的原始查询,一起送入交叉编码器模型(ms-marco-MiniLM-L-12-v2)。这个模型直接比较查询和每个候选文本之间的相关性,给出更精细的重排序分数。
在针对“模型复杂度”和“训练成本”等查询的测试中,这个机制成功地将原始的表格块推到了排名第1的位置,击败了那些语义上看似相关但实际信息密度不足的文本段落。
有个关键设计:这个交叉编码器步骤对图片查询是主动绕过的。
原因很务实:交叉编码器主要被训练来评估密集文本段落。在测试中,对视觉查询使用交叉编码器不仅没有提升,反而降低了排序质量。对于抓取视觉内容,模态增强本身就已经能达到足够的精度。
这是一个懂得取舍的架构——不一刀切,而是根据查询类型动态选择策略。
四、为什么要放在本地跑?
很多多模态应用默认使用托管API。但这套管线是完全在本地运行的。
这个选择与隐私考量同等重要:
- 数据不出门
:敏感文档、嵌入向量和查询都留在团队控制的基础设施内,而不流经第三方服务 - 全程可追踪
:开发者可以打开解析和富化阶段产出的中间JSON文件,清楚地看到文档是如何被布局、分割和描述的 - 调试友好
:哪一步出了问题,一眼就能定位,而不是面对一个API黑箱只能猜测
这对于处理合同、财报、技术专利等敏感文档的场景来说,不是锦上添花,而是底线要求。
五、代码结构:模块化的哲学
管线的执行通过一个中央入口点run_all.py来编排。解析和分块的逻辑被拆分成阶段独立的模块,编排器按顺序调用每个阶段。
核心逻辑清晰暴露在Python代码中。检索阶段的模态增强判断就是三行:
is_vis = bool(set(query.lower().split()) & visual_kw)if is_vis:for hit in results.points:if hit.payload.get('modality') == 'image': hit.score *= 1.35所有共享设置集中在src/config.yaml中。如果你想换一个嵌入模型,或者指向新数据库目录,只需要改一个文件的一个字段即可。模块之间边界清晰,改检索不会弄崩解析,换视觉模型不会影响入库。
这种设计哲学背后是一种务实主义:让管线像搭积木一样,可以拆、可以换、可以单独调试。
结构,是一切意义的容器
朴素PDF提取的危害远不止丢失格式。它摧毁的是让表格、图表、公式和段落具有意义的关系网。
结构感知管线在被摧毁之前就恢复了这些关系。当它再配合视觉描述、模态增强和交叉编码器重排序时,它能为答案生成提供更准确的上下文——不仅是“相关”的上下文,更是“结构完整、模态正确、位置准确”的上下文。
你不需要更聪明的模型,你需要更聪明的预处理。
因为垃圾进,垃圾出。碎片进,幻觉出。而结构进,意义才能出。
夜雨聆风