说实话我最早接触 RAG 的时候对“纯向量检索”是有滤镜的把文本往 embedding 模型里一丢算出向量往向量数据库里一存查询的时候算个相似度取 TopK听起来又简洁又“智能”。直到我们把这套方案真正接到业务里才发现翻车的地方远比想象中多。团队内部复盘时聊到一个很扎心的问题为什么大厂做 RAG 几乎从来不用纯向量检索答案不是大厂保守而是纯向量检索这条路在真实场景里真的走不通。这篇文章不聊玄乎的“架构先进性”就从一个实际做 RAG 项目的人视角把纯向量检索的毛病、混合检索为什么成为标配、重排到底在解决什么问题以及完整链路里有哪些容易踩的坑一次性讲透。1. 纯向量检索为什么在 RAG 里不够用1.1 信息瓶颈一段话压成一个向量多少细节丢了向量检索的基本思路用双塔模型把查询和文档分别编码成一个稠密向量再算余弦相似度。表面看是“语义匹配”但本质上是信息压缩。一段几百字的文档最终只保留几百维的浮点数向量。这个压缩过程必然面临信息瓶颈——尤其对精确信息极其不友好。举个例子。你检索“ABN 公司去年营收是多少”文档里写着“ABN 公司 2024 年营收为 735 亿元”。这句话经过 embedding 模型后关键词“ABN”“2024”“735”都会被混进一个向量里。“公司”“营收”“年”这些高频词可能在注意力机制里占了主导而“735”这个数字在整段话里只出现一次很容易被“平均”掉。你检索出来的结果可能是“ABN 公司近五年营收趋势分析”话题相关但里面根本不含“735 亿”这个答案。还有一个更经典的坑否定词。文档写“该功能不支持多用户同时编辑”查询是“支持多人编辑吗”。在向量空间里这两句话的表示可能非常接近因为除了“不”字之外所有 token 都一样。模型根本分不清“支持”和“不支持”在语义上的相反关系。这种问题在关键词检索里反而简单——分词之后“不支持”作为一个整体词项直接就命中了不会搞混。老实说embedding 模型擅长的是“话题级别”的联想比如“怎么提高效率”能联想到“时间管理技巧”。但 RAG 里大量真实问题是“事实级别”的要的是具体数字、编号、日期、版本号、是否定词。这些恰恰是稠密向量最不擅长、稀疏检索最擅长的地方。1.2 “语义相似”不等于“有答案”很多人对向量检索有一个误解相似度高的文本就是包含答案的文本。这个假设在 RAG 场景里常常不成立。向量相似度衡量的是“文本在语义空间里靠得近”而 RAG 需要的是“文档片段能直接回答问题”这两件事不是一回事。做一个知识库问答时用户问“台式机的保修政策是什么”向量检索引擎很可能把“笔记本的保修政策”“平板电脑的保修政策”全捞上来因为它们的主题词高度重叠、向量表示非常接近。但这些文档回答了用户的疑问吗没有。用户要的是台式机那一页不是笔记本那一页。真正的答案可能恰恰因为文档里少了“台式机”这个词排名掉到了后面。还有一个所有做过 RAG 的人都见过的现象纯向量召回出来的 TopK 结果高度冗余。一篇长文档切成 20 个 chunk向量检索经常把连续的中段全都召回来因为它们上下文高度相似。结果前 5 条内容近乎重复真正的答案被挤到了第 20 名开外。这也是为什么很多 RAG 系统早期表现很差——不是大模型不行是检索阶段压根没把正确答案送到模型面前。概括一下纯向量检索三大毛病一、对精确词项不敏感数字、型号、否定词容易丢二、相似度得分无法区分“话题相关”和“答案蕴含”三、召回结果冗余严重多样性差。这正是大厂 RAG 不在检索阶段只押注向量那一侧的原因。2. 大厂 RAG 的混合检索究竟怎么搭2.1 召回策略要“宽进”两路缺一不可大厂做 RAG 的第一原则是召回阶段宁可多召回一些不相关的候选也不能把正确答案漏掉。召回率一低后面重排也好、大模型也罢都没有补救空间。要做到高召回最稳妥的方案就是混合检索——向量检索和稀疏检索并行两边各自召回一批候选再合并。这两路的分工非常清晰对比维度向量检索稠密稀疏检索BM25/全文检索擅长场景语义改写、同义表达、概念相关专有名词、编号、数字、精确短语典型弱点精确词项不敏感、结果冗余不支持同义改写、词汇鸿沟排序量纲连续向量相似度词频/逆文档频率统计可解释性弱很难解释为什么召回强能看到命中哪些关键词资源开销需要向量索引常驻内存索引轻量更新快实际项目中两边都不可替代。比如用户提问“怎么让协同软件登录更快”如果文档里写的是“减少登录延迟的优化方法”纯 BM25 可能因为词面不匹配而漏掉这时候向量检索能补上因为“更快”和“延迟优化”语义相近。反过来用户提问“版本 2.3.1 的导出 bug 修复了吗”文档里写“2.3.1 修复了导出功能异常”向量检索可能把 2.3.0 和 2.3.2 的文档也捞出来但 BM25 能精确锁定“2.3.1”这个版本号。说白了混合检索不是炫技是让两个互补的召回器各干各的活二路齐发把召回率拉上去。大厂的 RAG 管线里这一步基本是标配区别只在于融合策略是简单粗暴还是精细调优。2.2 结果融合RRF 为什么比加权平均稳定两路召回集合合到一起之后就要面临融合排序的问题。很多人第一反应是做加权平均BM25 分数乘以 0.3向量相似度乘以 0.7再加起来。这个思路听着合理实际一调参就想骂人因为两种分数根本不在同一个量纲上。BM25 的分数是一个没有上界的统计值文档越长、词频越高分数可能越大向量余弦相似度则是 -1 到 1 之间的值。你把两个分布完全不同的分数强行加权那个权重系数到底怎么解释0.7 的向量权重意味着什么没人说得清。每次调权重都是在碰运气而且换一个数据集最佳权重就变了根本没有通用性。业界更常用的方案是 RRFReciprocal Rank Fusion倒数排名融合核心思想是不看分数只看排名。公式非常简单score(d) Σ 1 / (k rank_i(d))其中 rank_i(d) 是文档 d 在第 i 路检索结果里的排名k 是平滑系数通常取 60。这个公式的直觉是排在第一名得 1/61排第十名得 1/70排名越靠前贡献越大而且两路之间的分数差异根本不参与计算天然避免了量纲问题。为什么 k 取 60 而不是 10 或 100当年提出 RRF 的那篇工作里做了大量实验发现 k60 在多个数据集上表现稳定取值大一点小一点影响都不大是一个非常鲁棒的默认值。我自己实测也是这个参数基本不用调。RRF 还有另一个好处——完全不依赖两路召回器的绝对分数校准哪怕某一路的分数因为文档长度波动剧烈也不影响融合结果。2.3 元数据过滤容易忽略但提升最大的一层不少团队做混合检索时只盯着两路算法怎么融合却漏了最基础的一个操作元数据过滤。向量数据库和搜索引擎里都能存结构化字段比如文档来源、发布时间、文档类型、所属产品线、权限级别。检索之前先按这些字段把候选集过滤掉一批再做相似度排序效果往往比模型本身提分还明显。我之前做过一个内部 IT 支持问答系统知识库按产品线分成了办公软件、协同工具、数据库三类。用户提问“数据库连接报错怎么办”如果不加产品线过滤向量检索会把办公软件的“无法连接打印机”也捞出来因为“连接”“报错”这些词太接近了语义空间上它们确实很靠近。加了“产品线 数据库”这个过滤条件之后无关文档直接进不了候选集检索结果干净很多。元数据过滤在执行上有一个关键点chunk 切分时必须同步保留每段对应的元数据不能只在原始文档上打标签。很多项目用现成框架切文本时忘了把上级文档的字段透传到每个 chunk导致过滤条件一执行就筛掉全部结果。3. 重排Rerank把召回质量再提一档3.1 双塔召回的排序能力是有限的混合检索解决了“正确答案在不在候选集里”的问题但候选集里面的排序依然不够准。原因在于双塔模型计算相似度时查询和文档在编码阶段是完全独立的直到最后算余弦相似度才碰一次面交互非常浅。打个比方双塔模型像提前给文档拍了一张“缩略图”存在库里查询来了也拍一张缩略图两张图放一起比对。缩略图能看出大概风格、大概色调但看不出细节。可 RAG 要求的偏偏是细节判断这份文档是不是真的回答了这个问题。所以大厂 RAG 几乎都会在召回之后加一层重排。重排阶段不再用双塔而是用交叉编码器。交叉编码器把查询和文档完整拼接成一长串文本一起喂进 Transformer让每个 token 在 attention 机制里充分交互再输出一个匹配分数。这个分数比向量相似度可靠得多因为它真正看到了“查询里的问题”和“文档里的句子”之间有没有直接的语义呼应。有人会问既然交叉编码器更准为什么不直接用交叉编码器做召回因为做不了。交叉编码器不能离线预计算文档向量每个查询来了都得把所有候选文档实时过一遍模型。如果文档有几百万条这个计算量直接爆炸。所以现实方案只能是召回阶段用双塔粗筛出几十上百个候选重排阶段再用交叉编码器精排一遍。3.2 重排模型怎么用、参数怎么调实际落地时重排这层没有那么玄。业界的做法是把混合召回的结果合并、去重候选集控制在 50 到 100 条左右然后批量过重排模型最终取 Top 5 到 Top 10 进大模型上下文。这里有几个参数经验可以参考max_length 一般设 512不宜更长。交叉编码器要同时处理查询和文档输入太长推理很慢而且超过 512 的文本大多也是冗余内容。batch_size 从 16 起步显存紧张就降到 8。重排阶段是纯在线计算延迟敏感批量大小要测一下再定。候选集不是越大越好。我试过把候选扩到 200 条重排成绩反而下降因为大量不相关的文档会干扰模型的排序分布。50 到 100 条是甜点区间。重排分数只在同一查询内部比大小不能跨查询比较。这个分数天然没有归一化不要拿它当置信度或阈值用除非你对当前场景做过专门校准。有一个容易被忽略的细节重排之后的 TopK 里如果分数相差很大说明只有前一两篇文档是真正相关的如果分数都挤在一起说明候选集质量整体一般。这时候宁可根据分数断层截断而不是机械地取 K 条。宁可少喂给大模型几条也不要硬凑长度把噪音塞进上下文。4. 完整 RAG 链路从查询改写、混合检索到上下文组装4.1 查询改写先让“人话”变成“能检索的话”用户真正输入的问题往往不是理想的检索语句。RAG 系统上线之后你会发现用户问的最多的是“那后来呢”“第二个方案有啥问题”“和前面那个比哪个好”这类指代不清的口语。这些查询直接走检索向量模型再强也白搭因为信息本身就不完整。所以大厂 RAG 管线里查询进入检索之前通常会先过一个“查询改写”环节。改写要干两件事补全省略和指代。结合对话历史把“那后来呢”改写成“上一轮提到的某某方案的实施结果是什么”。分解复合问题。用户问“A 方案和 B 方案的区别”如果只拿这一句话去检索结果可能两边都沾边但都不够。拆成“A 方案介绍”“B 方案介绍”“A 与 B 对比”三个子查询分别检索再合并结果效果会好很多。这个改写层用大模型做比较灵活但要注意延迟。一个查询改写指令跑几百毫秒检索再几百毫秒重排再几百毫秒叠加起来用户就感觉慢了。优化办法是只在必要场景触发改写比如检测到代词、指代或“对比”“区别”这类关键词时才走改写其余情况直接检索。4.2 上下文组装把物料整理好了再交给模型检索和重排做完最后一步是组装上下文。这一步看起来简单实际做起来有不少讲究。很多 RAG 效果不佳问题不在检索而在组装太粗糙。组装阶段至少要处理四件事一是排序与截断。重排之后的候选按分数降序拼接同时设一个总 token 上限。我一般控制在模型最大输入长度的 60% 左右剩下 40% 留给回答生成和指令开销。二是去重。混合检索里不同路召回容易产生内容重叠的 chunk直接拼进去会浪费 token还会让模型重复引用。简单做法是把候选之间做一次相似度计算阈值超过 0.8 的视为重复只保留分数更高的那条。三是保留来源信息。给每个 chunk 前面加一个来源标签比如“[文档 3][第 2 节]”这样大模型回答时能引用来源用户也能溯源。这一点在企业内部知识问答里几乎是刚需。四是必要时做压缩。如果候选 chunk 太长可以先让大模型对每个 chunk 生成一个一句话摘要再把摘要拼进上下文。代价是增加一次模型调用和延迟收益是能塞进更多不同来源的内容。具体取舍看业务对延迟的容忍度。我自己操作时还有个习惯chunk 切分时把文档标题拼进每个片段的最前面。比如某个 chunk 来自“4.3 节 数据迁移方案”就在文本前面加上“[数据迁移方案]”。这样做之后检索命中的上下文里自带标题信息大模型回答时更清楚自己在引用哪一节整体正确率有明显提升。5. 落地方案与踩坑实录5.1 一套可以直接抄的检索配置参考如果不想从零造轮子可以直接参考我这套配置思路。它不绑定具体技术栈但按这个调参方向去落地一般不会出大问题。配置项推荐值说明向量模型开源中文嵌入模型优先选面向中文、支持长文本且效果排名靠前的版本稀疏检索基于 BM25 的全文搜索引擎直接把这次检索当做关键词匹配不用额外训练chunk 大小200 到 400 token重叠 50段落太大信息太杂太小则上下文碎片化元数据字段来源、时间、产品线、文档类型、权限每个 chunk 切分时都要带上查询改写有指代或复合问题时触发用大模型改写保留原查询兜底召回数量每路 Top 100 到 200保证召回率后续靠重排收割融合策略RRFk60不需要归一化分数稳重排候选50 到 100 条过交叉编码器精排最终进入上下文Top 5 到 10 条超过 10 条噪音大于收益技术选型方面重排模型尽量选专门训练过的中文 reranker不要图省事复用 embedding 模型。你可以把重排看作文档级“把关人”差一个模型排序质量差一个档次。5.2 常见问题排查速查表按我的经验RAG 检索链路出问题大部分集中在下面这几类。列一个速查表方便对照定位症状优先排查方向召回结果为空先查分词结果是否合理再看检索阈值是不是太严最后确认元数据过滤条件是否把所有候选都筛掉了召回结果全是无关内容检查两路召回是否都接上了尤其确认 BM25 那一路是否真的在跑不能只靠向量一路召回结果高度重复缺少去重步骤或者 chunk 切分粒度太大、重叠太多增加相似度去重调整重叠参数召回到了但答案还是错的检查重排是否生效重排候选是否太少再查最终上下文里有没有混入低分文档查询稍长就效果暴跌查询改写环节补全了上下文但没有控制长度长查询会让向量模型注意力分散可对改写结果做关键词提取延迟偏高看查询改写是否每次都触发、召回候选是否超过 200、重排 batch 是否过大这三处按顺序排查还有一个经验值得单独提上线前一定要做回归测试集。不需要多大三五十条真实用户问题就够了。每次换向量模型、改 chunk 参数、调重排策略都跑一遍这个测试集看相关文档的命中率和最终答案打分心里才有底。没有评测集的迭代就是闭着眼睛换轮子运气好能跑运气不好就得回滚。踩过几次坑之后我最大的体会是RAG 检索不是一个“选一个最强模型”的问题而是“把几路不完美的检索器组合起来让它们互相兜底”的问题。纯向量检索单看不弱但单打独斗在真实业务里撑不住。把 BM25 请回来把元数据过滤加上把重排放进去这套组合拳打下来RAG 的效果才会有质的提升。最后再分享一个小技巧。上线之后隔一段时间把用户实际搜不到答案的问题捞出来人工看一下失败原因。你会发现大部分失败不是模型不够强而是检索链路里某一个环节的配置没跟上场景变化。定期拿真实数据回流迭代比反复换模型带来的提升更扎实。