在 RAG 之前清理文档

为检索增强生成(RAG)清理文档,意味着在剩余文本被切分为块、嵌入并存储到向量索引之前,移除一切不属于内容的东西——PDF 在每一页上携带的页眉、页脚、页码和重复样板。这是 RAG 流水线中最不起眼的一步,却悄悄决定了检索是否有效:用未清理的文本构建的索引会把同样的版面杂讯存储数百次,并把它当作知识检索出来。本文解释为什么版面杂讯会毒害嵌入、分块实际上如何工作、为什么去重属于嵌入之前、什么该剥掉什么该保留,以及如何评估清理是否真的有帮助。要实际动手清理,请使用文档清理器,并对照你自己的结果阅读本指南。

为什么页面杂讯会毒害嵌入

嵌入模型把一块文本变成一个应当捕捉这块文本主题的向量。喂给它一个一半是报告标题、保密页脚和页码的块,这个向量也会忠实地捕捉那些东西——这意味着长文档中的每一个块都会向同样的杂讯漂移,本应在向量空间中相距很远的块,因为共享一个页眉而人为地靠得很近。于是检索恰恰做了它被要求做的事:问退款政策那一节,它返回五个块,它们最强的共同信号是页眉中的公司名称。没有人犯建模错误。索引以完整强度塞满了在每一页上重复的噪声,而重复的噪声对一个计算相似度的系统来说,与信号无法区分。

损害比稀释更严重。杂讯块可以干脆赢得检索:一个措辞像页脚的问题——关于保密、文档控制或版本——与被嵌入了两百次的页脚块的匹配,会胜过真正回答它的那一个段落。页码造成一种更微妙的失败,它教会模型裸数字就是内容,于是一个以“42”结尾的块可能被当作有意义的数字引用回来。而且每个杂讯块都占据有限的 top-k 结果集中的一个位置,把真正相关的块挤出模型被给到的上下文。因此清理不是美化性的整理;它是在你自己的索引内部,移除你自己内容的一个系统性竞争者。

分块基础:大小、重叠与边界

分块是关于什么单位被嵌入的决定,它有三个旋钮。大小在聚焦与上下文之间权衡:小块,几百个字符,锐利地嵌入一个想法、精确地检索,但可能缺少赋予这个想法条件的句子;大块,几千个字符,带着上下文,却把几个想法混成一个平均向量,于是没有一个能强力检索。大多数文档语料落在中间某处——大约 500 到 1,000 个字符是一个合理的起始区间——而正确答案由用你自己的问题测试你自己的文档决定,而不是抄一个框架默认值。LangChain 文本切分文档很好地梳理了切分器家族,从字符和递归切分器到结构感知切分器。

重叠是针对第二个旋钮边界的保险。一个不管结构、每 800 字符就切一刀的切分器,迟早会把一个定义切成两半,而两半都既不好检索也不好读。按边界切分——先段落、再句子、再空格,正如递归切分器所做的——大多数时候能保持单位完整,而百分之十到二十的适度重叠会把一个块的边缘重复到下一个块的开头,这样即使切分还是发生了,也不会让上下文成为孤儿。LlamaIndex 节点解析器文档中的层级从加载器一侧说明了同一点:先解析成尊重文档的节点,再切分。但这一切都救不了杂讯:重叠会忠实地把页眉也重复进下一个块,这就是为什么清理在分块之前、永远不在之后。

在嵌入之前去重

重复是第二种系统性污染物,它有两种。第一种是整篇文档的重复:同一 PDF 以不同文件名被索引两次,旧版本留在新版本旁边,邮件附件保存在共享盘副本旁边。第二种在文档内部:标题页上的免责声明又出现在附录中,摘要在每一章开头重复,表格既粘进正文又粘进附件。两种对检索的影响相同——重复文本被反复嵌入,于是被反复检索,用一个段落的副本挤满 top-k,而其他段落永远浮不上来——对成本的影响也相同,因为你要为每一份副本的嵌入和存储付费。

