☰
RAG答非所问?文档预处理才是关键,换模型解决不了
2026/11/2 8:58:48 网站建设 项目流程

1. 当RAG答非所问时,我踩过的三次弯路

RAG答错问题,绝大多数人的第一反应是换模型。从7B换到14B,从14B换到72B,从开源换到闭源,账单翻了几倍,效果可能只提升了几个百分点。我最早做RAG知识库的时候也这样,觉得模型不够聪明,后来才发现,问题根本不在模型身上——文档在进入系统之前就已经烂了。

这个判断听起来有点反直觉,但如果你拆开一个RAG系统的完整链路就会发现:文档解析、分块、向量化、检索、重排、生成,模型只负责最后一步。前面每一步都在决定模型能看到什么。如果文档解析阶段把表格拆成了乱码,如果分块把一段完整的操作步骤拦腰截断,如果向量化时把关键术语丢掉了,那模型再强也只能基于残缺的信息去猜。它猜错了,你怪它笨,其实是你没把饭喂好。

这篇文章适合两类人看:一类是正在搭RAG知识库、发现效果不达预期但不知道从哪下手排查的开发者;另一类是已经换过一轮模型、发现提升有限、开始怀疑是不是别的地方出了问题的人。我会把文档进入系统之前的每一个环节拆开讲,包括解析、清洗、分块、元数据标注、向量化前的预处理,每个环节都会说清楚为什么这样做、不这样做会出什么问题、以及我实际踩过的坑。

先给一个核心结论:RAG系统的上限在文档进入向量库之前就已经被决定了。模型决定的是你能不能逼近这个上限,而不是能不能突破它。所以当你发现RAG答错的时候,排查顺序应该是从后往前——先看检索到的内容对不对,再看分块合不合理,再看解析有没有丢信息,最后才考虑换模型。

2. 文档解析:PDF、Word、Excel各有各的坑

2.1 PDF解析的三种路线和各自的适用边界

PDF是RAG知识库最常见的文档格式,也是最容易出问题的格式。很多人直接用PyPDF2或者pdfplumber一把梭,结果发现解析出来的文本顺序全乱,表格变成了一堆数字堆在一起,双栏排版的论文读起来像天书。

PDF解析本质上是在做逆向工程。PDF格式的设计目标是精确渲染,不是语义表达。它记录的是“在坐标(x,y)画这个字符”,而不是“这是一个段落”。所以解析器要做的事情是从坐标信息里反推出阅读顺序和语义结构。

目前主流的PDF解析路线有三条:

第一条是基于规则的传统解析器,代表工具是pdfplumber、PyMuPDF。它们通过分析字符的坐标、字体大小、行间距来推断段落和标题。优点是速度快、不依赖外部服务,缺点是遇到复杂排版(多栏、表格嵌套、图文混排)就容易翻车。我实测下来,pdfplumber对单栏纯文本PDF的解析准确率能到95%以上,但遇到双栏学术论文,段落顺序错乱的概率超过30%。

第二条是基于深度学习的版面分析模型,比如LayoutLM、Detectron2配合OCR。这类方案先对每一页做版面检测,识别出标题、正文、表格、图片区域,再按区域分别解析。优点是准确率高,尤其是对复杂版面。缺点是需要GPU、推理速度慢、部署成本高。如果你要处理的是几百页的技术手册,用这条路线比较合适。

第三条是调用外部文档解析服务,比如一些云厂商提供的文档智能解析API。优点是省事,缺点是数据要出本地,而且按量计费,文档量大的时候成本不低。

我的建议是分场景选:如果文档以单栏纯文本为主,pdfplumber够用;如果有大量表格和复杂版面,上版面分析模型;如果文档量不大且对精度要求极高,可以考虑外部服务。

2.2 Word和Excel文档的结构化信息丢失问题

Word文档看起来比PDF好解析,因为docx本质上是一个zip包,里面是XML。python-docx可以直接读取段落、表格、样式信息。但这里有一个容易被忽略的问题:很多人只提取了文本,丢掉了样式信息。

样式信息在RAG里是有用的。比如标题样式(Heading 1、Heading 2)可以帮助你判断文档的层级结构,加粗的文本可能是关键术语,表格里的内容需要单独处理。如果你只提取纯文本,这些信息就全丢了。

Excel的问题更严重。Excel文档天然是结构化的,但很多人把它当成普通文本处理,一行一行读出来拼成字符串。这样做的问题是:表格的行列关系丢了,表头和数据混在一起,数值和单位分离。比如一个产品参数表,列名是“型号”“功率”“电压”,如果你直接拼成文本,模型看到的是“A型 100W 220V B型 200W 380V”,它根本不知道哪个数字对应哪个参数。

