资讯动态

没有检索模型的RAG是严重错误:构建完整检索管道才是正解

发布时间:2026/10/22 23:33:06 来源:尧图企业网站定制
这几年聊 RAG 的项目越来越多但真正把 RAG 做好的人反而比刚开始那批还少。原因很简单大家都把重心放在“生成”上没人愿意花时间打磨“检索”。我之前接手过一个知识库问答系统当时的表现就是“资料放进去能问但答案基本靠猜”。后来拆开一看检索环节只是把一个向量数据库接上文档直接整篇丢进去Query 进来之后拿向量库默认配置算了个相似度然后把召回结果原样塞给大模型。这种系统上线之后用户随口换一种说法问同一个问题答案就完全对不上。所以我对“没有检索模型构建 RAGs 是一个严重的错误”这件事几乎是举双手赞成。先说清楚一个容易被误解的点检索模型不是指某个单独的“检索大模型”而是整套负责“从知识库里找到有用上下文”的组件组合。嵌入模型、切分策略、索引结构、召回方式、重排器甚至 Query 改写逻辑都属于检索模型的范畴。很多人以为“我用了向量数据库所以我有检索了”这其实是把“存储”当成了“检索”。向量数据库只负责把文本变成向量存下来再给你一个暴力的相似度搜索接口它并不知道什么内容对你的问题真正有用。真正的检索模型需要在召回阶段就把“有可能相关”的候选捞出来再通过重排把“真正有用”的内容顶上去最后变成大模型能直接消费的上下文。1. 先搞清楚RAG 里的“检索模型”到底指什么1.1 RAG 不是“向量库 大模型”这么简单很多人第一次接触 RAG看到的架构图都是这样的文档切块 → 向量化 → 存入向量库 → 用户提问 → 向量检索 → 拼 Prompt → 大模型回答。看起来确实很顺甚至用现成的框架只需要几十行代码就能跑通。但跑通和跑好是两码事。RAG 的完整链路其实是两个大阶段检索阶段和生成阶段。生成阶段由大模型完成决定了回答的表达质量检索阶段则决定了“大模型到底能看到什么材料”。如果检索阶段返回的内容本身就不相关、不完整、碎片化那么大模型即使再强也只能基于垃圾输入编一个流畅的垃圾答案——这就是所谓的“幻觉”来源之一。我见过很多项目方花钱调大模型 Prompt、换更强的大模型结果效果提升微乎其微。问题恰恰出在检索端召回结果里一半是无关文档另一半是相关但信息已经被切碎的片段。这个时候再怎么优化生成天花板已经被检索质量锁死了。所以构建 RAG 的第一原则应该是先把检索做成一个可以被评测、被调优的独立子系统再谈后面的大模型。1.2 没有检索模型的三种典型表现结合我观察到的翻车案例“没有检索模型”在实际项目中通常表现为三种状态。第一种是完全不检索。有些团队图省事把大量资料直接塞进 Prompt 的上下文里让大模型自己找答案。这种做法的后果是上下文窗口很快被撑爆而且大模型对长上下文的注意力分布并不均匀中间位置的关键信息经常被忽略。知识库稍微大一点这条路就根本走不通。第二种是只用一个简单的向量相似度搜索不做任何召回策略设计。比如文档不切分或者随便按固定长度切Query 不做改写召回 Top-K 取出来就完事。这种做法能跑通 Demo但一到真实问题就露馅用户用口语提问、用同义词替换、问一个跨段落才能回答的综合问题向量检索单凭语义相似度往往无法稳定命中。第三种是做了检索但根本没有“排序”的概念。向量检索给出一个相似度分数就认为这个分数代表“相关性”。实际上向量相似度和真正的相关性之间有很大差距。一个含有大量关键词、但核心观点不对的段落向量相似度可能很高一个句子简短但精准命中问题要害的片段相似度分数反而可能很低。没有重排阶段这些分数会被直接当成排序依据Top-K 里混进大量噪音。1.3 “检索”是一件需要单独构建的系统工程把检索当成系统工程来看你会发现它至少包含四个可独立优化的环节召回源构建、召回策略、排序策略、查询端处理。召回源构建决定了一个知识库里有多少内容是可以被搜到的。文档切分粒度、索引字段设计、是否需要保留标题和摘要等元信息都在这一层决定。召回策略决定了一次查询用哪些手段去找候选内容——是用字面匹配还是语义匹配还是两者结合。排序策略负责在召回的候选里做精排把最相关的内容放在最前面。查询端处理则负责把用户的原始问题转换成更适合检索的形式比如纠错、改写、拆解成多个子查询。这四个环节任何一个偷懒最终都会反馈到回答质量上。所以后面所有内容和方案都会围绕这四个环节展开。这也是“没有检索模型”这句话里真正想表达的你不是少了一个库而是少了整个检索工程。2. 为什么说这是严重错误几个真实翻车现场2.1 只接向量库不调检索问答结果全靠运气我之前处理过一个客服知识库场景很典型企业有几百篇产品文档和操作手册希望员工能通过自然语言快速查到自己想要的故障处理步骤。当时他们的做法非常简单——PDF 转文本每篇文档作为一个整体丢进向量库用户提问后取出相似度最高的 3 篇文档全量拼进 Prompt。上线后出现了一个很有意思的现象问“发票开错了怎么办”系统返回的 Top 文档居然是整篇《发票管理制度》里面包含大量无关的报销流程和审批说明真正关于“开错发票作废重开”的步骤只占一小段。大模型虽然能从这一小段里抽出答案但因为上下文里还混着大量其他制度内容回答里经常出现“根据报销流程你需要先提交审批”这种多余的话。问题就出在检索粒度上。整篇文档作为召回单元意味着召回的上下文颗粒度太粗。向量相似度虽然能判断“这篇文档和发票有关”但没法精确到“这一段的第几条才是用户要的答案”。正确的做法应该是把文档切成更小的语义块让检索的目标直接落在“可以独立支撑一个答案的段落”上。2.2 关键词检索直接替代向量召回语境信息全丢另外一类翻车是另一个极端为了省去向量化的麻烦直接用关键词检索如 BM25作为唯一召回方式。这种方案对专有名词和编号类问题确实很稳比如查“HC-300 型号的额定电压”关键词命中非常准。但一旦问题带有改写、口语化表达或跨语言需求就完全失灵。比如用户问“设备一直滴滴响是怎么回事”文档里写的是“蜂鸣器持续报警提示”。字面上没有任何一个词重叠纯关键词检索的得分会是 0哪怕文档里描述的就是这个故障。类似的还有“怎么退款”和“退款流程是什么”用户问法稍微一变关键词检索就直接空手而归。关键词检索并不是不能用但它只能作为召回手段之一不能作为唯一手段。语义向量负责解决“说法不同但意思相同”的问题关键词负责解决“必须精确匹配编号、术语”的问题两者结合才靠谱。2.3 召回结果不做重排Top-K 里全是噪音还有一种隐蔽的错误召回做得很认真向量、关键词都上了但召回之后直接把 Top-K 拼进 Prompt没有重排。这种方案比前两种好一些但召回的排序依据相似度分数并不等于真正的相关性。我做过一次对比试验。同一个知识库、同一个问题分别用“向量召回 Top-10 直接拼”和“向量召回 Top-20 再用交叉编码器重排取 Top-5”两种方式跑。前者给出的答案经常出现张冠李戴的情况后者则稳定很多。原因在于向量检索的相似度分数代表的是“语义空间里的距离”重排器衡量的是“这段文本和问题之间的真实逻辑关联”。后者更接近人类判断“这句话到底有没有回答问题”的方式。尤其当文档切分粒度偏小、单块文本信息密度不高的时候Top-K 里往往会混入很多“看起来相关但实际没回答”的碎片。没有重排这些碎片就会白白占据上下文窗口甚至挤出真正有用的内容。2.4 不同搭建方式的差距对照表为了让差距更直观我把几种常见搭建方式放在一张表里对比。这基本上是 RAG 从简到繁的三个台阶也是我建议项目逐步升级的路线。搭建方式召回准确性对复杂问题的支持实现成本适用场景仅向量库默认相似度检索低差极低纯 Demo、概念验证固定切块 向量/关键词混合召回中一般较低中小型知识库、内部工具语义切块 混合召回 查询改写 重排高好较高生产级问答、客服、企业知识库注意这张表不是绝对的。有些场景下文档高度结构化、用户问题模式固定可能只需要混合召回就够用。但如果你想构建一个能应对真实用户各种问法的 RAG重排和查询改写迟早要补上。跳过这些环节就意味着你的系统把最重要的判断能力外包给了一个冷冰冰的余弦相似度。3. 正确姿势一套可落地的检索管道怎么搭3.1 文档切分先让检索器知道“一段”该多大检索质量的上限在文档切分这一步就已经被定死了。切得太粗一块文本里信息太杂召回后上下文颗粒度太大切得太细单块文本失去语境召回了也答不对。我常用的切分策略是“结构优先 长度兜底”。先按文档本身的章节层级切一级标题、二级标题、段落尽量保证每一块是一个相对完整的语义单元。然后再按长度限制做二次分割比如单块控制在 200 到 500 个 token 左右并设置前后重叠 20 到 50 个 token避免一句话因为切分而拦腰截断。拿 LangChain 这类框架来说用递归字符文本分割器就能实现基础效果。核心思路是让分割器按“段落 → 句子 → 标点”的优先级逐级尝试切分而不是简单粗暴按固定字符数截断。代码大致长这样from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap50, separators[\n\n, \n, 。, ., , ], ) chunks splitter.split_text(raw_document_text)这里的chunk_size和chunk_overlap不是随便填的。chunk_size 太小单块内容不够回答完整问题太大向量表示的语义精度会稀释而且重排阶段的计算成本也会上升。chunk_overlap 的作用是让相邻块之间有上下文连续性防止关键信息恰好落在切分边界上。我建议根据自己文档的平均段落长度实验调整如果文档段落普遍较长就把 chunk_size 调大一点如果文档是短句密集的操作手册sikeli 调小一点效果更好。3.2 混合召回向量与关键词两条腿走路我强烈建议生产级 RAG 直接上混合召回而不是只靠向量。混合召回的意思是一次查询同时走向量检索和关键词检索引擎然后把两路结果合并去重再统一排序。向量检索负责语义泛化解决“换个说法”的问题关键词检索比如 BM25负责精确命中解决“文档里明确写了某个型号、编号、专有名词”的问题。两路的得分尺度不一样不能直接相加一般用加权合并或先各取 Top-N 再交给重排器统一排序。以 FAISS 做向量检索、以 BM25 做关键词检索为例一个简化的混合召回流程长这样def hybrid_search(query, top_k20): # 向量召回 query_vector embed_model.encode(query) vector_hits vector_index.search(query_vector, top_k) # 关键词召回 keyword_hits bm25_index.search(query, top_k) # 两路结果合并按 doc_id 去重 merged {} for doc_id, score in vector_hits: merged.setdefault(doc_id, {score: 0, source: vector}) merged[doc_id][score] score * 0.5 for doc_id, score in keyword_hits: merged.setdefault(doc_id, {score: 0, source: keyword}) merged[doc_id][score] score * 0.5 return sorted(merged.items(), keylambda x: x[1][score], reverseTrue)[:top_k]两路权重需要根据你的知识库类型调。代码类文档、表格类数据关键词权重要高一些口语化问答、客服对话记录向量权重可以更高。没有绝对正确的固定值只能基于实测调。很多框架比如 LangChain、LlamaIndex 以及各种国产 RAG 框架现在都有现成的混合检索组件配置好 BM25 后端和向量库一行代码就能把两路召回跑起来。重点是理解这套机制在做什么它是在用两种互补的信号提高召回的覆盖面。3.3 查询改写先把问题翻译成文档听得懂的话用户的原始问题往往不是检索友好的。最常见的两类问题一是过于口语化和文档用词差异很大二是信息不全比如问“那个能批量导出的工具在哪”代词“那个”没有任何检索意义。查询改写就是在大模型处理之前先用一个轻量 LLM 调用把用户问题翻译成“更适合检索的查询”。这一步可以简单理解成给用户的话做一次同义扩展和补全输出几个不同角度的检索子问题。我常用的一个轻量 Prompt 模板用户的问题{question} 请把上面的问题改写成 3 个适合知识库检索的子查询。 要求 1. 每个子查询独立成句避免依赖上下文。 2. 保留专有名词、型号、编号等关键信息。 3. 可以用同义词替换部分口语化表达不改变原意。 只输出子查询列表不要其他内容。然后用这个 Prompt 跑一次大模型调用把返回的子查询分别送到检索引擎最后再把所有召回结果合并去重。这样做的直接收益是即使原始问法特别模糊只要扩展后的某个子查询恰好命中了文档里的表述检索就能把正确内容捞出来。代价是增加了一次大模型调用延迟大概多几百毫秒。对于对响应速度极度敏感的场景可以把查询改写做成可选开关只对失败查询启用。但无论是否开启都要知道这个环节的存在。3.4 重排序给 Top-K 一个“后悔”的机会重排是整个检索管道里最容易被忽略、但也最值得加的一步。向量检索和关键词检索都只是“粗排”它们的任务是从整个知识库里快速筛出候选重排的任务是在这些候选里用更精细的模型精挑细选。重排最常用的实现是交叉编码器。把问题和候选文本拼接成一对输入用一个专门的排序模型给它们打一个相关性分数。这种方式比向量相似度更准因为模型可以同时看到问题和文本的完整语义交互而不是把两者各自压缩成向量之后再用距离衡量。我一般用 Sentence Transformers 或者 huggingface 上的 cross encoder 模型来跑。核心流程如下from sentence_transformers import CrossEncoder # 加载一个重排模型 reranker CrossEncoder(某模型路径) def rerank(query, candidates, top_k5): pairs [[query, doc[text]] for doc in candidates] scores reranker.predict(pairs) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) return [doc for doc, score in ranked[:top_k]]这一步通常需要把候选量控制在 20 到 50 条以内因为交叉编码器的计算复杂度比向量检索高很多不适合对全库直接排序。流程上就是混合召回先快速缩小范围重排再精挑细选最终进入上下文的只有 5 条左右高质量内容。这也是我推荐的标准链路。重排器的选择也很有讲究。轻量模型速度快但相关性判断能力有限重量级模型更准但延迟和显存占用都更高。我的经验是先从轻量模型开始实测效果不够再加更大的模型不要一上来就上最大参数避免投入产出比失衡。4. 实操复盘完整构建一次带检索模型的 RAG4.1 第一步建立一张小规模评测表不管检索管道怎么搭第一步永远是做评测集。没有评测集的调优本质上就是凭感觉调参今天觉得好明天换个问题又露馅。我一般会先挑 30 到 50 个真实用户问过的问题给每个问题标注“正确答案应该来自哪份文档或哪个段落”。不需要太复杂一张 Excel 表格就够字段包括“问题”、“对应文档ID”、“对应段落文本片段”、“备注”。然后跑一遍检索管道检查每个问题是否能在召回结果里找到标注的答案片段计算命中率。这个过程就像给检索系统建立了一把尺子。之后无论你怎么调切块大小、换嵌入模型、改重排模型都可以用同一把尺子对比前后效果而不是靠感觉说“好像好了一点”。我见过太多项目连评测集都没有就开始用大模型输出质量做主观评估结果优化的方向完全跑偏。4.2 第二步组装并验证检索管道接下来就是你实际组装管道的阶段。我会按这个步骤来先把文档解析成纯文本按 3.1 的切分策略切成块。为每个块生成嵌入向量写入向量库同时为关键词检索建立倒排索引。测试单路检索的效果看向量命中率和关键词命中率分别怎么样。接入查询改写看看二次召回的命中率有没有提升。加入重排验证 Top-5 内容是否真正覆盖答案。把最终选出的内容拼进 Prompt跑通完整的问答链路。每一步都要用评测集里的问题去测。比如你发现关键词路命中率明显高于向量路那说明你的文档用词规范、专有名词多调参时就应该加重关键词权重。反过来如果向量路能命中但关键词路经常空手说明用户问法和文档表述差异大就要重点优化查询改写。在验证阶段我习惯在每个块上保留一个doc_id和source字段方便出问题时回溯到原始文档。否则你面对的是几十万个块的向量索引出了问题根本不知道该去查哪。4.3 踩过的几个坑这一节分享几个我在实际项目中踩过的坑每个都花了不少时间才排查出来。第一个坑是忘记统一 Query 和文档的预处理逻辑。比如文档里全是英文缩写和大写型号用户查询时输入的是小写关键词检索直接失配。解决方式是在切分和查询入口都做文本归一化——统一大小写、去掉多余标点、繁体转简体。这个步骤看起来不起眼但对关键词召回的影响非常大。第二个坑是向量库配置和嵌入模型不匹配。有些向量库默认的维度是 768而某个嵌入模型输出的是 1024 维插入数据时不会报错查询时结果全是错误的。排查了半天才发现是维度不匹配。这个坑要靠规范化的初始化配置来绕过去最好在代码里强制校验向量维度。第三个坑是切分时把表格或者代码块的上下文切断了。一个表格的每个单元格被当成独立文本块之后检索回来的内容往往只有孤零零的数字或字段名完全没法支撑回答。处理这类内容时我会先检测 Markdown 表格或代码块边界把它们作为不可分割的整体进行切分。第四个坑是重排阶段直接把全库结果丢进去。交叉编码器虽然准但跑全库的成本太高一次查询要等好几秒。后来我把流程固定成“混合召回 Top-30 → 重排取 Top-5”延迟才降下来。记住重排是精排不是召回不要拿它去做全库搜索。5. 排查手册检索质量不行时该查哪里5.1 从召回、排序、文本质量三个层面定位当检索质量出问题时最忌讳的是直接瞎调参数。我会按下面的顺序排查。先查召回给定一个问题召回结果里到底有没有正确答案如果正确答案根本没进 Top-20说明问题出在召回层跟重排无关。这时候要检查切分粒度、嵌入模型的质量、混合检索的权重分配、查询改写是否生效。再查排序如果正确答案在召回列表里但被排到了 Top-20 之外说明问题出在排序层。要么是缺少重排环节要么是重排模型的能力不够要么是召回候选量太大导致重排压力过载。最后查文本质量如果正确答案排在第一位但拼进 Prompt 之后大模型回答得还是不对那就要检查召回文本本身是否完整。是不是在切分时把关键上下文切丢了是不是这个片段缺少文档标题和上下文的衔接信息是不是召回的是结论但没有依据过程这层排查逻辑就像查一条流水线先看原料有没有送进来再看分拣是否把正确的挑出来了最后看送到组装车间的零件是否齐全。每一层的问题只能在这一层解决跨层找问题往往越找越乱。5.2 症状与对策速查表下面这张表是我根据大量排障经验整理的。遇到问题直接对号入座可以省不少时间。症状可能原因解决建议换个说法问同样的问题答案完全变了缺少查询改写或嵌入模型语义能力不足加查询改写或换更强的嵌入模型召回结果里正确内容排在非常后面没有重排或重排模型太弱引入交叉编码器重排或升级重排模型答案总是缺关键细节切块粒度太大或切断了关键上下文调整 chunk_size增加文本块重叠专有名词/型号经常匹配不到关键词路径权重太低提高 BM25 权重检查文本归一化多个问题交织在一起时答不对缺少查询拆解或切块无法覆盖跨段信息用查询改写拆子问题或启用段落合并召回响应延迟明显偏高重排候选量太大或重排模型过重压缩召回候选量换轻量重排模型指标评测时命中率低但人感觉还行评测集和真实问题分布不一致用真实用户问题扩充评测集这张表不是万能的但它能帮你快速定位大多数常见问题。尤其是前三条基本上是七八成 RAG 效果不佳的直接原因。5.3 给新手的最后一句实在话我从接触 RAG 到现在一个非常深的体会是这个领域真正难的不是大模型那部分而是如何把知识库变成大模型能稳定消费的形态。很多人被各种框架的“一行代码搭建 RAG”宣传误导以为检索只是调用一下接口的事。实际上检索模型需要根据语料特点、用户问法、业务场景反复调整是一个持续的迭代过程。如果你现在正准备构建一个 RAG 系统我建议先把“评测集 可观测的召回日志 分段调优”这套基础设施搭起来。哪怕最开始效果一般只要这套基础设施在你就有改进的方向和依据。相反如果一开始就盲目套框架、堆模块效果好坏全凭感觉那项目大概率会卡在“上线后效果不稳定”的阶段再也走不出去。另外想提醒一句检索模型的升级没有终点。用户问法在变知识库内容在变业务重点也在变。每隔一段时间就要重新跑一遍评测集看看检索命中率有没有下滑有没有新的失败模式出现。把 RAG 当成一个持续维护的系统而不是一次性的开发任务这才是做生产级应用的正确心态。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