RAG从入门到实战八检索策略与混合检索为什么不能只靠向量搜索文章目录RAG从入门到实战八检索策略与混合检索为什么不能只靠向量搜索前言一、检索策略在 RAG 中的位置二、稠密检索寻找语义相关内容2.1 工作方式2.2 优点与局限三、稀疏检索关注词项与精确匹配3.1 基本思路3.2 BM25 的直观理解代码示例用 BM25 检索中文文本四、稠密与稀疏检索的对比五、混合检索多路召回再融合结果5.1 基本流程5.2 各路取多少条六、融合方式一分数归一化后加权6.1 动手比较归一化后加权七、融合方式二RRF 倒数排名融合7.1 核心公式7.2 RRF 的优点与局限7.3 代码示例实现一个简化 RRF7.4 接入 Milvus稠密与 BM25 双路检索八、检索路由不同问题走不同路径九、如何验证检索策略十、常见误区十一、总结参考资料前言用户问“如何申请年假”向量检索可能找到措辞不同但语义相近的制度但当用户问“错误码ERR-42怎么处理”准确匹配编号可能比语义相似更重要。这说明不同问题需要的检索信号不同。一个实用的 RAG 系统往往需要同时考虑语义相关性、关键词匹配和结构化条件。本文介绍稠密检索和稀疏检索的思路与差异。混合检索如何合并多路候选。分数融合和 RRF 的工作方式。什么情况下考虑检索路由。如何评估与排查检索效果。示例数据均为虚构。检索策略没有普适最优解应结合数据、测试问题和成本选择。一、检索策略在 RAG 中的位置用户问题 → 查询理解 / 改写 → 检索策略选择 ├─ 稠密检索 ├─ 稀疏 / 关键词检索 └─ 结构化过滤或其他检索 ↓ 融合候选 → 重排序 → 上下文 → 生成这里的“召回”指从大量候选中找出一批可能相关的资料“重排序”是在候选集合内进一步调整顺序。若真正的证据根本没有进入候选集合后续重排序一般无法凭空找回它。权限控制贯穿整个流程每一路检索都必须限制为当前用户可访问的数据融合后也不能因合并不同来源而绕过授权。二、稠密检索寻找语义相关内容2.1 工作方式稠密检索通常使用 Embedding 模型将文档和问题映射成稠密向量再通过向量相似度寻找候选文档片段 → 文档向量 ─┐ ├→ 相似度搜索 → 候选片段 用户问题 → 查询向量 ─┘例如问题工作满一年能休几天带薪假 文档员工入职满一年后每年可申请 5 天带薪年假。即使用词不同合适的模型也可能识别出语义相关性。第五篇介绍了 Embedding、向量度量和索引此处关注它与其他检索方式的配合。2.2 优点与局限优点对部分同义表达、口语化问题有较好的匹配能力。可以用于问题与较长文本片段之间的语义检索。局限对某些精确型号、数字、错误码或少见术语可能不稳定。主题相似但结论相反的片段也可能被召回。受模型、语言、切分、向量空间和索引参数影响。相似度高并不表示内容正确、最新或足以回答问题。三、稀疏检索关注词项与精确匹配3.1 基本思路稀疏检索使用词项及其权重来表示查询与文档。常见关键词检索方法包括基于倒排索引和 BM25 的全文检索。当用户问错误码 ERR-42 的处理方法是什么直接匹配ERR-42可以提供很强的检索信号。稀疏检索也可帮助查找产品型号、专有名词、条款编号等。但“稀疏”不只等同于 BM25也有学习型稀疏表示。具体效果取决于分词、词项权重、字段配置以及查询方式。对中文、英文混合和包含标点的错误码分词结果尤其值得检查。3.2 BM25 的直观理解BM25 通常根据词项在文档中的出现情况、词项在语料中的区分度以及文档长度等因素计算相关性。常见词通常区分度较低较少见且匹配的词可能贡献更大。无需把 BM25 看成“只要命中就必然正确”问题和原文用词不同可能漏召回。文档中反复出现关键词不代表真正回答问题。中文分词、数字、连字符和大小写处理会影响结果。需要针对标题、正文和精确字段考虑合适的索引配置。代码示例用 BM25 检索中文文本下面使用虚构数据演示中文分词与 BM25 的词项匹配。安装依赖pip install jieba rank-bm25不需要下载模型或配置密钥importjiebafromrank_bm25importBM25Okapi documents[员工入职满一年后可以申请带薪年假。,设备连接失败时请检查网络连接。,年假申请需要提前三个工作日提交。,]tokens[list(jieba.cut(text))fortextindocuments]indexBM25Okapi(tokens)question年假申请需要提前多久scoresindex.get_scores(list(jieba.cut(question)))foriinsorted(range(len(documents)),keylambdai:scores[i],reverseTrue):print(f{scores[i]:.3f},documents[i])这里对文档和问题使用相同分词方式分数只用于本次语料内部的排序并非答案正确概率。错误码等符号组合可能被分词器拆开实际项目应检查分词结果并为精确编号设置专门字段或匹配规则生产系统还需自行加上权限过滤。本例尚未在真实检索服务上验证。四、稠密与稀疏检索的对比维度稠密检索稀疏 / 关键词检索主要信号模型生成的语义向量词项匹配及权重较适合部分同义表达、自然语言问题错误码、型号、专有词和原文关键词常见风险近似语义但事实相反精确编号不稳同义表达漏召回分词导致关键词丢失依赖Embedding 模型与向量索引分词、倒排索引或稀疏表示这只是常见倾向不是绝对规律。实际效果应通过带标注的问题集比较有时关键词检索也能处理同义词合适的稠密模型也可能识别准确编号。五、混合检索多路召回再融合结果5.1 基本流程┌→ 稠密检索 → 候选列表 A ┐ 用户问题 ────────┤ ├→ 合并 / 融合 → 候选 └→ 稀疏检索 → 候选列表 B ┘举例稠密检索可能找到“如何恢复设备连接”的语义相关片段。关键词检索可能找到精确包含ERR-42的故障手册。合并后再交由重排序或业务规则进一步判断。融合前需要保证不同索引能够回溯到同一个原始片段 ID以及版本、租户和权限范围一致。否则同一内容可能重复出现或发生不应合并的跨版本、跨租户记录。5.2 各路取多少条稠密和稀疏各取多少候选、融合后保留多少并没有固定答案取太少可能漏掉有用证据。取太多可能引入噪声、增加重排序成本。数据分布、过滤条件和问题类型都会影响结果。可以在同一测试集上对候选数量做消融实验并观察 RecallK、排序质量、延迟与成本。六、融合方式一分数归一化后加权概念上可以这样做最终得分 α × 稠密得分 β × 稀疏得分但是不能直接假定两路原始分数可比某一路可能返回余弦相似度另一路返回 BM25 分数。有的数值越大越相关有的是距离越小越相关。不同查询、索引和模型的分数分布也可能变化。常见做法是先进行适合场景的归一化或校准再设置权重。但 min-max 归一化会受候选范围和极值影响所有分数相同时也要定义边界行为归一化后仍不代表分数是概率。加权融合能利用分数信息但参数需要验证。没有标签或数据分布变化较大时仅凭经验设定权重可能不稳定。6.1 动手比较归一化后加权下面将已完成权限过滤的两路结果按片段 ID 合并。两路均使用“分数越大越相关”的约定如果检索器返回的是距离必须先按其度量方式转换方向。这个例子只演示融合不连接数据库或调用模型。fromcollections.abcimportMappingdefnormalize(scores:Mapping[str,float])-dict[str,float]:ifnotscores:return{}low,highmin(scores.values()),max(scores.values())iflowhigh:# 等分时没有排序信息约定命中项均为 1便于和未命中区分。return{key:1.0forkeyinscores}return{key:(value-low)/(high-low)forkey,valueinscores.items()}defweighted_fusion(dense:Mapping[str,float],sparse:Mapping[str,float],dense_weight:float0.5)-list[tuple[str,float]]:ifnot0dense_weight1:raiseValueError(dense_weight 必须在 [0, 1] 内)dense_norm,sparse_normnormalize(dense),normalize(sparse)idsdense_norm.keys()|sparse_norm.keys()scores{key:dense_weight*dense_norm.get(key,0.0)(1-dense_weight)*sparse_norm.get(key,0.0)forkeyinids}returnsorted(scores.items(),keylambdaitem:(-item[1],item[0]))if__name____main__:print(weighted_fusion({A:0.91,B:0.70},{B:8.0,C:4.0}))运行示例会输出[(A, 0.5), (B, 0.5), (C, 0.0)]A 在稠密路归一化得 1.0、稀疏路未召回记 0B 在稀疏路归一化得 1.0、稠密路归一化为 0等权融合后两者同为 0.5 并列平局按 ID 排序A 在前。C 只在稀疏路命中且是该路最低分归一化为 0融合后得 0.0。未被某一路召回的片段在该路贡献为 0与该路最低分归一化为 0 的片段在贡献上无法区分这是该简化策略的局限。候选集、极值和权重都会改变结果需要在同一测试集上与 RRF 比较而不是把示例权重当作最优值。七、融合方式二RRF 倒数排名融合7.1 核心公式RRFReciprocal Rank Fusion使用各路排名而不是直接比较原始分数RRF(d) Σ 1 / (k rankᵢ(d))其中rankᵢ(d)是文档d在第i路结果中的排名从 1 开始k是平滑常数。未出现在某一路结果中的文档该路不贡献分数。例如稠密排名A、B、C 稀疏排名B、D、AB 在两路中分别排第 2 和第 1A 分别排第 1 和第 3。RRF 为每个命中位置计算贡献并相加再按总分排序。k越大排名差异带来的单项差距通常越平缓多路出现的影响更明显具体表现仍取决于候选列表。不同资料中可能出现如60的经验值但它不是所有场景的最佳参数。7.2 RRF 的优点与局限优点不需要将不同来源的原始分数直接放到同一尺度容易作为混合检索基线。局限只使用排名丢失分数间隔信息候选截断、列表质量和重复 ID 处理都会影响结果。排名融合也不能纠正原文错误或替代权限验证。7.3 代码示例实现一个简化 RRF下面只演示排名融合不连接数据库。每一路列表中的元素是已完成权限过滤、且可用于去重的片段 ID。fromcollectionsimportdefaultdictdefreciprocal_rank_fusion(rankings,k60):rankings 是多个有序 ID 列表每一路重复 ID 只计算首次位置。ifk0:raiseValueError(k 必须大于或等于 0)scoresdefaultdict(float)forrankinginrankings:seenset()forrank,chunk_idinenumerate(ranking,start1):ifchunk_idinseen:continueseen.add(chunk_id)scores[chunk_id]1.0/(krank)# 分数相同时按 ID 排序确保演示可复现。returnsorted(scores.items(),keylambdaitem:(-item[1],item[0]))if__name____main__:dense[A,B,C]sparse[B,D,A]forchunk_id,scoreinreciprocal_rank_fusion([dense,sparse],k60):print(chunk_id,f{score:.6f})按这组输入B 的融合分数最高A 次之D 和 C 分别排第三、第四D 在其所在列表中排第二C 排第三。本例用 ID 作为平局时的确定性次序真实系统可以采用其他明确规则。一个细节代码对列表中的重复 ID 只计首次出现但排名仍使用原列表位置。更稳妥的做法是在送入融合器之前先按业务 ID 去重再确定每路排名。RRF 输出的分数是排序信号不是“答案正确概率”。该示例没有实现加权 RRF、超时降级、跨版本去重或权限校验生产环境需要补齐。7.4 接入 Milvus稠密与 BM25 双路检索前面的 RRF 使用人工排列的 ID下面展示如何把相同思路接入第六篇的 Milvus 集合。这是集成片段调用方必须提供已认证的client、已写入数据的collection_name、与写入向量同一模型和维度的embeddings并确认集合包含dense、sparse、content和section字段以及支持 BM25 的服务端配置。第六篇的演示 Schema 没有租户和权限字段要运行下面的授权过滤示例必须先按实际授权模型扩展 Schema、写入可信权限数据并重建集合。相关版本的 API 和分词器能力应以官方文档为准。frompymilvusimportAnnSearchRequest,RRFRankerdefsearch_hybrid(client,collection_name,embeddings,question,*,auth_filter):# auth_filter 必须由可信的服务端授权逻辑生成并验证不能直接拼接用户输入。ifnotauth_filter:raiseValueError(必须提供服务端生成的授权过滤条件)query_vectorembeddings.embed_query(question)dense_requestAnnSearchRequest(data[query_vector],anns_fielddense,param{metric_type:COSINE},limit10,filterauth_filter,)sparse_requestAnnSearchRequest(data[question],anns_fieldsparse,param{metric_type:BM25},limit10,filterauth_filter,)returnclient.hybrid_search(collection_namecollection_name,reqs[dense_request,sparse_request],rankerRRFRanker(k60),limit5,output_fields[content,section],)这段代码把同一授权过滤条件应用于两路再交由数据库进行 RRFlimit10、limit5和k60只是起点不能当成默认最优值。auth_filter是否足以表达业务权限取决于可信身份信息、字段、过滤构造和服务端规则需要对返回结果及后续父块扩展再做授权校验。不要用用户自由文本直接拼出过滤表达式。这段尚未连接真实 Milvus 和 Embedding 服务验证。运行需要符合第六篇要求的 Schema、数据、服务版本及自己的服务密钥知识库内容和问题会发送到 Embedding 服务使用前需确认授权。八、检索路由不同问题走不同路径检索路由根据问题类型、用户权限和知识库范围选择一个或多个检索器包含明确错误码 → 精确字段 / 关键词检索必要时结合向量检索 自然语言描述问题 → 向量检索必要时结合关键词检索 指定产品与版本 → 先施加可靠的结构化范围限制再检索路由可以基于明确规则也可以使用分类模型或大模型两者各有取舍。规则容易解释但可能漏判模型更灵活但可能产生误分类和额外延迟。路由结果不应成为授权依据模型判断“用户需要某个部门的文档”不等于用户有权读取它。权限由服务端可信逻辑强制约束。对难以判断的问题可以在成本允许的范围内使用多路检索或回退策略同时记录路由原因和检索结果便于离线分析。九、如何验证检索策略先准备覆盖不同类型的测试集并标注每个问题需要的原始证据问题类型示例重点语义改写“工作一年有几天带薪假”是否找到相关制度精确编号“ERR-42 如何处理”是否保留并匹配编号多条件“产品 A 的某版本是否支持退款”产品、版本与条件是否匹配跨文档“A 和 B 的规则分别是什么”两份证据是否都召回无答案“资料未提到的事项是什么”是否避免使用无关内容硬答在同一测试集与权限范围下比较稠密、稀疏、混合和不同候选数量。常用指标包括 RecallK、MRR 或 nDCG 等还需要观察答案引用、响应延迟、索引成本和无答案处理。评估时应区分“相关文档被召回”和“足以支持最终回答”。对需要多个证据的问题只召回其中一条可能仍然不足。十、常见误区有向量数据库就不需要关键词检索两类信号各有用途。把不同系统原始分数直接相加分数尺度和方向可能不同。混合后一定优于单路低质量候选、融合参数和分词错误都可能带来负面效果。RRF 分数越高越可信它反映融合排名不代表事实正确。重排序能找回所有遗漏证据未进入候选集的片段通常无法被排序器恢复。过滤条件等于权限系统权限必须由可信服务端执行和审计。十一、总结稠密检索关注语义表示稀疏检索关注词项匹配混合检索让多路信号互补。分数融合与 RRF 都需要结合真实数据验证而检索路由可以让不同类型的问题选择合适路径。下一篇将进入候选检索之后的环节Cross-Encoder 重排序与 MMR 结果多样性。参考资料RRF 原始论文https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdfBM25 概述The Probabilistic Relevance Frameworkhttps://doi.org/10.1561/1500000019
企业数字化 ERP 产品动态
相关推荐
GPT-6 Astra 的高杠杆用法 上周我把主力模型换成了 GPT-6 Astra。第一天用下来,体验并不算愉快。让它改一行变量名,它跑了 20 分钟测试,还顺手检查了三个不相关的模块。我盯着终端里的进度条,心里只剩一个问题:改个变量名,至于吗&… · 2026/11/1 1:55:31
课堂笔记4 课前练习求任意一个三位数,个十百位相加值#include <stdio.h>int main(){ int a,b,c,m,sum;printf("请输入任意一个三位数m");scanf("%d",&m);am%10;bm/10%10;cm/100;sumabc;printf("%d\n",sum);return 0;}正… · 2026/11/1 1:55:31
网页天气可视化工具开发:API调用与Canvas渲染实践 1. 项目概述:网页天气可视化工具开发实录去年夏天连续两周的暴雨预警让我意识到,市面上大多数天气应用都只提供枯燥的数字预报。作为前端开发者和数据可视化爱好者,我决定动手打造一个能直观展现天气变化的网页应用。这个项目融合了第三方天气… · 2026/11/2 20:58:01
IEC104主站源码解析:APCI/ASDU、总召与遥测实现 简介:这份资源是面向电力自动化、SCADA 系统开发者与工控通信学习者的 IEC 60870-5-104 主站协议实现源码,适合已具备网络编程与 C/C 基础、希望深入理解 104 主站通信细节的中高级开发者。压缩包共 28 个文件、约 302KB,以 cpp 与 h 源文件为… · 2026/11/2 20:58:01
Python os 模块实战:跨平台路径、目录遍历与进程避坑 上周帮人看一个数据清洗脚本,本地 Windows 上跑了小半年没出过事,扔到一台 Linux 机器上第一行就抛FileNotFoundError——路径里硬编码了反斜杠,再叠加一句os.chdir()换工作目录,相对路径全乱套。这类问题不复杂,但排查… · 2026/11/2 20:58:01
MATLAB mean数组均值:维度参数与omitnan缺失值处理 1. 先把"均值"这件事想清楚:从标量到矩阵的思维转换很多人第一次在 MATLAB 里写mean的时候,脑子里想的是一维数组求平均:几个数加起来除以个数,完事。等到真的拿它去处理实验数据、图像矩阵、传感器时序,才发… · 2026/11/2 20:58:01
Navicat SQL格式化功能解析与应用实践 1. Navicat的SQL格式化功能概述 作为数据库开发人员,我们经常需要处理复杂的SQL查询语句。这些语句可能包含多个子查询、连接条件和嵌套函数,导致代码行数膨胀,可读性下降。Navicat Premium提供的SQL格式化功能正是解决这一痛点的利器。 SQL… · 2026/11/2 20:58:01
从零学AI工程:数据、部署、监控的避坑指南与实战路线 从零开始学AI工程:我踩过的坑,和真正值得走的路线“ai engineering from scratch”这个话题,我其实一开始是带着怀疑的——两年前市面上讲AI的课程,十个里面有九个都在教你怎么调sklearn、怎么跑通一个pytorch的官方demoÿ… · 2026/11/2 20:57:01
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18