正确的做法是把Excel的每一行转换成一个结构化的描述。比如:

# 假设用openpyxl读取Excel import openpyxl wb = openpyxl.load_workbook("product_specs.xlsx") ws = wb.active headers = [cell.value for cell in ws[1]] rows = [] for row in ws.iter_rows(min_row=2, values_only=True): row_dict = dict(zip(headers, row)) # 转换成自然语言描述 desc = ",".join([f"{k}是{v}" for k, v in row_dict.items() if v is not None]) rows.append(desc)

这样每一行变成一个独立的、语义完整的描述,向量化之后检索效果会好很多。

2.3 扫描件和图片型文档的OCR预处理

扫描件和图片型PDF需要先做OCR。OCR的质量直接决定了后续所有环节的效果。我踩过的坑是:用了一个轻量级OCR模型,识别率大概85%,看起来还行,但错的那15%恰好是关键术语和数字。比如“额定电压220V”被识别成“额定电压22OV”,向量化之后和“电压”相关的查询就匹配不上了。

OCR环节有几个实操要点:

  • 分辨率要够:扫描件建议至少300DPI,低于200DPI识别率会明显下降。
  • 预处理不能省:灰度化、二值化、去噪、纠偏,这些步骤看起来简单,但对识别率的影响很大。
  • 术语表辅助:如果文档涉及专业术语,可以在OCR后加一层基于术语表的纠错。比如维护一个“电压、功率、阻抗”的术语列表,对OCR结果做模糊匹配纠正。
  • 表格单独处理:OCR对表格的识别效果通常很差,建议先用版面分析把表格区域框出来,单独用表格识别模型处理。

3. 分块策略:为什么你的检索总是差一口气

3.1 固定长度分块的致命缺陷

很多人做RAG分块就是一句话:按500个字符切,重叠50个字符。这个策略简单粗暴,但在实际项目里几乎是灾难性的。

问题出在“字符”这个单位上。500个字符可能是一段完整的操作步骤,也可能是半句话加上半张表格。更关键的是,固定长度分块完全无视文档的语义边界。一个完整的操作流程被切成两半,前半段在块A,后半段在块B。用户问“怎么配置XX”,检索到了块A,但块A只讲了第一步,第二步在块B里,模型看不到,回答自然不完整。

我做过一个对比测试:同一份技术文档,分别用固定长度分块和语义分块,然后用相同的20个问题做检索测试。固定长度分块的检索命中率是62%,语义分块是89%。差距非常明显。

3.2 基于文档结构的分块:标题、段落、列表的语义边界

语义分块的核心思路是:尊重文档本身的结构边界。文档是人写的,人写文档的时候已经用标题、段落、列表把语义单元划分好了。你要做的是识别这些边界,而不是用字符数去切。

具体操作上,可以按以下优先级来确定分块边界:

  1. 一级标题:最大的语义单元,通常对应一个完整的主题。
  2. 二级标题:一级标题下的子主题,适合作为独立的知识块。
  3. 段落:如果没有标题,段落是最自然的边界。
  4. 列表项:操作步骤类的列表,每一项应该完整保留,不要从中间切开。
  5. 表格:表格必须完整保留,不能拆分。

实现上,如果你用的是LangChain,可以用RecursiveCharacterTextSplitter配合自定义的分隔符列表:

from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( separators=["\n## ", "\n### ", "\n\n", "\n", "。", ";", " "], chunk_size=800, chunk_overlap=100, length_function=len, )

注意分隔符的顺序:先按二级标题切,再按三级标题切,再按段落切,最后才按句子和字符切。这样能最大程度保证语义完整性。

3.3 重叠窗口设多少才合理:一个被忽视的参数

分块重叠(overlap)这个参数很多人设得很随意,50、100、200都有。重叠的作用是防止关键信息恰好落在两个块的边界上被切断。但重叠设太大也有问题:一是存储和检索成本增加,二是检索时可能返回大量重复内容,挤占上下文窗口。

我的经验值是:重叠长度设为块大小的10%到15%。比如块大小800字符,重叠设80到120字符。这个比例能覆盖大部分边界情况,又不会造成太多冗余。

但有一个例外:如果文档里有很多跨段的逻辑关系(比如“如上所述”“接下文”),重叠可以适当加大到20%。另外,对于代码块和表格,建议单独处理,不要和正文混在一起分块。

4. 元数据与预处理:让检索少走弯路的隐藏手段

4.1 给每个块打上来源、章节、类型的标签

