☰
RAG落地六道关:从文档解析到评测闭环的工程实践
2026/10/3 18:56:48 网站建设 项目流程

这两年每次聊到 RAG,圈子里总有这么一句:RAG 已经烂大街了。说这话的人,大概率是照着网上教程搭过一条流水线——文档切块、向量化、存向量库、检索 Top-k、拼进 Prompt、丢给大模型。流水线就是这么条流水线,GitHub 上一抓一大把,三小时能跑通 Demo。但说句实话,烂大街的从来只是这条流水线,真正决定你做的 RAG 是玩具还是产品的,是流水线上下游这六个不起眼却致命的环节:文档接入与解析、分块策略、查询改写、混合检索与重排、上下文组装、评测闭环。这篇文章不聊 Demo,只聊我从几十套落地项目里反复踩过的这六道关。

1. 文档接入:真正决定RAG上限的第一道闸口

1.1 PDF转文本:看起来简单,实际是最大翻车主

很多团队把 RAG 开发当成“从干净的文本文件开始”。但业务里哪来干净的文本?全是 Word、PPT、PDF、扫描件、图片、网页。PDF 尤其难搞——有扫描版、有文本版、有双层、有矢量字体、有复栏排版。如果你图省事,直接用 PyPDF2 提取一段文字,出来的经常是一串乱序碎片,尤其是论文、合同、表格这些带复杂版面的文件,顺序完全不对。

这些碎片进了分块、进了向量库,后面检索命中率再高都白搭。我见过一个真实案例:某公文系统上线第一天,检索接口 Top-1 返回的是一份临时文件里的片段,查了半天才发现是 PDF 的页眉页脚和正文混在一起,被切块算法当成了正文。所以解析阶段就要把 Docling、Unstructured、Marker、MinerU 这些工具用起来,至少要做版面分析,把标题、正文、页眉页脚、表格区域分开。

我在项目里的选型参考如下:

文档类型推荐工具关键注意点
纯文本 PDFpdfplumber / PyMuPDF注意字体编码,部分中文 PDF 提取会乱码
扫描件 / 图片Unstructured + OCR(PaddleOCR/Tesseract)OCR 结果要做纠错,否则专有名词会被识别错
复杂版面(合同、年报)Marker / MinerU版面还原能力强,但需要 GPU,批量任务要控制并发
Word / PPT / HTMLpython-docx / Unstructured不要直接转纯文本,先提取标题层级和表格结构

把这一步做好,等于给整条流水线选好了原材料。烂蔬菜做不出好菜,解析阶段偷的懒,后面全都要加倍还。

1.2 版面还原与表格抽取:纯文本管线必然丢分

如果只是“能搜到”东西,上面说的可能还觉得无所谓。但 RAG 面对的是问答,比如用户问“去年四季度华北区销售额是多少”,答案就在表格里。纯文本管线会把表格抽成一行行换行分隔的文本,向量化之后语义被冲散,检索出来也未必能答对。我在实际项目里反复验证过:同一个表格,一种方式是拍平成纯文本,另一种是提取成 Markdown 表格,后者在问答评测里的命中率能高出 20% 以上。

做法其实不复杂:先做版面分析拿到表格区域,再用结构化抽取器把表格还原成 HTML 或 CSV 结构,然后单独对表格做分块。这里有个关键细节——表头必须作为独立上下文保存。因为用户问“三月份销售额”的时候,如果检索回来的块只有一行“3,200,000 元”而没有表头“月度销售额”,LLM 根本不知道这数字是什么。

所以我的习惯是:表格本身作为一个 chunk,表头单独抽出,用元数据关联起来。检索结果返回时,把表头和表格体一起带回,这样大模型才能直接读取。这属于典型的“前端烂、后端全废”的问题,别指望 LLM 能从错误文本里脑补出正确答案。

1.3 图片、扫描件与多模态:“RAG知识库能存储图片吗”这类问题背后的坑

很多人都在问 RAG 知识库能不能存图片。这个问题的答案分两层:一种是把图片当附件存进去,检索到时返回;另一种是让模型真的理解图片内容。前者只需要做文件索引,后者就得想清楚技术路线了。

我的建议是:能走 OCR 就别急着上多模态。把扫描件、图片先走 OCR 或用一个多模态 LLM 抽取成结构化文本,再进检索管线。多模态向量模型在中文场景的成熟度确实不如英文,直接拿来做上线项目,很容易在图文混排的文档上翻车。如果非得走多模态路线,一定要准备自己的图文对评测数据,别直接套用开源指标,否则线上表现和评测结果可能差一大截。

