承上 :上一篇我们用
QuestionAnswerAdvisor一行代码跑通了RAG,但是在测试过程中发现一堆Bad Case:问"怎么退钱"搜出来的是"退款到账时间",问"加班费怎么算"搜出来的是"加班调休规定"。今天,我们要解决RAG最头疼的问题------检索不准。
1. 问题诊断:为什么检索不准?
1.1. 三个典型Bad Case
sql
Case 1:口语化表达 vs 文档术语
用户问:"怎么退钱?"
文档写:"退款流程"
检索结果:"退款到账时间为3-5个工作日"(不相关)
Case 2:模糊问题 vs 精确关键词
用户问:"加班费怎么算?"
文档写:"加班调休"
检索结果:"加班2小时以上可申请调休"(部分相关,但不是用户想要的)
Case 3:多义词
用户问:"苹果最新款"
文档里既有"苹果公司产品"又有"苹果水果"
检索结果:混在一起(50%不相关)
1.2. 根本原因
向量检索的本质是语义相似度匹配 ,但"口语化表达"和"文档术语"之间存在语义鸿沟。
markdown
用户语言:怎么退钱? → 口语化
文档语言:退款申请流程 → 书面化
↓
向量距离可能很远,检索不到!
2. 优化一:Query改写
2.1. 核心思想
在检索之前,先用AI把用户的问题"翻译"成文档中可能出现的表达方式。
markdown
用户问:"怎么退钱?"
↓ AI改写
改写后:"退款申请流程 退款条件 退款方式"
↓ 用改写后的Query去检索
检索结果:"第三章 退款流程:用户可在订单完成后7天内申请退款..."
2.2. 实现QueryTransformer
在2.0版本中,Spring AI提供了一套完整的 预检索(Pre-Retrieval) 优化体系,核心接口是 QueryTransformer。
QueryTransformer 是Spring AI 2.0中专门用于在检索之前对用户查询进行变换 的接口。它在 RetrievalAugmentationAdvisor 内部自动执行,不需要我们手动管理Advisor顺序。
arduino
用户查询:"怎么退钱?"
│
▼
QueryTransformer.transform("怎么退钱?")
│
▼
改写后查询:"退款申请流程 退款条件 退款方式"
│
▼
用改写后的查询去向量数据库检索
│
▼
精准命中文档块!
| 实现类 | 功能 | 适用场景 |
|---|---|---|
RewriteQueryTransformer |
用AI改写用户查询 | 口语化→书面化,推荐首选 |
CompressionQueryTransformer |
结合历史对话压缩查询 | 多轮对话场景 |
TranslationQueryTransformer |
多语言翻译 | 跨语言检索 |
本篇重点讲最常用的 RewriteQueryTransformer。
2.3. 配置RewriteQueryTransformer
ini
package com.yunxi.ai.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.preretrieval.query.transformation.QueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueryTransformerConfig {
/**
* 配置 RewriteQueryTransformer Bean
* 该组件用于将用户模糊、口语化的查询重写为更适合向量检索的结构化查询
*/
@Bean
public QueryTransformer rewriteQueryTransformer(ChatModel chatModel) {
// 构建 ChatClient,建议设置较低的 temperature 以保证重写结果的稳定性
ChatClient.Builder builder = ChatClient.builder(chatModel);
// 定义严格的提示词模板
String rewritePrompt = """
你是一个查询优化助手。你的任务是将用户的原始查询重写为更适合向量数据库检索的形式。
规则:
1. 严禁改变原意:必须严格保留用户查询中的核心关键词和意图。
2. 严禁回答问题:不要提供答案,只输出重写后的查询语句。
3. 去噪与规范化:去除语气词、冗余字符,将口语转化为书面语。
4. 补全上下文:如果查询指代不明(如"它是什么"),请根据常识补全主语,但不要引入无关信息。
5. 输出格式:只返回重写后的查询文本,不要包含任何解释或前缀
原始查询: {query}
重写后的查询: {target}
""";
PromptTemplate promptTemplate = new PromptTemplate(rewritePrompt);
return RewriteQueryTransformer.builder()
.chatClientBuilder(builder)
.promptTemplate(promptTemplate) // 使用自定义提示词
.build();
}
@Bean
public RetrievalAugmentationAdvisor ragAdvisor(VectorStore vectorStore, QueryTransformer queryTransformer) {
// 构建文档检索器
VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.5)
.topK(3)
.build();
// 2. 构建 Advisor 并注入 Transformer
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(retriever)
.queryTransformers(queryTransformer) // 关键:应用查询转换器
.build();
}
}
配置解读:
scss
RetrievalAugmentationAdvisor 执行流程:
│
├── 1. queryTransformers.transform(原始查询)
│ → "怎么退钱?" → "退款申请流程 退款条件 退款方式"
│
├── 2. documentRetriever.retrieve(改写后查询)
│ → 向量检索 Top 3
│
└── 3. 注入Prompt → AI生成回答
2.4. 增强Service
arduino
package com.yunxi.ai.service;
import com.yunxi.ai.config.MyLoggerAdvisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ChatV2Service {
private final ChatClient chatClient;
public ChatV2Service(ChatClient.Builder builder, ChatMemory chatMemory, RetrievalAugmentationAdvisor retrievalAugmentationAdvisor) {
this.chatClient = builder
.defaultAdvisors(new MyLoggerAdvisor())
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.defaultAdvisors(retrievalAugmentationAdvisor)
.build();
}
/**
* 同步接口
* @param message
* @return
*/
public String chat(String message) {
return chatClient.prompt()
.system("你是一个专业的助手,可以回答用户的问题")
.user(message)
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, "TEST"))
.call()
.content();
}
}
测试结果单测:
ini
Query originalQuery = new Query(prompt);
Query transformedQuery = queryTransformer.transform(originalQuery);
transformedQuery.text();
2.5. Query改写效果对比
改写前:

改写后:

3. 优化二:结果重排序(Reranker)
在 Spring AI 的 RAG(检索增强生成)应用中,结果重排序(Reranker) 是一个关键的优化环节。它像一位"精筛师",在系统从向量数据库初步召回大量可能相关的文档后,通过一个专用的重排序模型(Rerank Model) ,对这些文档进行更精细的相关性打分、过滤和重新排序,最终把最相关的内容传递给大模型,从而提升最终答案的质量。
Spring AI Alibaba 提供的一个开箱即用的解决方案------RetrievalRerankAdvisor,就显得非常方便。
它是一个"顾问(Advisor) ",可以被注册到 ChatClient 中,自动在调用大模型前完成"检索→重排→过滤→组装"的整条链路。
核心组件
VectorStore: 负责第一步的"粗召回",从向量数据库中找出候选文档。RerankModel: 重排序模型接口,负责对候选文档进行打分,Spring AI Alibaba 默认集成了阿里云的 DashScope(百炼) 重排序模型。RetrievalRerankAdvisor: 作为胶水,将上述流程串联起来。
重排的实现在后面的SpringAIAlibab章节再详细写吧。
4. 优化三:混合检索(关键词 + 向量)
4.1. 向量检索的盲区
向量检索擅长找"语义相似",但对于精确匹配(如订单号、产品型号)表现差。
用户问:"ORD001状态"
向量检索:把ORD001当成普通词汇,可能搜出"订单状态说明"
关键词检索:精确匹配ORD001,直接找到该订单
4.2. 混合检索思路
markdown
用户查询
│
├──→ 向量检索(语义匹配)→ 得分:语义相似度
│
└──→ 关键词检索(精确匹配)→ 得分:BM25/关键词命中数
│
▼
融合两个分数,取加权平均
│
▼
最终排序结果
4.3. Spring AI中的混合检索
如果使用Elasticsearch作为向量数据库,Spring AI的ElasticsearchVectorStore原生支持混合检索。Redis Stack目前以向量检索为主,关键词检索需要单独配置。
限于篇幅,混合检索的完整代码不在本篇展开,会在知识库实战中结合实际场景演示。
5. 三种优化对比
| 优化方式 | 解决问题 | 成本 | 效果 |
|---|---|---|---|
| Query改写 | 口语化 vs 书面化 | 多一次AI调用 | ⭐⭐⭐⭐ |
| Reranker重排序 | 粗排精度不够 | 多N次AI调用 | ⭐⭐⭐⭐⭐ |
| 混合检索 | 精确匹配盲区 | 需搜索引擎配合 | ⭐⭐⭐⭐ |
组合使用:
css
Query改写 → 混合检索(粗排)→ Reranker(精排)→ Top 3 → 注入Prompt
6. 完整优化架构
css
用户问题:"怎么退钱?"
│
▼
┌─────────────────────┐
│ QueryTransformer │ → "退款申请流程 退款条件"
│ (Query改写) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ 混合检索(可选) │ → 向量检索 + 关键词检索
│ (粗排:召回Top 10) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ RerankerAdvisor │ → 对Top 5逐个AI打分 → 保留Top 3
│ (精排:重排序) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│QuestionAnswerAdvisor│ → 注入Prompt → 生成回答
│ (RAG问答) │
└─────────────────────┘
7. 本篇小结
这一篇我们解决了RAG的核心痛点------检索不准:
| 优化方式 | 原理 | 效果 |
|---|---|---|
| Query改写 | 用AI把口语化查询转成文档术语 | 解决表达方式不匹配 |
| Reranker重排序 | 对候选文档重新AI打分 | 解决粗排精度不够 |
| 混合检索 | 关键词+向量双路召回 | 解决精确匹配盲区 |
核心心法:
erlang
RAG的80%问题都出在检索环节,而不是生成环节。
检索不准 → AI看到的是不相关的资料 → 回答必然不准
检索精准 → AI看到的就是答案本身 → 回答自然准确
优化检索的投入产出比,远高于优化Prompt。
下一篇,我们要把前面学的所有RAG知识真正落地------面试知识库实战:我把自己收集的八股文喂给了AI,让它帮我模拟面试。
本文与DeepSeek协作完成