元数据是RAG系统里最被低估的东西。很多人分完块直接向量化入库,什么标签都不打。结果检索的时候只能靠语义相似度,没法做过滤和排序。

给每个块打上元数据标签,至少包括以下几类:

  • 来源信息:文件名、文件路径、文档版本。
  • 结构信息:所属章节、标题层级、在文档中的位置。
  • 类型信息:正文、表格、代码、列表、注意事项。
  • 时间信息:文档创建时间、最后修改时间。

这些标签在检索时非常有用。比如用户问“最新版的配置方法”,你可以按时间过滤,只检索最近版本的文档。用户问“XX参数是多少”,你可以按类型过滤,优先检索表格块。

4.2 文档清洗:去掉页眉页脚、水印、乱码

文档清洗是另一个容易被跳过的环节。PDF解析出来的文本里经常夹杂页眉、页脚、页码、水印文字。这些东西如果不清掉,会被当成正文向量化,污染检索结果。

我遇到过一个典型案例:一份技术手册每页页脚都有“版权所有 侵权必究”八个字。分块之后,几乎每个块里都带着这句话。用户问“版权声明是什么”,检索出来的全是各个章节的正文块,因为每个块里都有这八个字,语义相似度都很高。真正的版权声明页面反而被淹没了。

清洗规则可以根据文档特点定制,常见的包括:

  • 去掉重复出现的短行(页眉页脚)
  • 去掉纯数字行(页码)
  • 去掉特定关键词开头的行(水印)
  • 合并被错误换行的段落

4.3 向量化之前的文本规范化处理

文本规范化是在向量化之前做的最后一道处理。目的是让文本更“干净”,减少噪声对向量表示的干扰。具体包括:

  • 全角转半角:中文文档里经常混着全角英文字母和数字,统一转成半角。
  • 统一标点:中文标点和英文标点混用会影响分词和向量化效果。
  • 去除多余空白:连续空格、制表符、换行符统一处理。
  • 数字和单位规范化:比如“220V”和“220 V”统一成一种写法。

这些处理看起来琐碎,但对检索效果的影响是累积的。每一个小噪声可能只降低1%的相似度,但十个噪声叠加起来,就可能让一个本该被检索到的块掉出Top-K。

5. 检索环节的排查链路:从命中率反推问题源头

5.1 先看检索结果,再看生成结果

当RAG答错的时候,第一步不是看模型输出,而是看检索结果。具体操作是:把用户问题和检索到的Top-K块打印出来,人工判断这些块里有没有包含正确答案。

如果检索结果里根本没有正确答案,那问题出在检索之前——解析、分块、向量化、索引,这几个环节里有一个或多个出了问题。这时候换模型没有任何用,因为模型根本没看到正确的信息。

如果检索结果里有正确答案,但模型输出错了,那问题可能出在生成环节——提示词设计、上下文长度限制、模型本身的能力。这时候才需要考虑换模型或者优化提示词。

这个排查顺序非常重要。我见过太多人跳过第一步直接换模型,结果换了三四个模型,问题依旧。

5.2 用命中率和MRR量化检索质量

人工看检索结果只能定性判断,要定量评估需要引入指标。最常用的两个指标是命中率(Hit Rate)和MRR(Mean Reciprocal Rank)。

命中率的定义是:在测试问题集中,正确答案出现在Top-K检索结果中的比例。比如100个问题,有80个问题的正确答案在Top-5里,命中率就是80%。

MRR的定义是:正确答案在检索结果中排名的倒数的平均值。如果正确答案排第1,得分是1;排第2,得分是0.5;排第3,得分是0.33。MRR越高,说明正确答案排得越靠前。

这两个指标的计算需要先构建一个测试集:一批问题加上对应的正确答案所在的文档块。构建测试集本身有工作量,但这是评估RAG系统最可靠的方式。没有测试集,所有的优化都是盲人摸象。

5.3 常见检索失败的四种模式和对应的修复方向

根据我的经验,检索失败通常表现为四种模式:

失败模式表现根因修复方向
完全未命中Top-K里没有正确答案解析丢信息、分块切断、向量化噪声检查解析和分块环节
排名靠后正确答案在Top-20但不在Top-5相似度计算不准、缺少元数据过滤优化embedding模型、加元数据过滤
重复冗余Top-K里多个块内容重复分块重叠过大、文档本身重复调整重叠参数、去重
语义漂移检索到的块主题相关但细节不对分块粒度过大、向量表示模糊缩小分块粒度、增加元数据

这张表可以作为排查的起点。先判断你的系统属于哪种失败模式,再往对应的方向去修。

6. 那些换模型解决不了的问题:三个真实案例拆解

6.1 表格被拆散导致参数查询全错

