RAG系统详解:从知识检索增强到混合检索与重排序实战
先说一个我最近切身感受到的场景某问答项目的线上日志里用户反复问同一个问题——“你们支不支持批量导入”而大模型的回答每次都是“请提供更多上下文”或者干脆绕着圈子答。模型没有说错话但它根本不知道答案。这种时候最直接的办法不是换更大的模型而是给模型一本可以翻的“说明书”。这就是RAGRetrieval-Augmented Generation检索增强生成要解决的事。这篇文章围绕“8.3 RAG系统知识检索增强”来展开适合三类人看一是刚接触大模型应用、想给模型外挂私有知识的开发者二是已经在做RAG但检索效果不理想、想知道问题出在哪儿的工程师三是想从原理层面理解“为什么要分块、为什么要重排序、为什么纯向量检索不够”的产品和技术负责人。我会把RAG从离线索引到在线查询的完整链路拆开讲穿插可复现的参数配置、选型思路和实际踩坑记录最后聊聊这类系统在真实项目里最容易翻车的几个环节。1. 大模型记不住的东西交给外部知识库去补1.1 为什么只靠大模型不够先看一个大模型自身没法绕开的约束知识截止时间。模型训练完之后它的世界就固定在那一天。一个去年发布的新政策、一份最近更新的操作手册、一批内部产品的规格变更模型都不可能知道。就算是在截止时间之前的内容模型也只能记住“被训练语料覆盖到的部分”那些低频的、冷门的、只在企业内部文档里出现的知识它基本没有机会见过。另一个问题是幻觉。模型在不确定答案时倾向于用流畅的语言“编”一个说得通的回答而不是承认不知道。这在闲聊场景里还能接受在客服、医疗、金融、设备运维这些领域就是事故。早期的问答系统靠写规则做兜底维护成本极高后来有人开始做提示词约束让模型说“我不清楚”可模型并不知道自己哪些不清楚所以这个方案也不彻底。RAG的思路其实很朴素模型不知道的我们先把答案找出来塞给它。在用户提问的时候系统先去检索知识库把最相关的几段内容拿出来连同原始问题一起交给大模型让它根据这些资料来回答。这样一来模型不需要“记住”答案只需要“读懂”我们给的资料幻觉问题被大幅压低知识也有了明确的出处可以追溯、可以更新。1.2 RAG和微调的取舍我在不同项目里经常被问到要不要做个微调这个问题的标准回答是看你想让模型变什么。微调改变的是模型的“能力”和“行为习惯”比如让它输出固定格式、学会某种语气、掌握某个专业术语的表达方式。但微调很难把“事实”塞进模型。你拿一千份文档去微调模型最多记住几个高频结论面对同样内容的另一个问法它照样可能答错而且即使答对了你也说不出它是真知道还是蒙对了。RAG改变的是模型“可访问的信息范围”。它不改变模型的权重也不改变模型的表达能力只控制每一轮回答时模型能看到什么。文档更新之后重建索引马上生效不需要重新训练。微调一个模型动辄几天几周而且还需要准备高质量的训练数据相比之下RAG一周内就能从零搭出可用版本。不过RAG不是万能的。如果模型本身理解能力太差给它资料它也读不懂如果任务需要模型具备某种“推理习惯”而不是“查找答案”RAG帮助有限。现实项目里两个手段经常混用先用RAG解决知识来源问题再用微调解决输出风格和格式问题。微调改个性RAG补知识两者不是替代关系。1.3 什么场景值得上RAG不是所有项目都需要RAG。判断标准可以归纳为三条第一答案依赖的内容是否存在一个“外部来源”比如FAQ、产品手册、政策文件、实验报告第二这些内容是不是经常变化需要及时反映到回答中第三回答是否正确有没有可追溯的验证需求。如果三条都满足RAG就是当前性价比最高的方案。反过来说如果问题本身就是开放性的比如“帮我想几个创意”或者用户只想要一个简短寒暄那RAG没有太大意义。另外如果知识库很小、问题也很固定直接把FAQ写进提示词可能更简单没必要引一条完整链路进来。项目里有个错误倾向一上来就搭建完整RAG系统做完之后发现80%的问题靠简单的规则匹配就能解决。判断“复杂度边界”在哪里比盲目上技术栈更重要。2. 一条查询从发起到返回RAG的完整链路拆解2.1 离线侧从原始文档到可检索的索引RAG系统的离线侧目标只有一个把非结构化的文档变成“计算机能快速检索的结构化索引”。这里又拆成三个步骤解析、分块、向量化。解析阶段处理的是格式问题。PDF要提取文字并保留段落顺序Word要做标题层级识别网页要去掉导航和页脚噪声。很多项目在第一步就会踩坑扫描版PDF解析出来是一堆乱码表格数据被截断成半行文字代码片段被误当正文切碎。我的建议是在解析阶段宁可保守一点先保住文本完整性再考虑怎么切片。文本都是错的后面做得再好也没有意义。分块是把长文档切成小段每段作为一个独立检索单元。这个步骤直接影响检索精度后面会单独展开。向量化则是用Embedding模型把每一段文字转换成一串浮点数向量让“语义相似的句子在向量空间里距离更近”。这些向量和对应的原文一起存入向量数据库同时维护一个从向量到原文ID的映射关系。在线查询的时候把用户问题也变成向量去向量库里找最近的若干条记录。2.2 在线侧召回、重排、生成三步走用户提一个问题之后系统内部的流程大致分三步。第一步是召回。把用户问题向量化和知识库里的向量做相似度计算取Top-K个候选段落。K的选择通常在5到20之间太小容易漏掉关键信息太大会让后面的模型输入过长、噪声增多。纯向量召回的方式是“只按语义距离排序”它有一个天然缺陷忽略关键词精确匹配。比如用户搜“服务条款第3条”语义上最接近的可能是“服务条款概述”而不是真正的第3条原文。为了避免这个问题越来越多系统采用“混合召回”向量检索跑一遍同时用BM25这类传统词法检索跑一遍把两路结果合并去重再进入下一步。第二步是重排。召回阶段拿到的是几十条候选不能全塞给大模型所以要精挑。重排器Reranker会以“问题-段落”配对为单位逐一打分判断这段文字对回答当前问题到底有多大帮助。这一步和召回阶段的向量相似度不同重排器是专门为“相关性判断”训练的模型精度更高但也更贵所以只用来处理召回后的少量候选。第三步是生成。我们把重排后的得分最高的几段内容拼接起来按照“系统提示词参考资料原始问题”的结构发给大模型并在提示词里明确要求只能基于参考资料回答如果资料中没有答案就说不知道。这一步是RAG效果好坏的最后一道闸门提示词的约束力直接决定模型会不会发挥过度。2.3 一条实际请求的日志视角为了讲得更具体我给你看一个简化版的实际流程日志字段做了脱敏[query] 批量导入支持哪些格式 [expand] 批量导入, 支持格式, CSV, Excel, API [recall: vector] 命中 doc_345 段落2 (score 0.83), doc_102 段落5 (score 0.79) [recall: bm25] 命中 doc_345 段落2 (score 8.5), doc_208 段落3 (score 7.2) [merge] doc_345_p2, doc_102_p5, doc_208_p3 [rerank] 1. doc_345_p2 (0.92), 2. doc_208_p3 (0.68), 3. doc_102_p5 (0.45) [prompt] 参考资料: doc_345_p2, doc_208_p3 / 问题: ... [answer] 支持CSV和ExcelAPI导入正在灰度中...如果你在一个真实系统里看到这种日志就能一眼定位问题出在哪一段。检索阶段没有命中有效文档说明索引或分块有问题召回命中多但重排后分数普遍很低说明候选质量不行前三步都正常但生成结果胡扯说明提示词或模型本身要调。RAG问题排查的核心思路就是“逐段分离定位”别一上来就怀疑模型。3. 分块、嵌入与向量检索决定检索质量的三个环节3.1 分块策略不同文本结构要区别对待分块是RAG系统里最容易被轻视、实际影响最大的环节之一。分块太粗一段文字塞进了多个主题向量表示会被稀释检索时相似度分数不高不低找不准分块太细语义不完整比如一个条件句被从中间切断模型看到的是残缺信息。经验法则是块大小要根据“最小完整语义单元”来定而不是统一拍一个数字。我个人的实践是按文档结构分块——先解析出标题层级标题下的小节作为一个候选块块超过阈值的再递归拆分同时保留上下文的标题作为前缀。这个策略在有明确结构的文档说明书、规章制度、论文上效果很好。但像聊天记录、工单描述这类弱结构化文本就得靠固定窗口大小加重叠比如每块512个token相邻块之间重叠64个token。重叠的目的是防止同一个语义单元恰好被截断落在两块里给检索留点缓冲。还有一个细节经常被忽略分块之后要不要保留元信息。建议每一段带上文档标题、章节路径、更新时间、来源URL这些字段。检索结果里这些东西既是给重排器用的信号也是给用户展示来源的依据。没有元信息的RAG系统做出来像是个黑箱回答对了不知道哪儿查到的回答错了也没法追溯。3.2 嵌入模型选型通用与领域之间的平衡选Embedding模型时第一反应是看榜单分数这个方向对但不完全对。我更建议先做一组“领域样本测试”把你业务里典型的20个问题拿出来人工标注每个问题应该命中哪几个文档段落然后用不同Embedding模型跑一遍看各自的召回命中率有多少。分数高的通用模型不一定擅长你的领域尤其当文档里充满缩写、术语、数字型号时通用模型的向量空间很可能不认识这些词。举个例子。某设备维修场景的文档里大量出现“P-2311泵组”“联锁跳变”“DCS点位”这些词。用通用中文Embedding模型做检索“P-2311泵组”经常命中不了换用针对工业文档微调过的Embedding模型之后相关段落能稳定排进Top-5。这不是说通用模型不行而是说在专业领域里词表和语义分布差异太大稍有偏科就会影响召回。使用Embedding模型还有一个容易忽略的点文本长度限制。模型通常只接受512个token以内的输入超过会被截断。所以分块大小的上限本质上是由Embedding模型的输入窗口决定的不是你想切多大就切多大。3.3 向量检索为主还是混合检索为主纯向量检索的问题刚才提过它对精确词匹配不敏感。用户问“CVE-2024-1234”这个漏洞编号如果知识库里写的是“CVE-2024-1234 存在远程代码执行漏洞”向量检索大概率能命中但如果你问的是“编号1234的漏洞”向量检索可能就懵了。词法检索在“精确值匹配”上有天然优势编号、型号、人名、条款序号这些场景是它的强项。所以现在的RAG系统主流做法是混合检索向量召回做“语义模糊匹配”BM25召回做“词法精确匹配”两路结果合并后做归一化打分。归一化方法各家常不一样简单的做法是分数分别做Min-Max缩放到0到1再加权求和稍微复杂一点的做法是把两路结果交给重排器统一排序让模型自己学习“什么时候更信向量、什么时候更信词法”。从成本角度看混合检索多出来的BM25几乎不增加计算开销索引是文本级的不需要额外模型。我建议所有RAG项目从一开始就做混合检索不要先做纯向量、发现问题再加。一步到位省下来的重构时间比省下的那点开发量值钱得多。4. 从Demo到生产一次可复现的选型与部署记录4.1 基础链路选型清单下面这套选型是我在一个内部知识问答项目里实际跑过的组合目标是快速验证RAG在业务数据上的效果选型原则是“够用就好、方便替换”。环节选型理由文档解析自研解析服务加第三方PDF库文档格式杂统一走一套解析接口便于后续扩展分块标题结构优先固定窗口兜底兼顾结构文档与弱结构文本Embedding中文通用Embedding模型加领域微调版本对比用领域样本测试决定最终使用哪个向量库支持过滤、混合检索的向量数据库避免后期因元信息过滤需求而迁移词法检索BM25精确匹配兜底与向量结果合并重排轻量级Cross-Encoder重排模型平衡精度与性能大模型通用对话模型上下文窗口稍大依赖提示词约束完成生成这里要强调一点选型清单里的任何一项都不是固定的。向量库完全可以换成开源自建的Embedding模型迭代很快两三个月出一个新的候选。关键是每一层都留好接口不要把业务逻辑耦合死在某个具体实现上。我在项目里吃过这个亏早期用了某个向量库的特有排序语法后续要切换成别的库时检索逻辑要重写一遍。4.2 一套可直接落地的配置参考给一份可复现的分块与检索参数配置项目里可以直接拿去做基线测试再根据自身数据调整chunking: strategy: hierarchical max_tokens: 512 overlap_tokens: 64 min_chunk_tokens: 128 metadata_fields: [doc_id, title, section_path, update_time, source_url] embedding: model_name: text-embedding-3-small-classic max_input_tokens: 512 batch_size: 32 normalize: true retrieval: top_k_candidates: 20 vector_weight: 0.7 bm25_weight: 0.3 rerank_top_n: 5 min_score_threshold: 0.35 generation: model_context_window: 8192 reference_docs: 5 temperature: 0.2 max_output_tokens: 1024 system_prompt: 只依据参考资料回答...几个参数的解释min_chunk_tokens: 128表示小于128个token的碎片段落会被并入相邻块避免出现大量半句话块temperature: 0.2是经过测试后确定的降到0以下容易让回答生硬调到0.5以上幻觉概率明显升高min_score_threshold: 0.35的含义是重排结果低于这个分数时系统直接回答“知识库中未找到相关信息”不让模型硬答。4.3 部署之后先看这三个信号系统上线之后不要急着调提示词先看三件事第一知识库索引覆盖率——到底有多少文档成功进入了向量库多少文档解析失败或切片后为空第二检索Top-K命中率——随采样一批线上问题看正确文档是否出现在召回列表里这是检索质量最直接的反映第三生成环节的拒答率——有多少问题被系统判定为“知识不足”如果拒答率太高说明检索环节漏掉了太多信息不是模型的问题。这三个信号能帮你把问题快速分层索引覆盖率低问题在解析和入库检索命中率低问题在分块和Embedding命中率正常但拒答率高问题在重排阈值或提示词。逐层排查比盲目调参高效得多。我见过有人花了两周调Embedding模型最后发现是PDF解析阶段把一半文档的表格丢了。5. 检索增强系统的短板与优化方向5.1 索引更新RAG最容易被遗忘的运维负担RAG系统的知识不是一次建好就永远不变的。业务文档每周更新过时内容如果不及时从索引里移除模型会拿旧信息回答新问题。这在客服场景里尤其致命——用户问“最新价格”系统还答上个月的报价。索引更新策略一般分三种定时全量重建、增量更新、源文件触发更新。全量重建最简单但文档量大时耗时很久增量更新需要追踪文件变更状态触发更新最可靠又依赖文档管理平台能提供事件通知。我的建议是前期先做定时全量重建每天凌晨跑一次等系统稳定后再按需上增量。不要把增量更新设计得太复杂很多团队在上线第一个月就被“索引和源数据不同步”这个运维问题拖住了。5.2 多跳问题与查询改写RAG系统有个很典型的能力边界当问题需要综合多个段落甚至多篇文档的信息时单次检索往往不够。比如问“A型号设备和B型号设备的保修政策有什么区别”答案可能分散在A设备的介绍页和B设备的售后条款里一次召回很难同时拿到两个信息源。这种问题被称为多跳Multi-hop问题。应对思路是查询改写加多轮检索。把原始问题拆解为多个子查询先后检索再把第一次检索到的内容作为上下文去生成第二次查询。另一种更轻量的做法是“检索前改写”把问句中的指代词还原成完整描述比如“它们的保修政策”改写成“A型号设备和B型号设备的保修政策”能明显提升召回质量。这里可以用一个小模型做改写不必每次检索都动用大模型。5.3 另一个容易被低估的方向重排序模型要单独评估很多RAG项目会在检索环节花大力气对重排器却只随便选一个模型接上就不管了。但重排器是“从50条候选里挑出5条”的关键一旦它选错前面的召回做得再好也没用。我习惯把重排器当作一个独立模块来评估构造一组“问题-候选段落-相关与否”的测试集计算它的NDCG和Precision5而不是只凭感觉看几个例子。选重排模型时要特别注意它的输入长度限制。Cross-Encoder类重排器一般限制在512个token内段落过长会被截断导致相关信号丢失。遇到长段落要重排先按块召回再对块重排最后把命中的块还原成完整章节片段给生成模型这个策略在实践中效果不错。5.4 从RAG到Agent检索增强的下一步写这篇文章时RAG系统的边界正在被进一步推宽。传统RAG是“检索一次生成一次”遇到复杂任务时能力有限。现在很多系统把检索能力包装成一个工具交给Agent去规划调用——模型先判断要不要查知识库决定查几次查完再总结回答。这让系统具备了自主分解问题和多次检索的能力也能根据中间结果判断“信息够不够要不要换一个角度再查”。这个方向很有前景但对应的工程复杂度也上来了。Agent会自己决定调用动作日志追踪、超时控制、成本预估都要重新设计。我的观点是先把单轮RAG做到满意再考虑Agent化。别让Agent的灵活性掩盖了底层检索能力不足的问题。6. 一些实操建议少走弯路的经验之谈6.1 先手工验证再自动化评测搭建评测集是件很花时间的事但从项目第一天就该做。选50到100个真实问题每道题标注出正确答案对应的文档段落这就有了最基础的“问题-答案-证据”三元组。之后每次改动分块参数、换Embedding模型、调重排器都用这套数据跑一遍效果一目了然。基于这套人工标注的数据可以逐步扩展自动化评测。生成回答之后用“答案里是否包含关键事实”作为自动判分依据例如检查答案中是否出现了正确答案里的关键实体和数值。这种自动判分的准确率不是100%但用来对比不同版本的相对好坏完全够用。没有评测集的情况下调参基本等于猜。6.2 检索质量比生成质量更难优化也更值得投入项目中后期的大部分问题都出在检索上召回的文档不相关答案再怎么润色也是错的。我建议把优化精力按6:3:1分配——六成投入检索优化三成投入生成优化一成投入工程运维。很多人把精力放在提示词调优上因为反馈直接、看得见变化但检索的每次提升都是系统性的。具体到检索优化优先级先解决分块合理性再检查Embedding模型是否适应领域然后加混合检索最后调重排器。这个顺序基于“影响范围和隔离难度”排列每一步都能单独验证不会因为同时改了多个变量而搞不清是谁起的作用。6.3 最后一个小技巧给提示词里加入一层“参考答案格式”约束会明显提升回答的可用性。比如要求“先直接给结论再简单说明依据最后标注来源”。这种方式让用户一眼看到核心答案又保留了追溯出处的路径。在实际项目中同一个RAG系统换了这个提示词结构之后用户对回答的满意度提升了不少。提示词别贪多越简洁的指令模型执行得越稳定。