这里也顺便回应一个高频误解:RAG 知识库“存储”图片不等于“理解”图片。如果你的业务只是合同附件需要归档检索,那对图片做文件名、OCR 文本、上下文元数据的索引就够了,不必让向量模型硬吃视觉信号。多做实验,但别把多模态当默认选项。

2. 分块策略:检索精度的隐形天花板

2.1 固定长度分块为什么是最省事也是最危险的方案

我敢说,现在跑着的 RAG 服务里,至少有一半还在用固定长度分块——LangChain 默认的RecursiveCharacterTextSplitter,chunk_size 设成 512,overlap 设成 50,完事。

这个方案最大的问题:把逻辑上连贯的语义从中间切断。比如“设备保修期为三年”被切到两块,或者“该公司没有通过验收”的“没有”和“通过验收”被拆开,检索结果直接语义反转。我见过一个金融问答系统,用户问“什么情况下不赔偿”,系统返回的是“赔偿”的条款,因为切块把“不”字留在了上一个 context 里。这种错误特别隐蔽,因为从 Embedding 角度看,两块文本确实都命中了一些相关 token,但真正的答案被拦腰截断。

固定分块最荒谬的地方在于,它假设语料的语义密度是均匀的。但文档不是巧克力条,是五花肉,肥瘦不均——有的段落一句话就是完整语义,有的章节一整页才能讲清一个事。拿同一把刀均匀下刀,切出来的必然一半是糟粕。

2.2 结构感知分块的实践思路

正确做法是按结构切,不是按长度切。先用版面解析拿到 DOM 树或 Markdown 结构,再按章节层级递归切分。我在项目里常用的规则是:

  1. 先按二级标题切开;
  2. 再看该块内部是否超过 max_tokens,超过了再按段落、按句子粒度切;
  3. 子块继承父块的标题作为元数据。

这样切出来的块,天然自带上下文——检索到子块,它会知道自己属于哪个章节。这个“继承上下文”机制特别重要,在长文档场景里,答案往往依赖前后文,缺了上下文,光给一句话,LLM 无法判断对错。

如果哪天你想上 Ontology、GraphRAG 或者 LlamaIndex 那一套实体关系链路,结构感知分块是绕不开的地基。没有还原章节层级,后面做实体识别、构建知识图谱,都是在沙子上盖楼。GraphRAG 的实体关系图本质上也是从结构清晰的文档里抽取的,源头就乱,图谱必然稀碎。

2.3 块级元数据:让检索结果自带上下文

除了切块本身,每个 chunk 的元数据设计同样重要。我给每个块至少打上这些标签:文档 ID、标题层级、章节路径、页码、发布日期、模板类型。有些人觉得这是浪费存储,其实这些标签的价值在后面的检索阶段会成倍放大。

元数据至少有四个用处:第一,检索结果能直接拼接来源,用户点开就能跳到原文;第二,过滤器可以按日期、部门、版本来圈定检索范围,比如“只看 2024 年之后的合同”;第三,重排阶段可以按权威性加权,公司内部文件的优先级高于外部转载;第四,便于做邻近块的上下文恢复——给每个块记录chunk_id和前驱_chunk_id,命中某一小块时,可以顺带取回它前后几个块,组成更完整的上下文。

这些细节看似琐碎,但正是把 Demo 和产品区分开的地方。花 10 分钟设计元数据,后面能省几十个小时的排查时间。

3. 查询改写与意图路由:用户的问法不是索引的入口

3.1 用户问题为什么不能直接用来检索

用户提问的方式,和文档里的表述经常对不上。比如用户问“去年的增收率咋样”,文档里写的是“2023 年营收同比增长 12.5%”。直接拿“增收率”去检索,可能一个都命中不了。还有代词问题——“这个政策对小微企业有什么影响”,这里的“这个政策”指代前文某个已讨论过的文件,如果不做指代补全,检索系统只能傻眼。

这就是很多人忽略的:用户的问题是口语化的、简写的、有上下文的,而索引里存的是书面化的、长句式的、孤立切割的文本。两者之间不是天然对齐的,需要一层转换。不做查询改写的 RAG,相当于让一个人没头没尾地听你讲话,然后要求他立刻找出正确答案,这不现实。

3.2 查询改写与意图路由的落地套路

我现在做线上项目,一般是两层结构。

第一层是意图路由。先用一个轻量模型或者规则,判断用户问题是“事实型问答”“归纳总结”“比较分析”还是“闲聊”。不同意图走不同的管道——事实型走标准检索问答,归纳总结型走多跳检索加总结合并,闲聊型压根不进检索,直接大模型回答。

