为什么检索策略很重要前面六篇文章我们搞定了文档分块、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 个文档。resultsvectorstore.similarity_search(Python 异步编程,k4)实验结果召回 4 条覆盖 3 个类别排名年份类别标题12024云原生2024 年云原生技术趋势Service Mesh 与 eBPF22023前端开发2023 年前端框架对比React vs Vue vs Angular32024后端开发Python 爬虫开发Scrapy 与 Playwright 对比42024后端开发2024 年 Python 性能优化指南分析✅ 简单直接一行代码搞定❌ 结果集中在少数类别后端开发出现 2 次❌ 可能遗漏其他相关角度的内容注意排名第一的是云原生文章这看起来有点反直觉。原因是 BGE 模型从语义角度认为这篇文章和查询有一定关联都涉及技术趋势和服务概念但对我们人类来说明显不够精准。这正是为什么要用多种策略组合的原因。策略 2MMRMaximum Marginal Relevance原理MMR 的核心公式MMR λ × Sim(query, di) - (1-λ) × max(Sim(di, dj))第一项文档 di 和查询的相关性越大越好第二项文档 di 和已选文档的相似度越小越好保证多样性λlambda_mult平衡参数0.5 表示相关性和多样性各占一半retrievervectorstore.as_retriever(search_typemmr,search_kwargs{k:4,lambda_mult:0.5,fetch_k:20},)fetch_k20表示先从 20 个候选中筛选再用 MMR 从中选 4 个。候选池越大多样性越好。实验结果召回 4 条覆盖4 个类别排名年份类别标题12024云原生2024 年云原生技术趋势Service Mesh 与 eBPF22024后端开发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_scorevectorstore.similarity_search_with_score(query,k10)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手动阈值过滤threshold0.89filtered[(doc,score)fordoc,scoreinresults_with_scoreifscorethreshold]# 结果4 条前 4 个距离 0.89分析✅ 能剔除明显不相关的结果如 Go 语言文章距离 0.9481⚠️ 阈值设定需要实验设太高可能一条都没有设太低等于没过滤建议先跑一批查询看距离分布再设定阈值策略 4Self-Query查询解析 元数据过滤原理用户查询往往不是纯语义问题而是带有明确条件的“2024 年关于Python的文章” → year2024, tagsPython“后端开发类别的文章” → category后端开发“2023 年前端相关的文章” → year2023, category前端开发Self-Query 的核心流程自然语言查询 → 解析器 → 结构化过滤条件 → 元数据过滤 → 向量检索解析器实现生产环境可以用 LLM如 LangChain 的 SelfQueryRetriever做解析这里用规则解析器演示核心逻辑defparse_query(query:str)-dict:filters{}semanticquery# 提取年份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_retrieverSelfQueryRetriever.from_llm(llmllm,vectorstorevectorstore,document_contents技术博客文章,metadata_field_info[...],# 定义元数据字段)resultsself_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 生成回答# 组合示例retrievervectorstore.as_retriever(search_typemmr,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 种检索策略相似度检索— 简单直接适合通用场景MMR— 用 λ 参数平衡相关性和多样性解决结果重复问题阈值过滤— 通过距离分布设定阈值剔除低质量结果Self-Query— 把自然语言解析成结构化过滤条件精准响应限定查询关键认知没有最好的检索策略只有最适合当前查询的策略。组合使用 Self-Query MMR 阈值过滤才能构建一个既精准又全面的检索系统。参考资料LangChain Retrievers 文档MMR 算法论文Maximal Marginal RelevanceSelf-Query Retriever 指南