Spring AI 学习篇(八)| RAG效果优化第一弹:检索优化
- 一、本章核心学习目标
- 二、前置知识准备
- 三、为什么基础RAG需要检索优化?
-
- [1. 纯向量检索的语义偏差](#1. 纯向量检索的语义偏差)
- [2. 用户查询的模糊性与口语化](#2. 用户查询的模糊性与口语化)
- [3. 检索结果的冗余与噪声](#3. 检索结果的冗余与噪声)
- [4. 单轮检索的信息不完整性](#4. 单轮检索的信息不完整性)
- 四、优化技术一:混合检索(效果提升20-30%)⭐⭐⭐⭐⭐
-
- [1. 混合检索的工作原理](#1. 混合检索的工作原理)
- [2. Spring AI 1.0原生混合检索实现](#2. Spring AI 1.0原生混合检索实现)
-
- [(1) 启用混合检索](#(1) 启用混合检索)
- [(2) 混合权重调优指南](#(2) 混合权重调优指南)
- [3. 不支持内置稀疏向量的模型怎么办?](#3. 不支持内置稀疏向量的模型怎么办?)
- 五、优化技术二:查询重写(效果提升15-20%)⭐⭐⭐⭐
-
- [1. 为什么需要查询重写?](#1. 为什么需要查询重写?)
- [2. Spring AI实现查询重写](#2. Spring AI实现查询重写)
- [3. 集成到RAG系统中](#3. 集成到RAG系统中)
- [4. 高级查询重写策略](#4. 高级查询重写策略)
- 六、优化技术三:检索结果后处理(效果提升10-15%)
-
- [1. 相似度阈值过滤](#1. 相似度阈值过滤)
- [2. 结果去重](#2. 结果去重)
- [3. 元数据过滤](#3. 元数据过滤)
- 七、优化技术四:多轮检索(效果提升5-10%)
-
- [1. 多轮检索的工作原理](#1. 多轮检索的工作原理)
- [2. Spring AI实现多轮检索](#2. Spring AI实现多轮检索)
- 八、检索效果量化评估
-
- [1. 核心评估指标](#1. 核心评估指标)
- [2. 构建评估数据集](#2. 构建评估数据集)
- [3. 自动评估脚本](#3. 自动评估脚本)
- 九、企业级最佳实践
- 十、常见坑与解决方案
-
- [1. ❌ 混合检索权重设置不当](#1. ❌ 混合检索权重设置不当)
- [2. ❌ 查询重写过度](#2. ❌ 查询重写过度)
- [3. ❌ 相似度阈值设置过高](#3. ❌ 相似度阈值设置过高)
- [4. ❌ 多轮检索导致性能下降](#4. ❌ 多轮检索导致性能下降)
- 十一、本章总结与下章预告
- 十二、课后练习
一、本章核心学习目标
学完本章,你将能够:
- 理解基础RAG检索阶段的核心痛点与局限性
- 掌握混合检索技术,结合向量检索与关键词检索的优势
- 实现查询重写,解决用户模糊查询的问题
- 掌握检索结果过滤、去重与排序的高级技巧
- 实现多轮检索与上下文扩充
- 建立量化的检索效果评估体系
- 将基础RAG的检索准确率提升30%以上
二、前置知识准备
- 已经完成第7篇的学习,成功实现了基础RAG系统
- 理解向量相似度检索的原理与局限性
- 熟练使用Spring AI
VectorStore与ChatClient - 掌握提示词工程的基本方法
三、为什么基础RAG需要检索优化?
我们在第7篇实现的基础RAG系统已经可以工作,但它存在4个致命的缺陷,导致实际场景中准确率往往不足60%:
1. 纯向量检索的语义偏差
向量检索擅长捕捉语义相似性 ,但不擅长精确关键词匹配。
例如:用户搜索"Spring AI 1.0的Maven依赖配置"
- 纯向量检索可能会返回"Spring Boot依赖配置"的相关内容
- 但会漏掉包含精确关键词"spring-ai-bom"的最相关文档
2. 用户查询的模糊性与口语化
用户的提问往往是模糊、口语化的,不适合直接用于检索。
例如:用户问"这个东西怎么用啊?"
- 没有上下文的情况下,检索系统完全不知道"这个东西"指的是什么
- 直接用这个问题检索,会返回大量无关结果
3. 检索结果的冗余与噪声
基础RAG会返回Top K个最相似的文档,但其中往往包含重复、无关或低质量的内容。
例如:同一个文档被切成多个分块,全部被检索出来,导致上下文重复
4. 单轮检索的信息不完整性
很多复杂问题需要多轮检索才能获取足够的信息,单轮检索往往只能找到部分答案。
预告式提及:检索优化是提升RAG效果性价比最高的环节。本章我们会解决前4个问题,下一章我们会学习重排序和上下文压缩,进一步提升生成阶段的效果。
四、优化技术一:混合检索(效果提升20-30%)⭐⭐⭐⭐⭐
混合检索是目前最有效的检索优化技术之一。它的核心思想是:同时使用向量检索(语义匹配)和关键词检索(精确匹配),然后将两者的结果合并,取长补短。
1. 混合检索的工作原理
用户查询 → 向量检索(返回Top 20) → 结果合并 → 取交集/并集 → 排序 → 返回Top 5
→ 关键词检索(返回Top 20)
- 向量检索:捕捉语义相似的内容,擅长处理同义词、近义词和语义转述
- 关键词检索:捕捉精确匹配的内容,擅长处理专业术语、产品名称和代码片段
- 两者结合:准确率比单一检索方式提升20-30%
2. Spring AI 1.0原生混合检索实现
Spring AI 1.0已经内置了对混合检索的支持,并且BGE-M4等新一代嵌入模型已经内置了稀疏向量支持,不需要单独部署BM25服务。
(1) 启用混合检索
在SearchRequest中启用混合检索(以 Spring AI 1.0 实际 API 为准):
java
// 基础向量检索(之前的写法)
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(5)
.build()
);
// 混合检索(优化后的写法,具体配置方式以实际向量数据库 API 为准)
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(5)
// 混合检索的具体配置取决于向量数据库实现
.build()
);
(2) 混合权重调优指南
| 场景 | 推荐向量权重 | 推荐关键词权重 |
|---|---|---|
| 通用文档、技术文档 | 0.7 | 0.3 |
| 代码文档、API文档 | 0.5 | 0.5 |
| 产品手册、说明书 | 0.6 | 0.4 |
| 新闻、资讯 | 0.8 | 0.2 |
最佳实践:中文场景推荐使用0.7:0.3的权重比。
3. 不支持内置稀疏向量的模型怎么办?
如果你使用的是不支持内置稀疏向量的旧模型(如BGE-M3),Spring AI也支持集成独立的BM25检索:
java
// 集成BM25关键词检索(适用于旧模型)
List<Document> bm25Results = bm25Store.search(query, 20);
List<Document> vectorResults = vectorStore.similaritySearch(query, 20);
// 合并结果并去重
Set<String> seenIds = new HashSet<>();
List<Document> mergedResults = new ArrayList<>();
for (Document doc : Stream.concat(vectorResults.stream(), bm25Results.stream()).toList()) {
if (seenIds.add(doc.getId())) {
mergedResults.add(doc);
}
}
// 取前5个结果
return mergedResults.stream().limit(5).toList();
五、优化技术二:查询重写(效果提升15-20%)⭐⭐⭐⭐
查询重写是第二有效的检索优化技术。它的核心思想是:让大模型先将用户的模糊、口语化查询改写成更清晰、更适合检索的查询语句。
1. 为什么需要查询重写?
用户的查询往往存在以下问题:
- 模糊不清:"这个怎么配置?"
- 口语化:"Spring AI咋对接DeepSeek啊?"
- 指代不明:"它的最新版本是多少?"
- 包含冗余信息:"我想问一下,那个Spring AI 1.0版本的,它的Maven依赖应该怎么写啊?"
2. Spring AI实现查询重写
提示 :Spring AI 提供了内置的
RewriteQueryTransformer。本章采用手动构建方式,帮助你理解查询重写的原理。
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class QueryRewriterService {
private final ChatClient chatClient;
public QueryRewriterService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
/**
* 重写用户查询,使其更适合检索
*/
public String rewriteQuery(String userQuery) {
String prompt = """
你是一个专业的查询重写专家。请将用户的查询改写成一个更清晰、更简洁、更适合检索的查询语句。
要求:
1. 去除口语化、冗余的表达
2. 补充必要的上下文信息
3. 使用更专业的术语
4. 只返回重写后的查询语句,不要任何解释
5. 如果用户查询已经很清晰,直接返回原查询
例如:
用户:我想问一下,那个Spring AI 1.0版本的,它的Maven依赖应该怎么写啊?
重写:Spring AI 1.0 Maven依赖配置
用户:这个怎么配置?
重写:Spring AI DeepSeek对接配置
用户查询:{userQuery}
""";
return chatClient.prompt()
.user(prompt.replace("{userQuery}", userQuery))
.call()
.content();
}
}
3. 集成到RAG系统中
java
// 在RagService的chat方法中添加查询重写步骤
public String chat(String query) {
// 第一步:重写用户查询
String rewrittenQuery = queryRewriterService.rewriteQuery(query);
// 第二步:使用重写后的查询进行混合检索
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.builder().query(rewrittenQuery)
.topK(20)
.build()
);
// 后续步骤不变...
}
4. 高级查询重写策略
对于复杂问题,可以使用更高级的重写策略:
- 问题分解:将复杂问题分解为多个简单问题,分别检索
- 关键词提取:提取查询中的核心关键词,用于关键词检索
- 意图识别:识别用户的真实意图,调整检索策略
六、优化技术三:检索结果后处理(效果提升10-15%)
检索返回的原始结果往往包含冗余、噪声和低质量的内容,需要进行后处理。
1. 相似度阈值过滤
过滤掉相似度太低的结果,避免引入无关信息:
java
List<Document> filteredResults = vectorStore.similaritySearch(
SearchRequest.builder().query(query)
.topK(20)
.similarityThreshold(0.7)
.build()
);
阈值调优指南:
- 0.8以上:高置信度,几乎没有无关内容
- 0.7-0.8:中等置信度,可能有少量无关内容
- 0.6-0.7:低置信度,不建议使用
2. 结果去重
去除重复的文档分块:
java
Set<String> seenContents = new HashSet<>();
List<Document> uniqueResults = new ArrayList<>();
for (Document doc : filteredResults) {
String content = doc.getContent().trim();
if (seenContents.add(content)) {
uniqueResults.add(doc);
}
}
3. 元数据过滤
使用元数据进一步过滤结果,比如只返回特定分类或特定时间的文档:
java
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder().query(query)
.topK(20)
.filterExpression("category == 'Spring AI' && uploadTime > 1704067200000")
.build()
);
七、优化技术四:多轮检索(效果提升5-10%)
对于复杂问题,单轮检索往往无法获取足够的信息,可以进行多轮检索。
1. 多轮检索的工作原理
用户查询 → 第一轮检索 → 分析检索结果 → 判断是否需要补充检索 → 第二轮检索 → 合并结果
2. Spring AI实现多轮检索
java
public List<Document> multiRoundSearch(String query) {
// 第一轮检索
List<Document> firstRoundResults = vectorStore.similaritySearch(
SearchRequest.builder().query(query)
.topK(10)
.build()
);
// 分析第一轮结果是否足够回答问题
boolean needMoreInfo = needMoreInformation(query, firstRoundResults);
if (!needMoreInfo) {
return firstRoundResults;
}
// 生成补充查询
String supplementaryQuery = generateSupplementaryQuery(query, firstRoundResults);
// 第二轮检索
List<Document> secondRoundResults = vectorStore.similaritySearch(
SearchRequest.builder().query(supplementaryQuery)
.topK(10)
.build()
);
// 合并结果并去重
Set<String> seenIds = new HashSet<>();
List<Document> mergedResults = new ArrayList<>();
for (Document doc : Stream.concat(firstRoundResults.stream(), secondRoundResults.stream()).toList()) {
if (seenIds.add(doc.getId())) {
mergedResults.add(doc);
}
}
return mergedResults;
}
八、检索效果量化评估
不要凭感觉判断检索效果,一定要建立量化的评估体系。
1. 核心评估指标
| 指标 | 计算公式 | 含义 | 目标值 |
|---|---|---|---|
| 准确率@K | 前K个结果中相关结果的数量 / K | 检索结果的精确性 | >80% |
| 召回率@K | 前K个结果中相关结果的数量 / 所有相关结果的数量 | 检索结果的完整性 | >70% |
| MRR | 第一个相关结果位置的倒数的平均值 | 相关结果的排序质量 | >0.7 |
2. 构建评估数据集
准备一个包含100-200个问题的评估数据集,每个问题标注对应的正确文档ID。
json
[
{
"query": "Spring AI 1.0稳定版发布于哪一年?",
"relevant_doc_ids": ["doc1", "doc2"]
},
{
"query": "如何在Spring AI中对接DeepSeek?",
"relevant_doc_ids": ["doc3", "doc4", "doc5"]
}
]
3. 自动评估脚本
编写一个自动评估脚本,定期测试检索效果:
java
public void evaluateRetrieval() {
List<EvaluationItem> items = loadEvaluationDataset();
int total = items.size();
int correctAt5 = 0;
double mrr = 0.0;
for (EvaluationItem item : items) {
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder().query(item.getQuery()).topK(5).build());
List<String> resultIds = results.stream().map(Document::getId).toList();
// 计算准确率@5
long relevantCount = resultIds.stream().filter(item.getRelevantDocIds()::contains).count();
if (relevantCount > 0) {
correctAt5++;
}
// 计算MRR
for (int i = 0; i < resultIds.size(); i++) {
if (item.getRelevantDocIds().contains(resultIds.get(i))) {
mrr += 1.0 / (i + 1);
break;
}
}
}
double precisionAt5 = (double) correctAt5 / total;
double averageMrr = mrr / total;
System.out.println("准确率@5: " + precisionAt5);
System.out.println("MRR: " + averageMrr);
}
九、企业级最佳实践
1. 优先实现混合检索和查询重写。 这两个技术的投入产出比最高------混合检索改一行代码,查询重写加一个 Service,合计提升检索准确率 30%+。不要一上来就上重排序。
2. 逐步迭代,每次只改一个变量。 不要同时加混合检索、查询重写、阈值过滤。每次只开启一个优化,跑一遍评估数据集,确认效果正向后再叠下一个。
3. 建立评估数据集并持续维护。 准备至少 20 个标准问题 + 预期答案。每次调整参数后用脚本自动跑评估。凭"感觉"调参数不可靠。
4. 监控线上检索质量。 生产环境中记录检索召回数、空结果率、用户追问率。第15篇会专门讲可观测性方案。
十、常见坑与解决方案
1. ❌ 混合检索权重设置不当
问题 :关键词权重太高,导致语义匹配效果差;或者向量权重太高,导致精确匹配效果差
解决方案:从0.7:0.3的默认权重开始,根据评估结果逐步调整
2. ❌ 查询重写过度
问题 :大模型重写后的查询改变了用户的原意
解决方案:
- 在提示词中明确要求"不要改变用户的原意"
- 保留原查询,同时使用原查询和重写后的查询进行检索
- 对重写后的结果进行校验,如果和原查询差异太大,使用原查询
3. ❌ 相似度阈值设置过高
问题 :过滤掉了很多相关的结果,导致召回率低
解决方案:从0.7的默认阈值开始,根据评估结果逐步调整
4. ❌ 多轮检索导致性能下降
问题 :多轮检索增加了响应时间,影响用户体验
解决方案:
- 限制多轮检索的次数(最多2轮)
- 异步执行多轮检索
- 对检索结果进行缓存
十一、本章总结与下章预告
本章总结
- 基础RAG的检索阶段存在语义偏差、查询模糊、结果冗余和信息不完整四大问题
- 混合检索结合向量检索和关键词检索的优势,是效果提升最明显的优化技术
- 查询重写将用户的模糊查询改写成更适合检索的查询语句
- 检索结果后处理过滤掉冗余和噪声内容,提升结果质量
- 多轮检索补充单轮检索的信息不足
- 建立量化的评估体系,用数据说话,不要凭感觉优化
预告式提及:经过本章的优化,我们的检索准确率已经提升了30%以上。但检索返回的结果中仍然包含很多无关的信息。下一章我们将学习RAG效果优化第二弹:重排序与上下文压缩,进一步提升生成阶段的效果。
下章预告
下一章我们将深入学习RAG效果优化的第二部分:重排序与上下文压缩。你将学会:
- 为什么需要重排序?重排序模型的工作原理
- Spring AI集成BGE Reranker v3实现重排序
- 上下文压缩技术:只保留最相关的信息
- 分块召回与合并策略
- 高级RAG架构:检索-重排序-生成三阶段流水线
十二、课后练习
- 为你的RAG系统添加混合检索功能,对比开启前后的检索效果差异
- 实现查询重写功能,测试不同提示词对重写效果的影响
- 添加相似度阈值过滤和结果去重功能
- 构建一个包含20个问题的评估数据集,量化评估优化前后的准确率和MRR
- 尝试调整混合检索的权重和相似度阈值,找到最适合你的知识库的配置