第二层是查询改写。对进入检索的问题做改写,生成多个候选查询和精确查询。用 LLM 改写的 Prompt 可以参考:

你是检索系统的查询改写器。请把用户的原始问题改写成更适合文档检索的查询语句。 要求: 1. 去除口语化表达; 2. 补全代词指代; 3. 保留数字、专有名词、型号; 4. 直接输出改写结果,不要解释。 原始问题:{user_query} 历史上下文:{chat_history} 改写结果:

一个重要的操作细节:原始问题和改写后的查询要并行去检索,再合并去重,而不要只用改写结果。原因很简单,改写后语义更完整,但也可能丢失原始表达里的某些关键词,两路召回互相补,命中率能提升一个档次。

3.3 关键词召回与问答路径的多通道策略

除了语义检索,很多场景里关键词精确匹配极其关键。比如用户问“SG-102 故障码”,向量检索可能匹配到“机器型号示例 SG-102”的模糊片段,但 BM25 对精确 token 的命中反而更强。这时候只靠 Embedding 就是舍近求远。

所以我在架构里会加一条规则通道:用正则或者分词器精确匹配编号、型号、人名。举个例子,法律合同类场景,用户问“第 12 条”,规则通道可以直接命中,不用走语义那一套。这个设计和后面的混合检索是同一条路线上的事,但查询改写这一步先做对了,后面才能发挥价值。

4. 混合检索与重排:Top-k之前才是技术含量所在

4.1 向量检索的“语义盲区”到底在哪

Embedding 擅长语义相似,比如同义改写、近义表达,但至少在三个场景下很容易失效。第一是专有名词精确匹配,型号、编号、合同号,向量空间里这些词可能被泛化。第二是否定语义,“不含”“非”“除…以外”这类逻辑表达,向量模型经常捕捉不到,因为“不赔偿”和“赔偿”在你向量的真实空间里距离可能非常近,但答案完全相反。第三是长尾低频词,中文分词一旦把专有名词切错,Embedding 就彻底跑偏。

所以只看 Top-k 的向量检索,在真实业务里是远远不够的。我自己见过太多项目,问题出在“没召回正确块”,而不是“模型不聪明”。

4.2 混合检索:BM25+向量+规则通道

成熟的方案是:多路召回加融合。至少三路:

  • BM25 关键词召回:负责精确匹配,型号、编号、罕见词;
  • 向量语义召回:负责同义改写、近义表达;
  • 规则/元数据过滤:负责模板命中,比如按编号正则、日期区间、标签过滤。

各路先分别取回 30~50 条,再用 RRF 或加权 Sum 融合。不用迷信花哨的融合公式,RRF 的 k 值设 60 在大部分场景都很稳。关键问题反而是融合之后要去重和打标,否则一个文档的不同块会挤占结果面板,看起来有五条,实际只有一篇文章的碎片。

对于企业知识库,混合检索的提升是立竿见影的。我曾经在一个维修手册项目里做对比,单向量召回的 Hit Rate 大约在 60% 出头,加上 BM25 召回和规则通道之后到了 82%,整个调参只花了一个下午。

4.3 重排模型:从50条到5条的一步

召回阶段要宽,重排阶段要狠。先让混合检索拉回 30~50 个候选,再用重排模型从里面挑出最相关的 5~10 条,这样整体效果比“向量召回直接取 Top-5”好得多。

原因是:召回模型用的是双塔结构,query 和 document 各自编码,交互信息很弱;而重排模型走的是交叉编码,query 和 document 可以充分交互,语义捕捉更细。开源方案里 bge-reranker 系列到 v2 在中文场景已经很能打,我自己用下来效果稳定。重排阶段对延迟有要求,一般单条 50ms 以内,如果候选集太大,就分批重排或先粗筛再精排。

一个小忠告:重排模型不是万能药。如果召回阶段就已经把正确答案漏掉了,重排再强也翻不出花来。所以我始终强调召回在前,重排在后,两件事分开评测。

5. 上下文组装与生成:塞进提示词之前要先做质量把关

5.1 上下文塞不下的处理:渐进式压缩与摘要

很多团队把 Top-k 内容一股脑塞进 Prompt。如果每块有 800 字,5 块就 4000 字,再算上历史对话,直接爆掉上下文窗口。窗口在实际产品里是要钱的,速度也是问题。

所以送给 LLM 之前必须做上下文压缩。我的习惯是按顺序处理:先把最相关块排前面,再对长块做句子级别评分,保留关键句,最后做内容去重。压缩的原则是“宁可少给,不能给错”。LLM 在上下文混杂的时候,很容易被不相关的段落带偏,你把噪声降到最低,生成质量自然上来。

