
RAG烂大街了这句话我听了快两年。两年前大家还在讨论“检索增强生成”是不是AGI的标配半年后几乎每个技术社区都在教人搭同一条流水线读PDF、切块、向量化、存库、向量召回、拼提示词、交给大模型。看起来很快真上生产却四处漏风。用户问“这个附件第3页的表格里金额为什么要保留两位小数”流水线直接哑火业务方问“为什么知识库里明明有这份合同模型却说没有”你只能对着日志发呆。烂大街的不是RAG是那条演示用的流水线。真正的分水岭藏在这六个地方文档解析、检索链路、查询理解、索引表达、上下文控制、评测调优。这篇文章就一条条拆开讲都是我在真实项目里试过、趟过、验证过的东西适合正在把RAG从demo推向生产的同学。1. 文档解析与清洗RAG的地基也是第一道分水岭1.1 解析不是读个文本就完事先处理版式、扫描件和表格很多RAG教程的第一步极其敷衍把PDF丢进一个现成解析器抽出文本然后直接切块。真实业务里的资料很少是干净的纯文本。我见过最多的形态是扫描版合同、双栏排版的白皮书、带着页眉页脚的技术规范、还有一眼望不到边的产品说明书。直接用pypdf抽文本结果往往是段落的顺序错乱、表格被拆成碎片、图片里的关键结论全部丢失。你觉得是把文档“读进来了”实际上大模型拿到的是一座文字废墟。我现在的做法分三层。第一层做光学字符识别扫描件用PaddleOCR或Tesseract先把图像变成文字第二层做版面分析Unstructured、LayoutParser这一类工具能识别标题、段落、表格、图片区域把结构还原出来第三层才是文本抽取对表格单独处理可以用表格解析工具转成Markdown或结构化数据而不是让切块器把表格拦腰截断。Dify知识库流水线虽然内置了文档提取但遇到复杂的PDF时我一般会先在外部做完解析再分批灌入这样后续可控得多。我自己踩过的一个坑是只抽正文丢掉表格。结果用户问“这个产品有几档配置每档差价多少”模型答得磕磕绊绊因为答案藏在表格里而表格已经被拆散成几十个没有语义的片段。后来我把表格整体转成“配置名称 | 价格 | 说明”的Markdown再入库问题立刻解决。记住解析环节省下的时间会在评测环节加倍还回来。1.2 知识库能存图片吗先分清“存储”和“语义检索”很多人提问RAG知识库能存储图片吗答案是可以存但你要想清楚存进去之后怎么被检索、怎么被使用。如果你只是把图片文件挂在一个字段里那它对问答系统毫无意义因为普通文本检索器和向量检索器处理的是文字和向量不会自动“看懂”图片内容。图片里如果有产品参数、流程示意、界面截图这些信息就相当于被锁死了。真正可行的路线是先用视觉语言模型或OCR把图片内容转成文字描述再把“图片说明”作为文本块入库原图路径放在元数据里。用户问到相关内容时模型引用的是那段文字描述需要看原图时再把路径给出来。多模态检索是另一条路比如ColPali这样的模型直接对图片和文本做联合向量化效果确实好但资源成本和推理延迟都不低不是所有项目都扛得住。我通常会先问业务方图片里的信息用户更关心“内容结论”还是“原始截图”如果是前者转文字描述就够了如果是后者那就做好原图存档和引用展示。1.3 切块与拆分命中率的第一道关卡切块看似是最没有技术含量的一步实际却非常影响召回质量。固定200字、固定300字的版本我早期都试过结果是有的块把问题答案切成两半有的块塞进了两个完全无关的主题。后来我改成基于结构切块先识别Markdown标题按章节切章节太长时再递归拆段落之间有明确边界就不要硬拼。用LangChain的RecursiveCharacterTextSplitter分隔符优先级设为标题、段落、句子而不是无脑按字符数切。一个特别推荐的做法是“父子块”切分。子块小一点比如150到300字用来做精确检索父块大一些比如整段或整节用来做上下文补充。检索时先命中子块再把父块内容喂给大模型。这样既保证召回命中又不会让模型看到一段“无头无尾”的碎片。我在Dify知识库流水线里就会把块大小、重叠窗口按文档类型配置成多套方案说明书用大块FAQ用小块合同按条款切。所有块都必须保留来源、页码、标题这些元数据否则后面做引用归因和排障会非常痛苦。本地做文本拆解Unstructured、text-splitter这些工具都够用没必要一开始就上重型平台。2. 检索链路召回与重排序才是真正的技术分水岭2.1 向量检索不够混合检索才常见如果只看教程RAG的检索就是“把用户问题embedding在向量库里找相似”。但实际跑过就会知道向量检索对语义相近的表述确实有效对专有名词、编号、精确型号却经常失灵。比如用户问“合同编号C-2024-071里违约金比例是多少”向量检索可能把“违约金比例”相关的句子召回来却不一定能精确匹配到那个编号。这时候BM25这种基于关键词的稀疏检索反而更可靠。所以成熟项目里很少只靠一路召回通常是“稠密向量稀疏关键词”混合检索再加一层业务属性的元数据过滤。Dify知识库流水线里的“混合检索”模式就是这个思路Embedding模型负责语义BM25负责精确匹配两者互补再用权重调配谁说话更重要。你可能会问权重怎么设我的经验是先从五五开开始然后用一批真实问题做对比测试。如果业务里面精确查询多比如型号、编号、日期就把稀疏检索权重调高如果用户提问都是口语化的描述比如“那个经常卡住的设备怎么看日志”就把向量权重调高。这没有标准答案完全看你的数据形态和用户习惯。2.2 重排序不是“可选项”是保命的关键粗召回阶段为了召回率会把很多相关和不相关的内容都捞回来比如Top200。但如果直接把这200段往大模型里塞会发生两件事一是上下文爆炸成本飙升二是大模型被大量弱相关信息干扰反而答不准。重排序Rerank就是在这时候把候选段落重新“精排”。常用的Cross-Encoder模型比如bge-reranker-v2-m3会真正把“查询段落”拼在一起做深度交互效果远好于双编码器算相似度。我实测过一组数据有一批召回结果里正确答案排在Top20之外向量相似度分数都不高但用重排序模型一压答案被提到Top3。可以说重排序是我在RAG项目里投资收益最明显的一步。还有一个细节重排序后不要直接取TopK要设一个最低分数阈值。如果所有段落分数都很低说明文档库里可能根本没有答案这时候更应该触发“无法回答”流程而不是硬选一条弱相关段落去生成。2.3 一条可落地的检索链路设计我现在做项目默认的检索链路是请求进来后先做轻量级意图判断再并行发起多路召回——向量检索、BM25检索、精确字段过滤同时跑接着把所有候选合并去重对重复内容做折叠再送进重排序模型最终TopK取3到5段按分数顺序拼装上下文。整个过程要做到可观测每一轮查询的召回率、命中率、重排序前后排名变化都要有日志否则出了问题你根本不知道是哪个环节丢的。参数上也给个参考粗召回TopK一般设50到200重排序后取3到5如果你用父子块子块召回后对应父块会进一步去重。这些参数别拍脑袋用评测集多跑几轮再定。RAG框架用Dify、LlamaIndex、Haystack都能搭出这条链路但框架只是辅助真正值钱的是你对每一路召回信号的理解。3. 查询理解RAG不是“搜索框”是对话系统3.1 查询改写先让问题变成“可检索的句子”很多RAG项目上线后最常见的问题不是知识库不行而是用户根本不会按照你预设的方式提问。用户会说“那个文档里说的那个就是跟上一份合同差不多的那个”这句话里充满了指代和模糊描述直接拿去向量检索效果可想而知。查询改写Query Rewriting就是让大模型先对用户问题重新表述补全上下文、把指代换成具体对象、把口语变成更完整的检索点。我在系统里会加一个快速的改写步骤把最近几轮对话历史和当前问题一起交给大模型让它输出一到两个适合检索的候选查询再去跑召回。改写这一步不要做得太重。我见过有人让大模型输出五个改写版本然后每个版本都去做检索再把结果合并结果延迟翻了三倍收益却不明显。比较务实的做法是只生成一个改写后的主查询最多再加一个关键词版本召回时两条并行。改写费用很低但能显著提升长尾问题的命中率。3.2 意图识别与路由该走向量库还是知识图谱不是所有问题都适合从RAG文档库里找答案。有一次业务方问“某类产品总共有几条产品线分别关联哪些供应商”这种问题涉及实体之间的多跳关系你把所有文档都检索出来大模型也难以拼出一个不重不漏的答案。这种问题更适合走知识图谱查三元组关系。反过来一个“开通流程中的步骤是什么”这类描述性知识图谱反而没有文档来得直接。所以在检索之前务必做一次路由判断当前问题到底该走RAG文本检索、知识图谱查询、还是结构化数据库SQL查询。这里就牵出频繁被问到的概念区分KG知识库、RAG知识库和结构化知识库到底有什么不同我的理解是RAG知识库存的是非结构化文本按语义召回适合“这段内容讲了什么”KG知识库存的是实体和关系适合“A和B之间有什么关系”结构化知识库存的是规范表格数据适合“某个字段精确是多少”。它们不是互斥的真实系统里常常串联先用路由判断问题类型再调用对应引擎必要的时候把三者的结果合并到一起。3.3 RAG智能体化多轮交互和工具调用“RAG智能体”是最近绕不开的热词。如果你只是给RAG加一个“判断要不要继续检索”的循环这也可以叫智能体但真正的价值在于多轮交互。用户第一次问“怎么部署”你要先判断他是在问本地部署还是云部署如果资料里两者都有不妨反问一句“你指的是哪种环境”而不是自作主张答一个。智能体可以承担这种澄清职责它维护对话状态发现检索结果置信度低时发起追问或转换检索策略。我的实践心得是别急着把流程做成“大模型自主决定一切”。先让路由规则固定再逐步放开自由度。比如初期可以设定如果查询改写后召回结果低于阈值就尝试切换知识库或触发澄清追问后期再让模型自己选择调用哪个工具。这样可控性好很多也方便线上排障。RAG加智能体解决的不是“能聊天”而是“不会在模糊指令上一本正经跑偏”。4. 索引设计与知识表示向量库之外的世界4.1 三种知识库的边界与应用场景很多人把“搭知识库”等同于“装一个向量数据库”这其实是很大的误解。我用一个表格把三类知识库放在一起对比你就清楚了类型数据形态典型技术适合解决什么问题不适合什么RAG知识库非结构化文本、段落向量库、倒排索引、混合检索“文档里怎么描述”“有哪些要点”精确数值查询、多跳关系推理KG知识库实体、关系、属性三元组图数据库Neo4j等“A和B之间是什么关系”“链路上的实体有哪些”大段语义描述、开放性表述结构化知识库数据库表、CSV、API关系型数据库、SQL“某个订单金额是多少”“按条件过滤”模糊表达、语义不清晰的提问在实际项目里一个企业问答系统往往是三者混用的。RAG知识库承载产品文档KG知识库存放组织架构和业务流程关系结构化知识库对接订单和库存数据。用户问“产品文档里怎么描述”就走RAG问“这个流程的下游是谁”就走KG问“库存还剩多少”就走SQL。三种引擎各有擅长硬让一种引擎干所有活就会出现答不准或者大量幻觉。4.2 Ontology RAG用本体约束知识边界光有实体关系还不够我们还需要一套“词汇表”来约束概念之间的边界这就是本体Ontology。举例来说医疗领域里“高血压”和“血压偏高”可能是同义概念“并发症”和“合并症”有关联但不完全等同。如果这些语义关系没有定义RAG检索到的内容就可能跑偏。ontology-rag的思路是借用本体指导查询改写和检索扩展查询里出现“血压偏高”时根据本体自动关联到“高血压”再一并检索相关文档段落。这种做法的收益在垂直领域特别明显。金融、医疗、法律这些行业里术语的一致性几乎是命根子。我见过一个法律文档问答项目不使用本体时模型经常把“要约”和“要约邀请”混着答因为它们在很多文档里都相邻出现过。引入本体约束后每次检索前先做术语映射召回准确率提升非常明显。但这部分维护成本高建议先画最小可用本体不需要一上来就把业务图谱建得又全又细。4.3 分层索引和长文档处理长文档是RAG最容易翻车的场景之一。一份200页的项目文档切片后可能有几百上千个块用户问一个非常具体的事项向量检索可能把精力浪费在“主题相近但位置不同”的块上。我会在索引层做分层先把标题层级生成摘要索引摘要块负责定位到章节章节再映射到段落块。检索时先命中标题/摘要层再顺着映射关系展开详细内容这样可以避免一上来就在几百个片段里大海捞针。还有一个实战技巧把同一个主题下的多个文档片段通过元数据聚合成“虚拟文档”。比如用户问“这个产品所有型号的保修政策”单段召回或许只能找到某一种型号但虚拟文档聚合能把所有型号的相关段落合并再让模型做横向对比。这一步复用前面的父子块技巧只是映射关系从“父子”扩展到了“主题”。知识表示想清楚了检索质量自然有质的提升。5. 上下文管理与幻觉控制生成阶段的分水岭5.1 上下文长度不是越大越好大模型能处理很长的上下文不代表你就应该把Top10段落全塞进去。如果召回的段落里有两段是干扰信息模型很可能被它们带偏术语叫“迷失在中间”。我的做法是先把召回段落交给轻量级模型做一次“过滤和摘要”把每段压缩成两三句话再拼装成上下文。这样既保留了核心证据又压缩了噪声。重排序之后把最相关的段落放在提示词最前面然后是次要段落让模型优先看到权重最高的证据。这里有一个具体的分数阈值建议如果你用Rerank模型输出0到1的分数0.35以下的内容基本可以不进上下文。阈值太低噪声会干扰生成阈值太高可能错过正确答案。一般来说在评测集上多跑几轮找到“忠实度不下降、答案正确率最高”的区间即可。上下文管理做得好很多幻觉问题其实不需要在prompt层面反复强调。5.2 引用归因与“不知道就直说”生成阶段最容易出现的问题是模型不遵守证据擅自调用训练时学到的知识。对抗这个问题的办法一是强制引用二是允许拒答。我会在提示词里明确要求如果回答依赖某段资料必须标注来源编号例如“根据《产品手册》第三章容量为128GB[2]”如果资料里找不到答案明确回答“当前文档中没有找到相关信息”禁止编造。这里有个容易被忽略的点模型输出引用以后后端一定要做验证。解析出引用的编号去比对召回结果里是否真的存在对应段落、关键数字是否一致。如果引用不存在或者证据里根本没有那个数字宁可直接拦截结果也不发给用户。这一步我习惯用规则脚本处理不交给大模型自由发挥因为它们也会犯错。经常有人抱怨RAG会“一本正经地胡说八道”说到底是因为只做了“检索生成”少了“验证拒答”这道闸门。5.3 场景示例怎么判断回答真的“有依据”举一个我做过的例子。用户问“XX设备的最高工作温度是多少”知识库里有两份文档一份写“最高工作温度85℃”另一份写“短时可达100℃”。如果只做常规RAG模型很可能把两者都写上去然后说“大约在85到100℃之间”看起来没错但漏掉了“短时”这个条件。更严谨的做法是要求模型每次必须引用具体来源和限定语然后后端把“85℃持续工作”和“100℃短时”分别与原文比对确保回答没有丢掉限定词。RAG的瓶颈往往不在模型的推理能力而在“回答与证据的一致性”。我常用一个自动评分指标叫忠实度faithfulness把答案拆成若干原子声明再逐个去证据段落里确认是否被支持。分数低于0.8的结果直接不显示或者触发改用更严谨的prompt重跑一次。第一次跑RAG项目建议每天抽看20条失败case很快你就能总结出真正的瓶颈在哪一层。6. 评测不是收尾动作而是持续调优的底座6.1 先有评估集再谈调优没有评估集的RAG项目基本是玄学调参。我见过不少团队上线前用两三个测试问题自测一下感觉“还行”就发布了结果上线后用户一问各种角度的问题就露馅。正确做法是在项目初期就收集业务侧的真实问题凑够50到100条哪怕一开始没有标准答案都行。每条问题标注三样东西期望的答案要点、答案应该在知识库的哪个来源段落、以及这个问题是否应该拒答。收集来源可以是客服聊天记录、搜索词日志、销售提问记录。研发团队自己编的问题往往太干净真实用户说话又啰嗦又含糊所以一定要用真实表达。我通常还会按难度把数据集分为“简单检索型”“多跳推理型”“跨文档对比型”“无答案型”这样评测跑完就能看出来模型到底在哪类问题上掉链子。6.2 拆开测端到端指标和分环节指标做评测时不要只看最终答案对不对那样出了问题你根本不知道是召回漏了、重排错了还是生成阶段幻想了。我会把指标拆成三层检索层看召回率Recallk和命中的倒数排名MRR重排阶段看NDCG生成阶段看忠实度faithfulness和答案相关性answer relevance。端到端再看一个准确率或采纳率。每层独立打分哪层分数低就去修哪层效率要高得多。自动化评测可以用大模型当裁判让一个强模型对“参考答案系统回答证据段落”进行打分但一定要定期人工抽检因为裁判模型自己也会有偏。比较粗暴的做法是让裁判模型输出通过/不通过然后每周人工复核10%到20%的case。评测跑完把失败case按“缺召回、召回错、重排乱、生成幻觉”分类你会看到明显的分布规律。6.3 上线后的反馈闭环和后续扩展评测集不是一次建完就永远不变的。上线后要把用户的真实反馈持续回收用户对回答点了“有帮助”还是“没帮助”有没有继续追问有没有在回答后立刻切走。每天看失败case你会发现一些新问题类型是初始评测集没覆盖的那就把新case补进评测集形成滚动的回归测试。这个过程比换一个更贵的Embedding模型有用得多。后续扩展也基于这个闭环如果发现某一类问题总是因为文档更新不及时答错就建立增量更新机制如果发现多跳问题经常失败可以考虑给RAG再接一层图谱查询如果发现回答过于生硬再添加对话风格的prompt。我个人在实际项目里的体会是RAG从能跑到跑得稳靠的不是堆组件而是不断用真实数据把每一层分水岭都填平。你提前想清楚这六个分水岭就不会再把它当成一条“灌进去就能用”的流水线。