📋 目录

方法代码分析
示例代码
java
@Test
public void testRag3(@Autowired VectorStore vectorStore,
@Autowired DashScopeChatModel dashScopeChatModel) {
// 1. 创建 ChatClient
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(SimpleLoggerAdvisor.builder().build())
.build();
// 2. 构建 RetrievalAugmentationAdvisor(核心组件)
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
// 2.1 文档检索器
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.0)
.vectorStore(vectorStore)
.build())
// 2.2 查询增强器(处理空上下文)
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("用户查询位于知识库之外。礼貌地告知用户您无法回答")
.build())
.build())
// 2.3 查询转换器 - 重写查询
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(dashScopeChatModel))
.targetSearchSystem("航空票务助手")
.build())
// 2.4 查询转换器 - 翻译查询
.queryTransformers(TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(dashScopeChatModel))
.targetLanguage("english")
.build())
// 2.5 文档后处理器
.documentPostProcessors((query, documents) -> {
System.out.println("Original query: " + query.text());
System.out.println("Retrieved documents: " + documents.size());
return documents;
})
.build();
// 3. 执行 RAG 查询
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user("我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?")
.call()
.content();
System.out.println(answer);
}
代码结构分析
testRag3 方法
├── ChatClient 构建
│ └── 配置默认 Advisor(日志记录)
│
├── RetrievalAugmentationAdvisor 构建
│ ├── documentRetriever(文档检索器)
│ │ └── VectorStoreDocumentRetriever
│ │ ├── similarityThreshold(0.0)
│ │ └── vectorStore
│ │
│ ├── queryAugmenter(查询增强器)
│ │ └── ContextualQueryAugmenter
│ │ ├── allowEmptyContext(false)
│ │ └── emptyContextPromptTemplate
│ │
│ ├── queryTransformers(查询转换器链)
│ │ ├── RewriteQueryTransformer(重写查询)
│ │ └── TranslationQueryTransformer(翻译查询)
│ │
│ └── documentPostProcessors(文档后处理器)
│ └── Lambda 函数(监控、日志)
│
└── RAG 查询执行
└── ChatClient.prompt().advisors().user().call()
核心组件详解
1. RetrievalAugmentationAdvisor
作用 :Spring AI 提供的高级 RAG Advisor,相比 QuestionAnswerAdvisor 提供了更多的定制化能力。
核心特性:
- ✅ 支持查询转换(Query Transformation)
- ✅ 支持查询增强(Query Augmentation)
- ✅ 支持文档后处理(Document Post Processing)
- ✅ 支持自定义文档检索器(Document Retriever)
- ✅ 支持空上下文处理(Empty Context Handling)
与 QuestionAnswerAdvisor 的区别:
| 特性 | QuestionAnswerAdvisor | RetrievalAugmentationAdvisor |
|---|---|---|
| 查询转换 | ❌ 不支持 | ✅ 支持多个转换器链 |
| 查询增强 | ❌ 不支持 | ✅ 支持 ContextualQueryAugmenter |
| 文档后处理 | ❌ 不支持 | ✅ 支持自定义后处理器 |
| 配置复杂度 | 简单 | 复杂但灵活 |
| 适用场景 | 基础 RAG | 高级 RAG、生产环境 |
2. VectorStoreDocumentRetriever
作用:从向量库中检索相关文档的检索器。
配置参数:
java
VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore) // 向量库实例
.similarityThreshold(0.0) // 相似度阈值(0.0 表示不过滤)
.topK(5) // 返回前 K 个结果(可选)
.filterExpression(filterExpression) // 元数据过滤表达式(可选)
.build()
参数说明:
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
vectorStore |
VectorStore |
向量库实例 | 必填 |
similarityThreshold |
double |
相似度阈值,低于此值的文档会被过滤 | 0.0 |
topK |
int |
返回前 K 个最相似的文档 | 未设置 |
filterExpression |
FilterExpression |
元数据过滤表达式 | null |
工作原理:
- 将用户查询转换为向量(使用 EmbeddingModel)
- 在向量库中搜索最相似的文档向量
- 根据
similarityThreshold过滤低相似度文档 - 根据
topK返回前 K 个结果 - 应用
filterExpression进行元数据过滤(如果提供)
3. ContextualQueryAugmenter
作用:处理检索结果为空的情况,决定是否继续回答或返回提示信息。
配置参数:
java
ContextualQueryAugmenter.builder()
.allowEmptyContext(false) // 是否允许空上下文
.emptyContextPromptTemplate(template) // 空上下文时的提示模板
.build()
参数说明:
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
allowEmptyContext |
boolean |
是否允许在检索结果为空时继续回答 | true |
emptyContextPromptTemplate |
PromptTemplate |
空上下文时的提示模板 | null |
行为模式:
allowEmptyContext |
检索结果为空时的行为 |
|---|---|
true |
继续使用 LLM 回答(可能产生幻觉) |
false |
使用 emptyContextPromptTemplate 返回提示信息 |
使用场景:
- ✅ 严格知识库模式 :
allowEmptyContext=false,确保只基于知识库回答 - ✅ 宽松模式 :
allowEmptyContext=true,允许 LLM 基于通用知识回答
4. RewriteQueryTransformer
作用:使用 LLM 重写用户查询,使其更适合检索系统。
配置参数:
java
RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel)) // ChatClient 构建器
.targetSearchSystem("航空票务助手") // 目标检索系统描述
.build()
工作原理:
- 接收原始用户查询
- 使用 LLM 根据
targetSearchSystem重写查询 - 返回优化后的查询用于向量检索
示例:
原始查询:"我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?"
重写后查询:"退票费用是多少?"
优势:
- ✅ 去除无关信息(如"心情不好")
- ✅ 提取核心查询意图
- ✅ 适配特定检索系统(如"航空票务助手")
5. TranslationQueryTransformer
作用:将用户查询翻译为目标语言,用于多语言知识库检索。
配置参数:
java
TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel)) // ChatClient 构建器
.targetLanguage("english") // 目标语言
.build()
参数说明:
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
chatClientBuilder |
ChatClient.Builder |
ChatClient 构建器,用于调用 LLM 进行翻译 | 必填 |
targetLanguage |
String |
目标语言(如 "english", "chinese", "spanish") | 必填 |
工作原理:
- 接收原始用户查询(可能是中文)
- 使用 LLM 将查询翻译为目标语言(如英文)
- 使用翻译后的查询进行向量检索
- 返回检索到的文档(可能是英文文档)
示例:
原始查询(中文):"退票需要多少费用?"
翻译后查询(英文):"What is the fee for ticket refund?"
检索结果:英文文档(如果知识库是英文的)
使用场景:
- ✅ 多语言知识库:知识库是英文的,但用户用中文提问
- ✅ 跨语言检索:支持不同语言之间的知识检索
- ✅ 国际化应用:为不同语言用户提供统一的知识库访问
注意事项:
- ⚠️ 翻译会增加 LLM 调用次数,可能影响响应时间
- ⚠️ 翻译质量会影响检索准确性
- ⚠️ 如果知识库和用户语言一致,不需要翻译
6. DocumentPostProcessors
作用:在检索到文档后、传递给 LLM 之前,对文档进行处理、过滤或转换。
配置方式:
java
.documentPostProcessors((query, documents) -> {
// 自定义处理逻辑
System.out.println("Original query: " + query.text());
System.out.println("Retrieved documents: " + documents.size());
// 可以过滤、排序、转换文档
return documents; // 返回处理后的文档列表
})
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
query |
Query |
原始查询对象,包含查询文本 |
documents |
List<Document> |
检索到的文档列表 |
常见用途:
1. 文档过滤
java
.documentPostProcessors((query, documents) -> {
// 过滤掉过短的文档
return documents.stream()
.filter(doc -> doc.getText().length() > 100)
.collect(Collectors.toList());
})
2. 文档排序
java
.documentPostProcessors((query, documents) -> {
// 按元数据中的优先级排序
return documents.stream()
.sorted((d1, d2) -> {
int p1 = (int) d1.getMetadata().getOrDefault("priority", 0);
int p2 = (int) d2.getMetadata().getOrDefault("priority", 0);
return Integer.compare(p2, p1); // 降序
})
.collect(Collectors.toList());
})
3. 文档去重
java
.documentPostProcessors((query, documents) -> {
// 根据文档 ID 去重
Set<String> seenIds = new HashSet<>();
return documents.stream()
.filter(doc -> {
String id = doc.getId();
return seenIds.add(id);
})
.collect(Collectors.toList());
})
4. 文档监控和日志
java
.documentPostProcessors((query, documents) -> {
// 记录检索统计信息
log.info("Query: {}, Retrieved: {} documents",
query.text(), documents.size());
// 记录文档详情
documents.forEach(doc -> {
log.debug("Document ID: {}, Similarity: {}",
doc.getId(), doc.getMetadata().get("similarity"));
});
return documents;
})
5. 文档增强
java
.documentPostProcessors((query, documents) -> {
// 为文档添加额外的元数据
return documents.stream()
.map(doc -> {
Map<String, Object> metadata = new HashMap<>(doc.getMetadata());
metadata.put("processed_at", LocalDateTime.now());
metadata.put("query_text", query.text());
return Document.builder()
.from(doc)
.metadata(metadata)
.build();
})
.collect(Collectors.toList());
})
执行流程与原理
完整执行流程
用户查询:"我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?"
↓
[1] Query Transformers(查询转换器链)
├─ RewriteQueryTransformer
│ └─ 重写查询:"退票费用是多少?"
│
└─ TranslationQueryTransformer
└─ 翻译查询:"What is the fee for ticket refund?"
↓
[2] Document Retriever(文档检索器)
├─ 将查询转换为向量
├─ 在向量库中搜索相似文档
├─ 应用 similarityThreshold 过滤
└─ 返回相关文档列表
↓
[3] Document Post Processors(文档后处理器)
├─ 记录日志
├─ 过滤/排序文档(可选)
└─ 返回处理后的文档
↓
[4] Query Augmenter(查询增强器)
├─ 检查检索结果是否为空
│ ├─ 如果为空且 allowEmptyContext=false
│ │ └─ 返回 emptyContextPromptTemplate 的提示
│ └─ 如果为空且 allowEmptyContext=true
│ └─ 继续执行(使用 LLM 通用知识)
└─ 如果有文档,构建增强后的 Prompt
↓
[5] LLM 生成回答
├─ 输入:用户查询 + 检索到的文档上下文
├─ 处理:LLM 基于上下文生成回答
└─ 输出:最终答案
↓
返回答案:"根据我们的政策,退票费用取决于舱位类型:
- 经济舱:75 美元
- 豪华经济舱:50 美元
- 商务舱:25 美元"
详细步骤说明
步骤 1:查询转换(Query Transformation)
目的:优化用户查询,使其更适合向量检索。
执行顺序:
-
RewriteQueryTransformer 先执行
- 输入:原始查询(包含无关信息)
- 输出:重写后的查询(提取核心意图)
-
TranslationQueryTransformer 后执行
- 输入:重写后的查询
- 输出:翻译后的查询
示例流程:
原始查询:"我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?"
↓ RewriteQueryTransformer
重写查询:"退票费用是多少?"
↓ TranslationQueryTransformer
翻译查询:"What is the fee for ticket refund?"
步骤 2:文档检索(Document Retrieval)
目的:从向量库中检索相关文档。
执行过程:
- 使用
EmbeddingModel将查询文本转换为向量 - 在
VectorStore中计算查询向量与文档向量的相似度 - 根据
similarityThreshold过滤低相似度文档 - 根据
topK返回前 K 个最相似的文档 - 应用
filterExpression进行元数据过滤(如果提供)
相似度计算:
- 使用余弦相似度(Cosine Similarity)或点积(Dot Product)
- 相似度范围:[-1, 1] 或 [0, 1]
similarityThreshold=0.0表示不过滤任何文档
步骤 3:文档后处理(Document Post Processing)
目的:对检索到的文档进行进一步处理。
执行时机:在文档检索之后、传递给 LLM 之前。
常见操作:
- 文档过滤(去除不相关文档)
- 文档排序(按优先级、相关性等)
- 文档去重(避免重复内容)
- 文档增强(添加元数据)
- 监控和日志(记录检索统计)
步骤 4:查询增强(Query Augmentation)
目的:处理空上下文情况,构建最终的 Prompt。
执行逻辑:
java
if (documents.isEmpty()) {
if (allowEmptyContext == false) {
// 返回空上下文提示
return emptyContextPromptTemplate.render();
} else {
// 继续使用 LLM 回答(可能产生幻觉)
return llm.call(userQuery);
}
} else {
// 构建增强后的 Prompt
Prompt prompt = buildPrompt(userQuery, documents);
return llm.call(prompt);
}
Prompt 构建:
System Message: "你是一个航空票务助手,基于以下文档回答问题:"
User Message:
"""
文档1:预订航班: 通过我们的网站或移动应用程序预订...
文档2:取消预订: 最晚在航班起飞前 48 小时取消...
用户问题:退票费用是多少?
"""
步骤 5:LLM 生成回答
目的:基于检索到的文档上下文生成最终答案。
执行过程:
- LLM 接收增强后的 Prompt(包含文档上下文和用户查询)
- LLM 分析文档内容,提取相关信息
- LLM 生成基于文档的回答
- 返回最终答案
使用场景
场景 1:企业知识库问答系统
需求:企业内部知识库,需要处理复杂的用户查询,支持查询优化和空上下文处理。
实现:
java
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.6)
.topK(5)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false) // 严格模式,只基于知识库回答
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("抱歉,我无法在知识库中找到相关信息。请咨询相关部门。")
.build())
.build())
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel))
.targetSearchSystem("企业知识库系统")
.build())
.documentPostProcessors((query, documents) -> {
// 记录检索日志
log.info("Query: {}, Retrieved: {} documents",
query.text(), documents.size());
return documents;
})
.build();
业务价值:
- ✅ 提高查询准确性(查询重写)
- ✅ 避免幻觉回答(空上下文处理)
- ✅ 支持审计和监控(文档后处理)
场景 2:多语言知识库系统
需求:知识库是英文的,但用户可能用中文、日文等多种语言提问。
实现:
java
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore) // 英文知识库
.build())
.queryTransformers(
// 先重写查询
RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel))
.targetSearchSystem("Product Documentation")
.build(),
// 再翻译为英文
TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel))
.targetLanguage("english")
.build()
)
.build();
业务价值:
- ✅ 支持多语言用户访问英文知识库
- ✅ 提高跨语言检索准确性
- ✅ 降低多语言知识库维护成本
场景 3:智能客服系统
需求:客服系统需要处理用户的情感化表达,提取核心问题,并确保回答基于知识库。
实现:
java
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.5)
.topK(3)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("抱歉,我无法找到相关信息。请转接人工客服。")
.build())
.build())
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel))
.targetSearchSystem("客服知识库")
.build())
.documentPostProcessors((query, documents) -> {
// 过滤掉过时的文档
return documents.stream()
.filter(doc -> {
String status = (String) doc.getMetadata().get("status");
return "active".equals(status);
})
.collect(Collectors.toList());
})
.build();
业务价值:
- ✅ 处理用户情感化表达(查询重写)
- ✅ 确保回答准确性(空上下文处理)
- ✅ 过滤过时信息(文档后处理)
场景 4:法律文档检索系统
需求:法律文档系统需要精确检索,支持查询优化和文档质量过滤。
实现:
java
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.7) // 高阈值,确保精确性
.topK(5)
.filterExpression(FilterExpressionBuilder.builder()
.eq("status", "有效")
.build())
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false) // 严格模式
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("未找到相关法律条文,请咨询专业律师。")
.build())
.build())
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(chatModel))
.targetSearchSystem("法律文档检索系统")
.build())
.documentPostProcessors((query, documents) -> {
// 按发布时间排序,优先返回最新文档
return documents.stream()
.sorted((d1, d2) -> {
String date1 = (String) d1.getMetadata().get("published_date");
String date2 = (String) d2.getMetadata().get("published_date");
return date2.compareTo(date1); // 降序
})
.collect(Collectors.toList());
})
.build();
业务价值:
- ✅ 确保法律条文的准确性
- ✅ 优先返回最新、有效的法律条文
- ✅ 避免引用过时或无效的法律条文
与其他方法的对比
java
package com.xushu.springai.rag;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import org.apache.el.lang.ExpressionBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.rag.Query;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.preretrieval.query.transformation.QueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.TranslationQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootTest
public class ChatClientRagTest {
ChatClient chatClient;
@BeforeEach
public void init(
@Autowired DashScopeChatModel chatModel,
@Autowired VectorStore vectorStore) {
Document doc = Document.builder()
.text("""
预订航班:
- 通过我们的网站或移动应用程序预订。
- 预订时需要全额付款。
- 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
""")
.build();
Document doc2 = Document.builder()
.text("""
取消预订:
- 最晚在航班起飞前 48 小时取消。
- 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
- 退款将在 7 个工作日内处理。
""")
.build();
List<Document> documents = List.of(doc, doc2);
// 存储向量(内部会自动向量化)
vectorStore.add(documents);
}
@Test
public void testRag(
@Autowired DashScopeChatModel dashScopeChatModel,
@Autowired VectorStore vectorStore) {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(SimpleLoggerAdvisor.builder().build())
.build();
String content = chatClient.prompt()
.user("退票需要多少费用?")
.advisors(
SimpleLoggerAdvisor.builder().build(),
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest.builder()
.topK(5)
.similarityThreshold(0.6)
.build()
).build()
)
.call()
.content();
System.out.println(content);
}
@Test
public void testRag2(
@Autowired DashScopeChatModel dashScopeChatModel,
@Autowired VectorStore vectorStore) {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(SimpleLoggerAdvisor.builder().build())
.build();
//FilterExpression基于元数据过滤搜索结果的参数
String content = chatClient.prompt()
.user("退票需要多少费用?")
.advisors(
SimpleLoggerAdvisor.builder().build(),
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest.builder()
.topK(5)
.similarityThreshold(0.1)
//.filterExpression()
.build()
).build()
)
.call()
.content();
System.out.println(content);
}
@Test
public void testRag3(@Autowired VectorStore vectorStore,
@Autowired DashScopeChatModel dashScopeChatModel) {
chatClient = ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(SimpleLoggerAdvisor.builder().build())
.build();
// 增强多
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
// 查 = QuestionAnswerAdvisor
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.0)
// .topK()
// .filterExpression()
.vectorStore(vectorStore)
.build())
// 检索为空时,allowEmptyContext=false返回提示 allowEmptyContext=true 正常回答
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.emptyContextPromptTemplate(PromptTemplate.builder().template("用户查询位于知识库之外。礼貌地告知用户您无法回答").build())
.build())
// 检索查询转换器
// 重写检索查询转换器
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(dashScopeChatModel))
.targetSearchSystem("航空票务助手")
.build())
// 翻译转换器
.queryTransformers(TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(dashScopeChatModel))
.targetLanguage("english")
.build())
// 检索后文档监控、操作
.documentPostProcessors((query, documents) -> {
System.out.println("Original query: " + query.text());
System.out.println("Retrieved documents: " + documents.size());
return documents;
})
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user("我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?")
.call()
.content();
System.out.println(answer);
}
@TestConfiguration
static class TestConfig {
@Bean
public VectorStore vectorStore(DashScopeEmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
}
}
testRag vs testRag2 vs testRag3
| 特性 | testRag | testRag2 | testRag3 |
|---|---|---|---|
| 使用的 Advisor | QuestionAnswerAdvisor |
QuestionAnswerAdvisor |
RetrievalAugmentationAdvisor |
| 查询转换 | ❌ | ❌ | ✅ RewriteQueryTransformer ✅ TranslationQueryTransformer |
| 查询增强 | ❌ | ❌ | ✅ ContextualQueryAugmenter |
| 文档后处理 | ❌ | ❌ | ✅ DocumentPostProcessors |
| 空上下文处理 | ❌ | ❌ | ✅ 支持自定义提示 |
| 配置复杂度 | 简单 | 简单 | 复杂 |
| 灵活性 | 低 | 低 | 高 |
| 适用场景 | 基础 RAG | 基础 RAG + 元数据过滤 | 高级 RAG、生产环境 |
代码对比
testRag(基础 RAG)
java
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.topK(5)
.similarityThreshold(0.6)
.build())
.build()
特点:
- ✅ 简单易用
- ❌ 不支持查询转换
- ❌ 不支持空上下文处理
- ❌ 不支持文档后处理
testRag2(基础 RAG + 元数据过滤)
java
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.topK(5)
.similarityThreshold(0.1)
.filterExpression(filterExpression) // 可以添加元数据过滤
.build())
.build()
特点:
- ✅ 支持元数据过滤
- ❌ 不支持查询转换
- ❌ 不支持空上下文处理
- ❌ 不支持文档后处理
testRag3(高级 RAG)
java
RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()...)
.queryAugmenter(ContextualQueryAugmenter.builder()...)
.queryTransformers(RewriteQueryTransformer.builder()...)
.queryTransformers(TranslationQueryTransformer.builder()...)
.documentPostProcessors((query, documents) -> {...})
.build()
特点:
- ✅ 支持查询转换(重写、翻译)
- ✅ 支持空上下文处理
- ✅ 支持文档后处理
- ✅ 高度可定制
- ⚠️ 配置较复杂
- ⚠️ 需要更多 LLM 调用(查询转换)
选择建议
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 快速原型 | testRag |
简单易用,快速验证 |
| 基础生产环境 | testRag2 |
支持元数据过滤,满足基本需求 |
| 高级生产环境 | testRag3 |
支持查询优化、空上下文处理等高级功能 |
| 多语言知识库 | testRag3 |
支持查询翻译 |
| 严格知识库模式 | testRag3 |
支持空上下文处理,避免幻觉 |
最佳实践
1. 查询转换器的顺序
原则:先重写,再翻译。
java
// ✅ 正确顺序
.queryTransformers(
RewriteQueryTransformer.builder()... // 先重写
.build(),
TranslationQueryTransformer.builder()... // 再翻译
.build()
)
// ❌ 错误顺序:先翻译再重写会导致翻译质量下降
.queryTransformers(
TranslationQueryTransformer.builder()... // 先翻译(不推荐)
.build(),
RewriteQueryTransformer.builder()... // 再重写
.build()
)
原因:
- 重写可以去除无关信息,提高翻译质量
- 翻译后的查询可能包含语法错误,影响重写效果
2. 空上下文处理策略
严格模式(推荐用于生产环境)
java
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false) // 严格模式
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("抱歉,我无法在知识库中找到相关信息。请咨询相关部门或人工客服。")
.build())
.build())
适用场景:
- ✅ 企业知识库(需要准确性)
- ✅ 法律文档系统(避免错误引用)
- ✅ 医疗知识系统(避免误导)
宽松模式(适用于通用场景)
java
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true) // 宽松模式
.build())
适用场景:
- ✅ 通用问答系统
- ✅ 客服系统(允许基于通用知识回答)
- ✅ 原型验证阶段
3. 相似度阈值设置
建议:
java
// 根据业务场景选择合适的阈值
VectorStoreDocumentRetriever.builder()
.similarityThreshold(
// 严格场景(法律、医疗):0.7-0.8
// 一般场景(客服、知识库):0.5-0.6
// 宽松场景(探索性查询):0.3-0.4
0.6 // 根据实际效果调整
)
.build()
调整策略:
- 从较低阈值开始(如 0.3)
- 观察检索结果的质量
- 逐步提高阈值,直到找到平衡点
- 考虑使用
topK限制结果数量
4. 文档后处理的最佳实践
监控和日志
java
.documentPostProcessors((query, documents) -> {
// 记录检索统计
log.info("Query: '{}', Retrieved: {} documents",
query.text(), documents.size());
// 记录文档详情(DEBUG 级别)
if (log.isDebugEnabled()) {
documents.forEach(doc -> {
log.debug("Document ID: {}, Similarity: {}, Text length: {}",
doc.getId(),
doc.getMetadata().get("similarity"),
doc.getText().length());
});
}
return documents;
})
文档质量过滤
java
.documentPostProcessors((query, documents) -> {
return documents.stream()
.filter(doc -> {
// 过滤过短的文档
if (doc.getText().length() < 50) {
return false;
}
// 过滤状态为"已删除"的文档
String status = (String) doc.getMetadata().get("status");
if ("deleted".equals(status)) {
return false;
}
return true;
})
.collect(Collectors.toList());
})
文档排序
java
.documentPostProcessors((query, documents) -> {
return documents.stream()
.sorted((d1, d2) -> {
// 优先返回高优先级的文档
int p1 = (int) d1.getMetadata().getOrDefault("priority", 0);
int p2 = (int) d2.getMetadata().getOrDefault("priority", 0);
if (p1 != p2) {
return Integer.compare(p2, p1); // 降序
}
// 如果优先级相同,按相似度排序
double s1 = (double) d1.getMetadata().getOrDefault("similarity", 0.0);
double s2 = (double) d2.getMetadata().getOrDefault("similarity", 0.0);
return Double.compare(s2, s1); // 降序
})
.collect(Collectors.toList());
})
5. 性能优化
减少 LLM 调用次数
java
// ⚠️ 避免不必要的查询转换
// 如果用户查询已经是标准格式,不需要重写
if (needsRewrite(userQuery)) {
.queryTransformers(RewriteQueryTransformer.builder()...)
}
// ⚠️ 如果知识库和用户语言一致,不需要翻译
if (!isSameLanguage(userQuery, knowledgeBaseLanguage)) {
.queryTransformers(TranslationQueryTransformer.builder()...)
}
缓存查询转换结果
java
// 缓存重写后的查询,避免重复调用 LLM
Map<String, String> rewriteCache = new ConcurrentHashMap<>();
.documentPostProcessors((query, documents) -> {
String cachedRewrite = rewriteCache.get(query.text());
if (cachedRewrite != null) {
// 使用缓存的查询
}
return documents;
})
限制文档数量
java
.documentPostProcessors((query, documents) -> {
// 限制传递给 LLM 的文档数量,减少 token 消耗
return documents.stream()
.limit(3) // 只保留前 3 个最相关的文档
.collect(Collectors.toList());
})
6. 错误处理
处理查询转换失败
java
try {
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(RewriteQueryTransformer.builder()...)
.build();
} catch (Exception e) {
// 降级为不使用查询转换
log.warn("Query transformer failed, falling back to direct query", e);
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(...)
.build();
}
处理空上下文
java
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(false)
.emptyContextPromptTemplate(PromptTemplate.builder()
.template("""
抱歉,我无法在知识库中找到相关信息。
建议:
1. 尝试使用不同的关键词重新提问
2. 联系相关部门获取帮助
3. 查看知识库目录
""")
.build())
.build())
常见问题
Q1: 为什么需要查询转换器?
问题:用户查询可能包含无关信息、情感化表达或不符合检索系统的格式。
解决方案 :使用 RewriteQueryTransformer 提取核心查询意图。
示例:
原始查询:"我今天心情不好,不想去玩了,你能不能告诉我退票需要多少钱?"
↓ RewriteQueryTransformer
重写查询:"退票费用是多少?"
优势:
- ✅ 提高检索准确性
- ✅ 减少噪音文档
- ✅ 适配特定检索系统
Q2: 什么时候需要 TranslationQueryTransformer?
适用场景:
- ✅ 知识库是英文的,但用户用中文提问
- ✅ 多语言知识库,需要统一检索语言
- ✅ 国际化应用,支持跨语言检索
不需要的场景:
- ❌ 知识库和用户语言一致
- ❌ 向量模型支持多语言(如多语言 Embedding 模型)
Q3: allowEmptyContext 应该设置为 true 还是 false?
建议:
| 场景 | 推荐值 | 原因 |
|---|---|---|
| 企业知识库 | false |
确保只基于知识库回答,避免幻觉 |
| 法律文档系统 | false |
避免错误引用法律条文 |
| 医疗知识系统 | false |
避免误导性医疗建议 |
| 通用客服系统 | true |
允许基于通用知识回答常见问题 |
| 原型验证 | true |
快速验证功能,后续再调整 |
Q4: 查询转换器会增加多少延迟?
延迟分析:
| 转换器 | LLM 调用次数 | 预估延迟(秒) |
|---|---|---|
RewriteQueryTransformer |
1 次 | 0.5-2.0 |
TranslationQueryTransformer |
1 次 | 0.5-2.0 |
| 两者组合 | 2 次 | 1.0-4.0 |
优化建议:
- ✅ 缓存转换结果
- ✅ 异步执行转换(如果支持)
- ✅ 只在必要时使用转换器
Q5: 如何调试查询转换过程?
调试方法:
java
// 方法 1:在文档后处理器中记录
.documentPostProcessors((query, documents) -> {
System.out.println("=== Query Transformation Debug ===");
System.out.println("Original query: " + query.text());
System.out.println("Retrieved documents: " + documents.size());
documents.forEach(doc -> {
System.out.println("Document: " + doc.getText().substring(0,
Math.min(100, doc.getText().length())));
});
return documents;
})
// 方法 2:使用 SimpleLoggerAdvisor
ChatClient.builder(chatModel)
.defaultAdvisors(SimpleLoggerAdvisor.builder().build())
.build()
Q6: 文档后处理器会影响性能吗?
性能影响:
- 轻微影响:简单的过滤、排序操作(< 10ms)
- 中等影响:复杂的文档处理(10-100ms)
- 显著影响:涉及外部服务调用(> 100ms)
优化建议:
- ✅ 避免在文档后处理器中进行耗时操作
- ✅ 使用异步处理(如果支持)
- ✅ 限制处理的文档数量
Q7: 如何选择合适的相似度阈值?
选择策略:
- 从低阈值开始(如 0.3)
- 观察检索结果 :
- 如果返回太多不相关文档 → 提高阈值
- 如果返回太少相关文档 → 降低阈值
- 根据业务需求调整 :
- 严格场景(法律、医疗):0.7-0.8
- 一般场景(客服、知识库):0.5-0.6
- 宽松场景(探索性查询):0.3-0.4
Q8: RetrievalAugmentationAdvisor 和 QuestionAnswerAdvisor 如何选择?
选择建议:
| 需求 | 推荐 Advisor |
|---|---|
| 快速原型 | QuestionAnswerAdvisor |
| 基础 RAG | QuestionAnswerAdvisor |
| 查询转换 | RetrievalAugmentationAdvisor |
| 空上下文处理 | RetrievalAugmentationAdvisor |
| 文档后处理 | RetrievalAugmentationAdvisor |
| 多语言支持 | RetrievalAugmentationAdvisor |
| 生产环境 | RetrievalAugmentationAdvisor |
总结
核心要点
-
RetrievalAugmentationAdvisor 是高级 RAG 解决方案 :相比
QuestionAnswerAdvisor,提供了更多的定制化能力。 -
查询转换器优化检索 :
RewriteQueryTransformer和TranslationQueryTransformer可以提高检索准确性。 -
空上下文处理避免幻觉 :通过
ContextualQueryAugmenter的allowEmptyContext=false确保只基于知识库回答。 -
文档后处理增强灵活性:支持文档过滤、排序、增强等操作。
-
配置复杂度与灵活性平衡 :
testRag3配置较复杂,但提供了最大的灵活性。
适用场景总结
| 场景 | 是否适合 testRag3 |
|---|---|
| 企业知识库 | ✅ 非常适合 |
| 多语言知识库 | ✅ 非常适合 |
| 智能客服系统 | ✅ 非常适合 |
| 法律文档系统 | ✅ 非常适合 |
| 医疗知识系统 | ✅ 非常适合 |
| 快速原型验证 | ⚠️ 建议使用 testRag |
| 简单问答系统 | ⚠️ 建议使用 testRag 或 testRag2 |
最佳实践总结
| 实践 | 说明 |
|---|---|
| ✅ 查询转换器顺序 | 先重写,再翻译 |
| ✅ 空上下文处理 | 生产环境使用严格模式 |
| ✅ 相似度阈值 | 根据业务场景调整(0.5-0.7) |
| ✅ 文档后处理 | 用于监控、过滤、排序 |
| ✅ 性能优化 | 缓存、限制文档数量 |
| ✅ 错误处理 | 实现降级策略 |
技术栈总结
- 核心组件 :
RetrievalAugmentationAdvisor - 文档检索 :
VectorStoreDocumentRetriever - 查询转换 :
RewriteQueryTransformer、TranslationQueryTransformer - 查询增强 :
ContextualQueryAugmenter - 文档处理 :
DocumentPostProcessors