RAG 系列(七):检索策略——如何找到最相关的内容
2026/5/5 18:37:26 网站建设 项目流程

为什么检索策略很重要?

前面六篇文章,我们搞定了文档分块、Embedding 生成、向量库存储。现在假设用户问了一个问题:“Python 异步编程有什么最佳实践?”

你的向量数据库里有 10 万篇文档。最 naive 的做法是:直接做相似度检索,返回 Top-K 最相似的文档。

但问题来了:

  • 问题 1:结果重复。返回的 5 篇文章可能都在讲 asyncio,没有任何一篇讲 aiohttp 或实际踩坑经验。
  • 问题 2:低质量混入。第 5 篇文章虽然语义上有点相关,但其实是在讲 Go 的并发模型,对 Python 用户毫无帮助。
  • 问题 3:查询含明确条件。用户问的是 “2024 年关于 Python 的文章”,但纯向量检索完全无视了 “2024 年” 这个时间条件。

本文会对比4 种检索策略,帮你解决这些问题。


四种检索策略速览

策略核心思想解决的问题适用场景
相似度检索按向量相似度排序基础检索通用场景
MMR相关性与多样性权衡结果重复需要多角度回答
阈值过滤只保留高相似度结果低质量混入宁可少不可错
Self-Query解析查询生成过滤条件查询含明确条件时间/类别限定

实验环境

我们用 10 篇技术博客文章作为测试数据,每篇带有元数据(年份、类别、标签):
可运行的实验源码在文章最后

[{"title":"Python 异步编程实战:从 asyncio 到 aiohttp","year":2024,"category":"后端开发"},{"title":"2024 年 Python 性能优化指南","year":2024,"category":"后端开发"},{"title":"JavaScript 异步编程:Promise 与 async/await","year":2023,"category":"前端开发"},{"title":"2023 年前端框架对比:React vs Vue vs Angular","year":2023,"category":"前端开发"},{"title":"Go 语言微服务实战:gRPC 与 Kubernetes","year":2024,"category":"后端开发"},{"title":"Rust 系统编程:内存安全与零成本抽象","year":2023,"category":"系统编程"},{"title":"Python 机器学习入门:从 NumPy 到 PyTorch","year":2024,"category":"人工智能"},{"title":"2024 年云原生技术趋势:Service Mesh 与 eBPF","year":2024,"category":"云原生"},{"title":"数据库选型指南:PostgreSQL vs MySQL vs MongoDB","year":2023,"category":"数据库"},{"title":"Python 爬虫开发:Scrapy 与 Playwright 对比","year":2024,"category":"后端开发"}]

查询统一用:“Python 异步编程”


策略 1:相似度检索(Similarity Search)

原理

最基础的检索方式。把查询文本转成向量,在向量库里找最相似的 K 个文档。

results=vectorstore.similarity_search("Python 异步编程",k=4)

实验结果

召回 4 条,覆盖 3 个类别:

排名年份类别标题
12024云原生2024 年云原生技术趋势:Service Mesh 与 eBPF
22023前端开发2023 年前端框架对比:React vs Vue vs Angular
32024后端开发Python 爬虫开发:Scrapy 与 Playwright 对比
42024后端开发2024 年 Python 性能优化指南

分析

  • ✅ 简单直接,一行代码搞定
  • ❌ 结果集中在少数类别(后端开发出现 2 次)
  • ❌ 可能遗漏其他相关角度的内容

注意:排名第一的是"云原生"文章,这看起来有点反直觉。原因是 BGE 模型从语义角度认为这篇文章和查询有一定关联(都涉及"技术趋势"和"服务"概念),但对我们人类来说明显不够精准。这正是为什么要用多种策略组合的原因。


策略 2:MMR(Maximum Marginal Relevance)

原理

MMR 的核心公式:

MMR = λ × Sim(query, di) - (1-λ) × max(Sim(di, dj))
  • 第一项:文档 di 和查询的相关性(越大越好)
  • 第二项:文档 di 和已选文档的相似度(越小越好,保证多样性)
  • λ(lambda_mult):平衡参数,0.5 表示相关性和多样性各占一半