去重在嵌入之前最便宜,那时它还只是一次字符串比较。完全重复可以靠规范化解决:折叠空白,忽略你已经剥掉的页面杂讯,对结果做哈希并只保留一份。近似重复——同一段落只改了日期或重编了标题——需要一次相似度检查,但即使粗糙的检查也能抓住危害最大的样板。有两条提醒让去重保持诚实。第一,保留第一次出现,并记录副本在哪里;一个在两章中重复的警告可能是刻意的安全装置,把它从记录中彻底移除,与把它从索引中移除是不同的决定。第二,在清理之后去重,而不是之前:两页如果只差页码,那就是被不同杂讯包裹的内容重复,只有清理后的文本才能揭示这一点。

什么该剥掉,什么该保留

剥掉那些重复却不提供信息的东西:页眉页脚、页码、水印、保密横幅、生成的“打印于”行,以及网页转 PDF 导出在每一页上盖的导航屑。保留那些提供结构的东西:真正的标题,因为它们是你拥有的最强的分块边界信号,并且常常应进入块的元数据;图注,因为图题往往是唯一说明图里有什么的文本;表格内容,但要小心处理,因为被剥过的表格会失去让其数字有意义的行列标题;还有列表,其条目是天然的单位。临界案例是目录。作为块,目录行是杂讯——一串与每个查询都隐约匹配、却回答不了任何一个的标题和页码——但作为元数据,同一页目录是文档的地图。把它从被嵌入的文本中剥掉,把它留在你关于语料的笔记中。

同样的判断也适用于文档清理器本身:杂讯和页码默认被勾选,因为它们几乎总是可以安全移除,而重复段落从不默认勾选,因为重复有时正是目的所在。提取的诚实在这里也重要。PDF 是一种结构化格式——ISO PDF 标准 ISO 32000 把页面定义为绘图和文本操作的内容流,而不是流动的文本——因此任何提取器都在近似阅读顺序,而扫描版 PDF 在 OCR 给它文本层之前根本没有文本层。如果你的源文档出于其他原因也重要,配套文章你的 PDF 是网站无障碍的一部分介绍了同时帮助屏幕阅读器和提取器的标签结构。

清理后评估检索效果

清理是一个假设——检索会变好——而关于搜索质量的假设可以用少得令人尴尬的机械来检验。收集二十到五十个你的用户真正会问的问题,并手工注明每个问题应当由哪段文字回答。用同一个检索器、同样的块设置和同样的 top-k,把它们跑在未清理的索引和清理后的索引上,比较两件事:正确的段落是否出现在结果中,以及它出现时排名有多高。这一对——召回与排名——就是记分牌。如果清理在起作用,杂讯块会停止出现在结果中,正确的段落会上升,而那些过去因为页脚压过答案而失败的问题会开始通过。

然后阅读失败案例,因为数字本身会奉承你。一个清理后的索引可能召回变好,却丢掉一个问题——它的答案确实住在你去重去掉的一个重复摘要里,或者住在一个为每个块带来所需产品名的页眉里。这些损失是信息:它们告诉你把那个元素保留为元数据,或者有意地把文档标题前置到每个块,而不是意外地把它嵌入两百次。每当语料、切分器或嵌入模型改变时,重新运行评估,并把问题集与索引一起版本化。RAG 质量不是发布时的一次性属性;它是一种维护属性,一个小的固定测试集正是把“感觉变好了”变成可以辩护的东西的关键。

来源与延伸阅读

常见问题

清理到底能把检索改善多少?

这取决于你的文档有多重的杂讯,而这是可测量的:在清理前数一数跨页重复的行,并用一组固定的真实问题在索引的前后各测一次。带有强页眉的长报告收益最大,因为杂讯可以占据每个块的十分之一或更多;短而干净的文档可能收益甚微。诚实的方法是最后一节中的前后对比评估,而不是承诺某个百分比。

我应该在分块之前还是之后清理?

永远在之前。对未清理的文档分块,会把杂讯嵌入每一个块,把它重叠进下一个块,并把页码散布在整个索引中,之后的任何步骤都无法可靠地移除它们。先清理提取出的文本,再对结果分块——文档清理器正是按这个顺序产出清理后的文本和现成的 JSONL 块的。

我应该从什么分块大小和重叠开始?

从大约 800 个字符、按段落边界切分、带适度重叠开始,然后针对你自己的问题集调优。较小的块检索更精确但上下文更少;较大的块恰好相反。像来源中链接的 LangChain 文本切分文档这样的框架指南描述了切分器家族,但适合你语料的数字来自评估,而不是来自默认值。