这里顺便提一下,上下文压缩做到极致,其实就是 Agentic RAG 的入口了——让模型判断哪些上下文值得留、哪些扔掉、是否需要二次检索。但那是进阶话题,先把手动压缩做对再说。

5.2 引用溯源:不给出处的回答不能算完成

生产级 RAG 一定要给引用,这件事没有商量余地。做法很简单:把检索回来的 chunk 带上doc_id和原文片段,在 Prompt 里要求回答时在句子末尾标注引用编号。比如:

回答要求: - 每个关键结论后标注引用编号,例如 [1],[2]; - 如果检索内容不支持结论,请明确说明“根据现有资料无法确认”; - 引用编号必须对应检索内容中的具体来源。

然后在下游做一个引用解析器,把答案句子和它引用的 chunk 做语义一致性校验。这一步能直接拦截幻觉,模型编一句“正确”但上下文里根本没有的话时,校验阶段就会打回。引用另外一个价值是给用户信任感——点击引用能跳到原文,用户敢用你的系统。

5.3 生成阶段的“拒答”与幻觉控制

当检索结果和问题关联度不高时,不应该硬答。我在 Prompt 里会明确写:如果检索内容不能直接回答用户问题,就回答“根据现有资料无法确认”。

这还不够,最好再加一道自检逻辑。生成之后,用另一个轻量 LLM 对模型回答里的每个关键句做“是否被检索内容支持”的二元判断,一旦发现不支持就重写。这个自检成本可控,但幻觉率下降非常明显。有人觉得多此一举,但真实场景里用户会用刁钻问题考验你,没把握的时候主动说不知道,比一本正经胡说八道要好得多。

6. 评测闭环:没有度量就没有优化,也就没有水平线

6.1 离线评测集:比调参更重要的第一件事

很多团队不建评测集就开始调参,这是导致“看起来能答,一换问题就废”的根因。建评测集的成本没有想象中那么高:从真实 Query 里抽样 50~100 条,标注标准答案和判定标准就好。

我强烈建议同时标注“金标 chunk ids”——也就是每条问题应该命中哪些文档块。没有金标,你永远分不清是检索坏了还是生成坏了。比如 Hit Rate 低,说明检索环节有问题;Hit Rate 正常但回答不对,说明生成环节需要动。

建评测集的过程本身就是一次文档质量体检。我第一次做这件事时,发现自己精心调好的系统在 50 条真实问题上只有 55% 的命中率,当时差点把杯子摔了,但这就是真实水平。

6.2 命中率、忠实度、有用性:三个指标就够用

指标不用多,三个就够起步:

指标含义常见雷区
Hit Rate(命中率)金标 chunk 是否出现在召回结果中只看 Top-1 太苛刻,建议看 Top-5 或 Top-10
Faithfulness(忠实度)生成内容是否被检索内容支持自动指标会打错分,需要抽检复核
Usefulness(有用性)回答是否真正解决用户问题这个问题主观性强,需要人工抽样

开源工具里 Ragas、TruLens、LlamaIndex 的 eval 模块都能用,中文场景我优先推 Ragas,配置简单,文档友好。但自动指标本质上依赖另一个模型,不可能百分百准确,所以一定要保留少量人工抽检。

6.3 线上观测与A/B:烂管道也需要装上仪表盘

离线评测之外,线上观测同等重要。我建议至少记录这几个字段:原始 Query、改写后的 Query、召回 Top-k 列表、最终回答、用户反馈(点赞还是点踩)。这些数据攒下来,价值比调一个参数大得多。

我当时做过一次失败 case 复盘:系统大量失败集中在表格类 Query,回溯之后才发现是表格分块没有保留表头,导致检索回来的块丢失了语义上下文。单独修了这一个点,整个 Hit Rate 提升了 8 个百分点。这就是评测闭环的价值——没有数据,你根本不知道病根在哪里。


做了这么多套 RAG,我最大的体会是:Demo 和产品之间差的从来不是大模型,而是这六处没人愿意写进 PPT 的细节。很多人以为瓶颈在模型能力,其实大部分项目是“前端烂、中端糙、后端盲”——文档解析没收干净,检索靠 Top-5 碰运气,后面还没有评测。把这六环挨个补上,哪怕你用的是一个普通开源模型,整体效果都会明显上一个大台阶。以后要是再有人说 RAG 烂大街,你可以心平气和地问他一句:你那六个环节,哪一个做到位了?

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

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

立即咨询