retriever=vectorstore.as_retriever(search_type="mmr",search_kwargs={"k":4,"lambda_mult":0.5,"fetch_k":20},)

fetch_k=20表示先从 20 个候选中筛选,再用 MMR 从中选 4 个。候选池越大,多样性越好。

实验结果

召回 4 条,覆盖4 个类别

排名年份类别标题
12024云原生2024 年云原生技术趋势:Service Mesh 与 eBPF
22024后端开发Python 爬虫开发:Scrapy 与 Playwright 对比
32023系统编程Rust 系统编程:内存安全与零成本抽象
42023数据库数据库选型指南:PostgreSQL vs MySQL vs MongoDB

对比分析

指标相似度检索MMR
覆盖类别数34
类别列表后端开发、云原生、前端开发后端开发、云原生、系统编程、数据库
特点集中在少数类别更分散、更多样

MMR 参数调优

# 只追求相关性search_kwargs={"k":4,"lambda_mult":1.0}# 等同于相似度检索# 只追求多样性search_kwargs={"k":4,"lambda_mult":0.0}# 结果可能和查询不太相关# 平衡两者(推荐)search_kwargs={"k":4,"lambda_mult":0.5,"fetch_k":20}

策略 3:相似度阈值过滤

原理

只保留相似度分数(距离)超过阈值的结果,低于阈值的直接丢弃。

重要认知:Chroma 返回的是距离(distance),不是相似度分数。距离越小表示越相似。

# 先查看距离分布results_with_score=vectorstore.similarity_search_with_score(query,k=10)fordoc,scoreinresults_with_score:print(f"距离={score:.4f}|{doc.metadata['title']}")

距离分布实测

距离=0.8652 | 2024 年云原生技术趋势:Service Mesh 与 eBPF 距离=0.8764 | 2023 年前端框架对比:React vs Vue vs Angular 距离=0.8833 | Python 爬虫开发:Scrapy 与 Playwright 对比 距离=0.8857 | 2024 年 Python 性能优化指南 距离=0.8906 | Python 机器学习入门:从 NumPy 到 PyTorch 距离=0.9019 | Rust 系统编程:内存安全与零成本抽象 距离=0.9024 | Python 异步编程实战:从 asyncio 到 aiohttp 距离=0.9145 | JavaScript 异步编程:Promise 与 async/await 距离=0.9147 | 数据库选型指南:PostgreSQL vs MySQL vs MongoDB 距离=0.9481 | Go 语言微服务实战:gRPC 与 Kubernetes

手动阈值过滤

threshold=0.89filtered=[(doc,score)fordoc,scoreinresults_with_scoreifscore<=threshold]# 结果:4 条(前 4 个距离 <= 0.89)

分析

  • ✅ 能剔除明显不相关的结果(如 Go 语言文章距离 0.9481)
  • ⚠️ 阈值设定需要实验:设太高可能一条都没有,设太低等于没过滤
  • 💡建议:先跑一批查询看距离分布,再设定阈值

策略 4:Self-Query(查询解析 + 元数据过滤)

原理

用户查询往往不是纯语义问题,而是带有明确条件的:

  • 2024 年关于Python的文章” → year=2024, tags=Python
  • 后端开发类别的文章” → category=后端开发
  • 2023 年前端相关的文章” → year=2023, category=前端开发

Self-Query 的核心流程:

自然语言查询 → 解析器 → 结构化过滤条件 → 元数据过滤 → 向量检索

解析器实现

生产环境可以用 LLM(如 LangChain 的 SelfQueryRetriever)做解析,这里用规则解析器演示核心逻辑:

defparse_query(query:str)->dict:filters={}semantic=query# 提取年份ifmatch:=re.search(r'(20\d{2})\s*年',query):filters["year"]=int(match.group(1))# 提取类别forcatin["后端开发","前端开发","系统编程",...]:ifcatinquery:filters["category"]=cat# 提取标签fortagin["Python","JavaScript","Go",...]:iftaginquery:filters["tags"]=tagreturn{"semantic_query":semantic,"filters":filters}

