在现代大型语言模型(LLM)应用中,RAG(Retrieval-Augmented Generation) 是一种常用策略:通过检索外部知识库来增强生成结果的准确性。
在 Spring AI 框架中,RetrievalAugmentationAdvisor 扮演着 RAG (检索增强生成) 流程的指挥官角色。
它负责编排从用户查询到最终上下文准备的整个过程,确保 LLM 接收到的 Prompt 是经过多层优化和增强的高质量输入。
本文将基于其内部的六个核心属性,详细阐述 RAG 流程中每一个组件的作用、典型应用场景,以及它们如何协同工作,共同提升问答系统的性能。
一、 QuestionAnswerAdvisor
研究RetrievalAugmentationAdvisor之前,我们先分析下QuestionAnswerAdvisor
两者都属于 Spring AI 提供的 Advisor API,用于 RAG(Retrieval-Augmented Generation)场景
QuestionAnswerAdvisory用于实现 简单、快速入门的 RAG 流。 
- 它接收用户问题,从向量存储(vector store)中检索相关文档,然后把这些文档拼接到用户的问题里,给 LLM 生成答案。 
- 自定义能力较有限 ------ 查询扩展、重写等定制较弱。
源码分析
java
public class QuestionAnswerAdvisor implements BaseAdvisor {
public static final String RETRIEVED_DOCUMENTS = "qa_retrieved_documents";
public static final String FILTER_EXPRESSION = "qa_filter_expression";
/**
* query: 原始prompt查询
* question_answer_context: vector查询结果
*/
private static final PromptTemplate DEFAULT_PROMPT_TEMPLATE = new PromptTemplate("{query}\n\nContext information is below, surrounded by ---------------------\n\n---------------------\n{question_answer_context}\n---------------------\n\nGiven the context and provided history information and not prior knowledge,\nreply to the user comment. If the answer is not in the context, inform\nthe user that you can't answer the question.\n");
private final VectorStore vectorStore;
private final PromptTemplate promptTemplate;
private final SearchRequest searchRequest;
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
//1. build search requst
SearchRequest searchRequestToUse = SearchRequest.from(this.searchRequest)
.query(chatClientRequest.prompt().getUserMessage().getText())//1.1 用chatClientRequest 替换searchRequest.query
.filterExpression(this.doGetFilterExpression(chatClientRequest.context()))//1.2 使用searchRequest.filterExpression
.build();
//2. 相似度查询
List<Document> documents = this.vectorStore.similaritySearch(searchRequestToUse);
//3. enrich context
Map<String, Object> context = new HashMap(chatClientRequest.context());
context.put("qa_retrieved_documents", documents);
//4. build new prompt
String documentContext = documents == null ? "" : (String)documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
UserMessage userMessage = chatClientRequest.prompt().getUserMessage();
String augmentedUserText = this.promptTemplate.render(Map.of("query", userMessage.getText(), "question_answer_context", documentContext));
//5. execute prompt
return chatClientRequest.mutate().prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText)).context(context).build();
}
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
ChatResponse.Builder chatResponseBuilder;
if (chatClientResponse.chatResponse() == null) {
chatResponseBuilder = ChatResponse.builder();
} else {
chatResponseBuilder = ChatResponse.builder().from(chatClientResponse.chatResponse());
}
//9. 将rag_document_context 放到repoonse中
chatResponseBuilder.metadata("qa_retrieved_documents", chatClientResponse.context().get("qa_retrieved_documents"));
return ChatClientResponse.builder().chatResponse(chatResponseBuilder.build()).context(chatClientResponse.context()).build();
}
}
一、RetrievalAugmentationAdvisor
RetrievalAugmentationAdvisor 的核心职责
RetrievalAugmentationAdvisor 实现了 BaseAdvisor 接口,它的核心职责是:
- 查询优化: 预处理用户查询,使其更适合检索。
- 文档检索: 调用检索器获取相关文档。
- 文档精炼: 对检索结果进行过滤、重排和摘要。
- 上下文组装: 将精炼后的文档和原始/增强查询整合,形成最终的 Prompt 上下文(Context)。
通过链式调用其内部的组件,该 Advisor 实现了 RAG 流程的自动化和模块化。
源码分析
java
public final class RetrievalAugmentationAdvisor implements BaseAdvisor {
public static final String DOCUMENT_CONTEXT = "rag_document_context";
private final List<QueryTransformer> queryTransformers;
@Nullable
private final QueryExpander queryExpander;
private final DocumentRetriever documentRetriever;
private final DocumentJoiner documentJoiner;
private final List<DocumentPostProcessor> documentPostProcessors;
private final QueryAugmenter queryAugmenter;
public ChatClientRequest before(ChatClientRequest chatClientRequest, @Nullable AdvisorChain advisorChain) {
Map<String, Object> context = new HashMap(chatClientRequest.context());
Query originalQuery = Query.builder().text(chatClientRequest.prompt().getUserMessage().getText()).history(chatClientRequest.prompt().getInstructions()).context(context).build();
Query transformedQuery = originalQuery;
//1. queryTransformer(查询转换): --- 对原始用户问题进行标准化或重写,使其易被检索和模型理解。
for(QueryTransformer queryTransformer : this.queryTransformers) {
transformedQuery = queryTransformer.apply(transformedQuery);
}
//2. QueryExpander(查询扩展): --- 生成多个语义相关查询,提高检索覆盖率。 1 -> n
List<Query> expandedQueries = this.queryExpander != null ? this.queryExpander.expand(transformedQuery) : List.of(transformedQuery);
//3.Retriever(文档检索): --- 从向量数据库或知识库中检索相关文档。
Map<Query, List<List<Document>>> documentsForQuery = (Map)expandedQueries.stream().map((query) -> CompletableFuture.supplyAsync(() -> this.getDocumentsForQuery(query), this.taskExecutor)).toList().stream().map(CompletableFuture::join).collect(Collectors.toMap(Map.Entry::getKey, (entry) -> List.of((List)entry.getValue())));
//4. documentJoiner(文档合并):
List<Document> documents = this.documentJoiner.join(documentsForQuery);
//5.DocumentPostProcessors(文档后处理): ---- 清洗、去重、摘要或重排检索结果,提高文档质量。
for(DocumentPostProcessor documentPostProcessor : this.documentPostProcessors) {
documents = documentPostProcessor.process(originalQuery, documents);
}
//6. queryAugmenter(查询增强): --- 在不改变语义的前提下加入上下文或元数据。
context.put("rag_document_context", documents); //6.1 将document存放至context
Query augmentedQuery = this.queryAugmenter.augment(originalQuery, documents); //6.2 使用queryAugmenter 查询originalQuery
//7.最终执行增强query
return chatClientRequest.mutate().prompt(chatClientRequest.prompt().augmentUserMessage(augmentedQuery.text())).context(context).build();
}
//3.从vector-store retrieve document
private Map.Entry<Query, List<Document>> getDocumentsForQuery(Query query) {
List<Document> documents = this.documentRetriever.retrieve(query);
return Map.entry(query, documents);
}
public ChatClientResponse after(ChatClientResponse chatClientResponse, @Nullable AdvisorChain advisorChain) {
ChatResponse.Builder chatResponseBuilder;
if (chatClientResponse.chatResponse() == null) {
chatResponseBuilder = ChatResponse.builder();
} else {
chatResponseBuilder = ChatResponse.builder().from(chatClientResponse.chatResponse());
}
//9. 将rag_document_context 放到repoonse中
chatResponseBuilder.metadata("rag_document_context", chatClientResponse.context().get("rag_document_context"));
return ChatClientResponse.builder().chatResponse(chatResponseBuilder.build()).context(chatClientResponse.context()).build();
}
}
三、核心组件解析
1. queryTransformers: List<QueryTransformer> (查询转换器)
- 作用: 在执行检索之前,对原始用户查询进行修改或转换。这是 RAG 流程的第一道优化关卡。
- 典型应用:
- 上下文感知重写 (Context-Aware Rewrite): 将多轮对话中的代词(如"它"、"这个")替换成具体名词,使查询更独立。
- 多语言翻译 (Translation):
- 将用户非目标语言的查询翻译成知识库文档的语言(如将中文查询转为英文)。
- 对原始用户查询进行语义层面的"重写或改写",使其更适合被向量检索或关键字搜索使用
- Spring AI 组件:
RewriteQueryTransformer,TranslationQueryTransformer等。
demo
java
@Test
public void test_RewriteQueryTransformer(){
Query query = Query.builder()
.text("今天天气挺好的,你在看什么?在看西游记啊, 你能告诉我孙悟空在花果山的朋友都有哪些人吗?")
.build();
QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.targetSearchSystem("孙悟空")
.build();
Query transformedQuery = queryTransformer.transform(query);
System.out.printf("Transformed Query: %s%n", transformedQuery.text()); //孙悟空在花果山的朋友有哪些?
}
@Test
public void test_TranslationQueryTransformer(){
Query query = Query.builder()
.text("今天天气挺好的,你在看什么?在看西游记啊, 你能告诉我孙悟空在花果山的朋友都有哪些人吗?")
.build();
QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.targetLanguage("Japanese")
.build();
Query transformedQuery = queryTransformer.transform(query);
System.out.printf("Transformed Query: %s%n", transformedQuery.text()); //今天天気はとても良いですね、何を見ていますか?『西遊記』を見ていますか、孫悟空の花果山の友達は誰か教えてもらえますか?
}
2. queryExpander: QueryExpander (查询扩展器)
- 作用: 基于原始查询,生成多个语义相关的替代查询,用于并行或多次检索,以提高文档召回率(Recall)。
- 典型应用:
- 同义词扩展 (Synonym Expansion): 将"薪水"扩展为"工资"、"报酬"。
- 假设文档生成 (HyDE - Hypothetical Document Embedding): 利用 LLM 为查询生成一个虚拟的、可能的答案,然后对这个虚拟答案进行 Embedding 检索,这能更好地捕获查询的深层语义。
- Spring AI 组件:
MultiQueryExpander。
demo
java
@Test
public void test_QueryExpend(){
Query query = Query.builder()
.text("齐天大圣?")
.build();
List<Query> expendQueries = MultiQueryExpander.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.includeOriginal(true) //是否包含原始查询
.numberOfQueries(5) //生成查询数量
.build().expand(query);
System.out.printf("Expend Queries: %s%n", expendQueries.size()); //6
expendQueries.forEach(expendQuery -> System.out.printf("Expend Query: %s%n", expendQuery.text()));
/**
Expend Query: 齐天大圣?
Expend Query: 齐天大圣的历史背景
Expend Query: 齐天大圣的传说故事
Expend Query: 齐天大圣在中国文化中的象征意义
Expend Query: 关于齐天大圣的影视作品推荐
Expend Query: 齐天大圣的武器和神通介绍
*/
}
3. documentRetriever: DocumentRetriever (文档检索器)
- 作用: 接收经过转换和扩展的查询,从配置的知识库(如向量数据库、搜索引擎)中检索出最相关的一批文档片段。
- 典型应用:
- 向量检索 (Vector Store Retrieval): 从 Chroma, Milvus, Redis 等向量库中基于余弦相似度检索。
- 混合检索 (Hybrid Search): 同时利用关键词(如 Elasticsearch)和向量检索,以获得更高的精确度和召回率。
- Spring AI 组件:
VectorStoreRetriever,ElasticSearchRetriever。
4. documentPostProcessors: List<DocumentPostProcessor> (文档后处理器)
- 作用: 对
documentRetriever检索到的文档列表进行二次处理和精炼,以提高文档质量和降低 LLM 的输入噪声。 - 典型应用:
- 重排 (Re-Ranking): 使用 Reranker 模型(通常是 Cross-Encoder)对文档进行评分和重新排序,将最相关的文档放在前面,对抗 LLM 的位置偏差 (Positional Bias)。
- 过滤 (Filtering): 基于元数据(如权限、时效性)或相似度得分阈值,移除低质量或不应公开的文档。
- 摘要/压缩 (Summarization/Compression): 对过长的文档片段生成简洁摘要,确保其在 LLM 的上下文窗口内。
5. documentJoiner: DocumentJoiner (文档连接器)
- 作用: 负责将所有经过后处理的文档片段,按照特定的格式和分隔符连接起来,形成一个完整的上下文字符串,供 LLM 使用。
- 典型应用:
- 标准 Markdown 格式: 使用
\n\n---\n\n或 Markdown 标题分隔符连接。 - JSON/XML 结构化格式: 将每个文档片段及其元数据封装成结构化数据,以便 LLM 更好地解析。
- 标准 Markdown 格式: 使用
- Spring AI 组件:
ConcatenationDocumentJoiner
6. queryAugmenter: QueryAugmenter (查询增强器)
- 作用: 在检索和后处理完成后,将最终的上下文字符串 、系统角色等非检索信息注入到原始查询中,准备构建发送给 LLM 的最终 Prompt。
- 典型应用:
- 系统指令注入: 添加如"你是一个友好的金融顾问,请基于上下文回答问题"的指令。
- 最终 Prompt 组装: 将
documentJoiner生成的上下文字符串放入一个 Prompt 模板的特定占位符中。
- Spring AI 组件:
ContextQueryAugmenter,MetadataQueryAugmenter。
ContextQueryAugmenter
ContextualQueryAugmenter:基于上下文增强查询。默认情况下 allowEmptyContext = false
- 如果为false,即不允许在没有检索到任何相关内容时,
emptyContextPromptTemplate作为回应返回给用户。 - 如果为true,即允许在没有检索到任何相关内容时,LLM将仅基于用户的查询生成回答。
四、 综合应用案例:高性能混合 RAG 流程
本案例展示如何组合上述组件,实现一个高性能、多语言支持的混合 RAG 系统。
代码结构(核心逻辑)
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
// 1. 查询转换 (QueryTransformer):
.queryTransformers(
//1.1 中文 -> 英文
TranslationQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.targetLanguage("English").build(),
//2. 提取关键词 Monkey King
RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.targetSearchSystem("Monkey King")
.build()
)
//2. 前查 == 多重查询扩展
.queryExpander(MultiQueryExpander.builder()
.chatClientBuilder(ChatClient.builder(openAiChatModel))
.includeOriginal(true) //是否包含原始查询
.numberOfQueries(5) //生成查询数量
.build())
//3. retriever
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.1)
.topK(3)
// .filterExpression()
.vectorStore(vectorStore)
.build())
//4. 文档后处理 (DocumentPostProcessors)
.documentPostProcessors(
//mock reranking
(query, documents) -> {
System.out.printf("Re-ranker - original query:", query.text());
//简单的基于关键字year 重排示例
List<Document> rerankedDocs = documents.stream()
.sorted((doc1, doc2) -> {
double count1 = Optional.ofNullable(doc1.getMetadata().get("year")).map(Object::toString).map(Double::parseDouble).orElse(0.0);
double count2 = Optional.ofNullable(doc2.getMetadata().get("year")).map(Object::toString).map(Double::parseDouble).orElse(0.0);
return Double.compare(count2,count1); //降序
})
.collect(Collectors.toList());
System.out.printf("Re-ranker - re-ranked %d documents.%n", rerankedDocs.size());
rerankedDocs.forEach(doc -> System.out.printf("Re-ranker - re-ranked document: %s%n", doc.getMetadata()));
return rerankedDocs;
},
//mock filtering
(query, documents) -> {
documents.removeIf(doc -> doc.getScore() < 0.75);
return documents;
}
)
//5.文档连接 (DocumentJoiner)
.documentJoiner((documents) -> {
/**
* todo
* MarkdownDocumentJoiner: 将多个文档合并为一个Markdown格式的文档,适用于需要结构化内容的场景。
* List<List<Document>>> -> List<Document(markdown)>>
*/
return null;
})
/**
* 6. 查询增强 (QueryAugmenter):
* - ContextualQueryAugmenter:基于上下文增强查询。默认情况下 allowEmptyContext = false
* - 如果为false,即不允许在没有检索到任何相关内容时,emptyContextPromptTemplate 作为回应返回给用户。
* - 如果为true,即允许在没有检索到任何相关内容时,LLM将仅基于用户的查询生成回答。
*/
.queryAugmenter(
ContextualQueryAugmenter.builder()
.allowEmptyContext(true) //允许空上下文,否则没有检索到内容回复: 无法处理
.emptyContextPromptTemplate(
PromptTemplate.builder().template("用户查询之于知识库之外,请礼貌的告诉他: 您的问题, 超出了我的知识范围,建议您咨询相关领域的专家。")
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.build())
.build())
.build();