我之前做过一个工业设备知识库,文档里有大量设备参数表。用户问“XX型号的额定功率是多少”,RAG总是答错或者答成别的型号的参数。

排查过程:打印检索结果,发现检索到的块里确实有功率数值,但型号和功率的对应关系乱了。原因是PDF解析时表格被拆成了多行文本,分块时又把不同型号的行切到了不同的块里。模型看到的是“额定功率是100W”,但不知道这是哪个型号的。

修复方案:把表格单独解析成结构化数据,每一行转换成一个独立的描述块,比如“XX型号的额定功率是100W,额定电压是220V”。这样每个块自带完整的上下文,检索时不会混淆。

修复后,参数查询的准确率从40%提升到了92%。模型没换,只是改了文档处理方式。

6.2 操作步骤被截断导致回答不完整

另一个案例是操作手册类知识库。用户问“怎么重置设备”,RAG回答的步骤只有前两步,后面三步丢了。

排查过程:检索到的块里确实只有前两步。原因是分块时按固定长度切,第三步恰好被切到了下一个块里。而用户的问题和第一个块的相似度更高,所以只检索到了第一个块。

修复方案:改用基于列表项的分块策略,把每个操作步骤列表作为一个完整的块。如果步骤太长,按步骤序号分块,但保证每个块里都包含“这是XX操作的第N步”这样的上下文信息。

修复后,操作类问题的完整回答率从55%提升到了88%。

6.3 术语不一致导致检索匹配不上

第三个案例是技术文档知识库。用户问“怎么配置超时时间”,RAG检索不到相关内容。但文档里明明有“超时设置”的章节。

排查过程:文档里用的是“超时设置”,用户问的是“超时时间”。虽然语义相近,但embedding模型给出的相似度不够高,没有进入Top-K。

修复方案:两层处理。第一层是在文档预处理时做同义词扩展,把“超时设置”和“超时时间”“timeout”关联起来。第二层是在检索时加入关键词匹配作为补充,不单纯依赖向量相似度。

修复后,这类术语不一致导致的检索失败从每周十几次降到了几乎为零。

7. 一套可复用的文档预处理检查清单

7.1 解析阶段的验收标准

解析完成后,不要直接进入分块环节。先做一轮验收:

  • 随机抽取5到10页,人工对比解析文本和原始文档,检查段落顺序是否正确。
  • 检查表格是否被正确识别,表格内容是否完整。
  • 检查特殊字符(公式、符号、上下标)是否丢失或乱码。
  • 检查页眉页脚是否被清除。
  • 统计解析后的总字符数,和原始文档做对比,偏差超过10%需要排查。

7.2 分块阶段的验收标准

分块完成后,同样需要验收:

  • 随机抽取20个块,人工判断每个块是否语义完整。
  • 检查是否有块以半句话开头或结尾。
  • 检查表格和代码块是否被完整保留。
  • 检查块的大小分布,是否存在过大或过小的异常块。
  • 检查重叠区域是否合理,是否有大量重复内容。

7.3 上线前的检索质量基线测试

在正式上线之前,构建一个至少包含50个问题的测试集,覆盖不同类型的问题(事实查询、操作步骤、参数对比、原因解释)。然后计算命中率和MRR,作为基线。

上线之后,定期用同样的测试集做回归测试。如果发现指标下降,说明文档更新或系统变更引入了问题,需要及时排查。

提示:测试集的问题应该来自真实用户提问,而不是你自己编的。真实问题的表述方式、术语使用、问法多样性,和你自己编的问题差别很大。我建议在系统上线初期,每天收集用户的实际提问,从中筛选出有代表性的加入测试集。

8. 写在最后:一些个人体会

做RAG系统这两年,我最大的体会是:模型不是瓶颈,数据才是。大家愿意花时间研究哪个embedding模型排行高、哪个LLM推理能力强,但很少有人愿意花同等精力去研究文档怎么解析、怎么分块、怎么清洗。而后者对最终效果的影响,往往比换模型大得多。

另一个体会是:排查问题要从后往前,优化系统要从前往后。排查的时候先看检索结果,确认问题出在哪个环节。优化的时候从文档解析开始,因为这是整个链路的源头,源头没处理好,后面怎么调都是事倍功半。

最后分享一个实用技巧:如果你不确定分块策略是否合理,可以做一个简单的实验。把同一份文档用三种不同的分块策略处理,然后用同一组问题做检索测试,对比命中率。这个实验花不了多少时间,但能给你非常明确的信号。我每次接手新的知识库项目,第一件事就是做这个对比实验,基本上跑完就能确定最优的分块方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询