实验结果

查询 1:「2024 年关于 Python 的文章」

解析结果: 语义查询:Python 过滤条件:{'year': 2024, 'tags': 'Python'} 元数据过滤后剩余 4 篇: - Python 异步编程实战:从 asyncio 到 aiohttp - 2024 年 Python 性能优化指南 - Python 机器学习入门:从 NumPy 到 PyTorch - Python 爬虫开发:Scrapy 与 Playwright 对比

查询 2:「后端开发类别的文章」

解析结果: 语义查询:后端开发 过滤条件:{'category': '后端开发'} 元数据过滤后剩余 4 篇: - Python 异步编程实战:从 asyncio 到 aiohttp - 2024 年 Python 性能优化指南 - Go 语言微服务实战:gRPC 与 Kubernetes - Python 爬虫开发:Scrapy 与 Playwright 对比

查询 3:「2023 年前端相关的文章」

解析结果: 语义查询:前端 过滤条件:{'year': 2023} 元数据过滤后剩余 4 篇: - JavaScript 异步编程:Promise 与 async/await 深度解析 - 2023 年前端框架对比:React vs Vue vs Angular - Rust 系统编程:内存安全与零成本抽象 - 数据库选型指南:PostgreSQL vs MySQL vs MongoDB

分析

  • ✅ 精准响应用户的明确条件(时间、类别、标签)
  • ✅ 先过滤再检索,大幅减少向量比较的范围
  • ⚠️ 解析器质量决定效果(规则解析 vs LLM 解析)

生产环境用 LLM 解析

fromlangchain.retrievers.self_query.baseimportSelfQueryRetriever self_query_retriever=SelfQueryRetriever.from_llm(llm=llm,vectorstore=vectorstore,document_contents="技术博客文章",metadata_field_info=[...],# 定义元数据字段)results=self_query_retriever.invoke("2024 年关于 Python 的文章")

注:LangChain 1.2.16 的社区包中 SelfQueryRetriever 的模块位置可能有变化,请根据实际安装的版本调整导入路径。


四种策略对比总结

策略适用场景核心参数注意点
相似度检索通用场景,追求最高相关性k结果可能重复
MMR需要多角度回答lambda_mult,fetch_k参数需调优
阈值过滤质量要求高,宁可少不可错score_threshold需先实验确定阈值
Self-Query查询含时间/类别等明确条件解析器质量可用规则或 LLM 解析

组合使用建议

真正的生产环境中,组合使用效果更佳:

用户查询 ↓ Self-Query 解析 → 元数据过滤(缩小范围) ↓ 向量检索 → MMR(保证多样性) ↓ 阈值过滤(剔除低质量) ↓ Top-K 结果 → LLM 生成回答
# 组合示例retriever=vectorstore.as_retriever(search_type="mmr",search_kwargs={"k":5,"lambda_mult":0.5,"fetch_k":50,"filter":{"year":2024,"category":"后端开发"}# Self-Query 解析出的条件})

完整代码

本文的完整代码已开源:

https://github.com/chendongqi/llm-in-action/tree/main/07-retrieval-strategies

核心文件:

  • retrieval_strategies.py— 四种检索策略的完整对比实验
  • data/sample_articles.json— 10 篇测试文章数据

小结

本文通过代码实验对比了 4 种检索策略:

  1. 相似度检索— 简单直接,适合通用场景
  2. MMR— 用 λ 参数平衡相关性和多样性,解决结果重复问题
  3. 阈值过滤— 通过距离分布设定阈值,剔除低质量结果
  4. Self-Query— 把自然语言解析成结构化过滤条件,精准响应限定查询

关键认知:没有最好的检索策略,只有最适合当前查询的策略。组合使用 Self-Query + MMR + 阈值过滤,才能构建一个既精准又全面的检索系统。


参考资料

  • LangChain Retrievers 文档
  • MMR 算法论文:Maximal Marginal Relevance
  • Self-Query Retriever 指南

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询