ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

RAG落地六道关:从文档解析到评测闭环的工程实践

RAG落地六道关:从文档解析到评测闭环的工程实践 这两年每次聊到 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 OCRPaddleOCR/TesseractOCR 结果要做纠错否则专有名词会被识别错复杂版面合同、年报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 默认的RecursiveCharacterTextSplitterchunk_size 设成 512overlap 设成 50完事。这个方案最大的问题把逻辑上连贯的语义从中间切断。比如“设备保修期为三年”被切到两块或者“该公司没有通过验收”的“没有”和“通过验收”被拆开检索结果直接语义反转。我见过一个金融问答系统用户问“什么情况下不赔偿”系统返回的是“赔偿”的条款因为切块把“不”字留在了上一个 context 里。这种错误特别隐蔽因为从 Embedding 角度看两块文本确实都命中了一些相关 token但真正的答案被拦腰截断。固定分块最荒谬的地方在于它假设语料的语义密度是均匀的。但文档不是巧克力条是五花肉肥瘦不均——有的段落一句话就是完整语义有的章节一整页才能讲清一个事。拿同一把刀均匀下刀切出来的必然一半是糟粕。2.2 结构感知分块的实践思路正确做法是按结构切不是按长度切。先用版面解析拿到 DOM 树或 Markdown 结构再按章节层级递归切分。我在项目里常用的规则是先按二级标题切开再看该块内部是否超过 max_tokens超过了再按段落、按句子粒度切子块继承父块的标题作为元数据。这样切出来的块天然自带上下文——检索到子块它会知道自己属于哪个章节。这个“继承上下文”机制特别重要在长文档场景里答案往往依赖前后文缺了上下文光给一句话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 关键词召回负责精确匹配型号、编号、罕见词向量语义召回负责同义改写、近义表达规则/元数据过滤负责模板命中比如按编号正则、日期区间、标签过滤。各路先分别取回 3050 条再用 RRF 或加权 Sum 融合。不用迷信花哨的融合公式RRF 的 k 值设 60 在大部分场景都很稳。关键问题反而是融合之后要去重和打标否则一个文档的不同块会挤占结果面板看起来有五条实际只有一篇文章的碎片。对于企业知识库混合检索的提升是立竿见影的。我曾经在一个维修手册项目里做对比单向量召回的 Hit Rate 大约在 60% 出头加上 BM25 召回和规则通道之后到了 82%整个调参只花了一个下午。4.3 重排模型从50条到5条的一步召回阶段要宽重排阶段要狠。先让混合检索拉回 3050 个候选再用重排模型从里面挑出最相关的 510 条这样整体效果比“向量召回直接取 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 里抽样 50100 条标注标准答案和判定标准就好。我强烈建议同时标注“金标 chunk ids”——也就是每条问题应该命中哪些文档块。没有金标你永远分不清是检索坏了还是生成坏了。比如 Hit Rate 低说明检索环节有问题Hit Rate 正常但回答不对说明生成环节需要动。建评测集的过程本身就是一次文档质量体检。我第一次做这件事时发现自己精心调好的系统在 50 条真实问题上只有 55% 的命中率当时差点把杯子摔了但这就是真实水平。6.2 命中率、忠实度、有用性三个指标就够用指标不用多三个就够起步指标含义常见雷区Hit Rate命中率金标 chunk 是否出现在召回结果中只看 Top-1 太苛刻建议看 Top-5 或 Top-10Faithfulness忠实度生成内容是否被检索内容支持自动指标会打错分需要抽检复核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 烂大街你可以心平气和地问他一句你那六个环节哪一个做到位了
返回列表