主题
检索增强生成
检索增强生成(RAG)是一种用于克服大语言模型局限性的技术, 这些模型在长文本内容、事实准确性和上下文感知方面表现欠佳。
Spring AI 通过提供一种模块化架构来支持 RAG,让你既能自己构建自定义的 RAG 流程, 也能借助 Advisor API 使用开箱即用的 RAG 流程。
NOTE
在概念一节中可了解更多关于检索增强生成的内容。
顾问
Spring AI 借助 Advisor API,为常见 RAG 流程提供开箱即用的支持。
要使用 QuestionAnswerAdvisor 或 VectorStoreChatMemoryAdvisor,你需要把 spring-ai-vector-store-advisor 依赖添加到你的项目:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-vector-store-advisor</artifactId>
</dependency>QuestionAnswerAdvisor
向量数据库存储着 AI 模型未知的数据。当用户的问题被发送给 AI 模型时,QuestionAnswerAdvisor 会就与用户问题相关的文档查询该向量数据库。
来自向量数据库的响应会被追加到用户文本之后,为 AI 模型提供上下文以生成响应。
假设你已把数据加载进了一个 VectorStore,你可以通过向 ChatClient 提供一个 QuestionAnswerAdvisor 实例来执行检索增强生成(RAG)。
java
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(userText)
.call()
.chatResponse();在本例中,QuestionAnswerAdvisor 会对向量数据库中的所有文档执行相似度搜索。要限制被搜索的文档类型,SearchRequest 接收一个类 SQL 的过滤表达式,它可在所有 VectorStore 之间移植。
这个过滤表达式可以在创建 QuestionAnswerAdvisor 时配置,从而始终适用于所有 ChatClient 请求,也可以在运行时按请求提供。
下面演示如何创建一个 QuestionAnswerAdvisor 实例,其阈值为 0.8 并返回前 6 条结果。
java
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
.build();动态过滤表达式
使用 FILTER_EXPRESSION 顾问上下文参数,在运行时更新 SearchRequest 的过滤表达式:
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().build())
.build())
.build();
// Update filter expression at runtime
String content = this.chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();FILTER_EXPRESSION 参数让你能依据所提供的表达式,动态过滤搜索结果。
自定义模板
QuestionAnswerAdvisor 使用一个默认模板,用检索到的文档来增强用户问题。你可以通过 .promptTemplate() 构建器方法提供你自己的 PromptTemplate 对象来定制这一行为。
NOTE
这里提供的 PromptTemplate 定制的是顾问如何把检索到的上下文与用户查询合并。这不同于在 ChatClient 本身配置 TemplateRenderer(使用 .templateRenderer()),后者影响的是顾问运行之前、初始用户/系统提示词内容的渲染。关于客户端级模板渲染的更多细节,参见 ChatClient 提示词模板。
自定义的 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认使用基于 StringTemplate 引擎的 StPromptTemplate)。重要的要求是,模板必须包含以下两个占位符:
- 一个
query占位符,用于接收用户问题。 - 一个
question_answer_context占位符,用于接收检索到的上下文。
java
PromptTemplate customPromptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.template("""
<query>
Context information is below.
---------------------
<question_answer_context>
---------------------
Given the context information and no prior knowledge, answer the query.
Follow these rules:
1. If the answer is not in the context, just say that you don't know.
2. Avoid statements like "Based on the context..." or "The provided information...".
""")
.build();
String question = "Where does the adventure of Anacletus and Birba take place?";
QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.promptTemplate(customPromptTemplate)
.build();
String response = ChatClient.builder(chatModel).build()
.prompt(question)
.advisors(qaAdvisor)
.call()
.content();NOTE
QuestionAnswerAdvisor.Builder.userTextAdvise() 方法已被弃用,改用以获得更灵活定制的 .promptTemplate()。
RetrievalAugmentationAdvisor
Spring AI 包含一个 RAG 模块库,你可用它构建自己的 RAG 流程。 RetrievalAugmentationAdvisor 是一个 Advisor,基于模块化架构,为最常见的 RAG 流程提供开箱即用的实现。
要使用 RetrievalAugmentationAdvisor,你需要把 spring-ai-rag 依赖添加到你的项目:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>顺序式 RAG 流程
朴素 RAG
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build())
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();默认情况下,RetrievalAugmentationAdvisor 不允许检索到的上下文为空。当这种情况发生时, 它会指示模型不要回答用户查询。你可以按如下方式允许空上下文。
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();VectorStoreDocumentRetriever 接收一个 FilterExpression,以依据元数据过滤搜索结果。 你可以在实例化 VectorStoreDocumentRetriever 时提供它,或在运行时按请求提供, 使用 FILTER_EXPRESSION 顾问上下文参数。
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build())
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.advisors(a -> a.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, "type == 'Spring'"))
.user(question)
.call()
.content();更多信息参见 VectorStoreDocumentRetriever。
高级 RAG
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder.build().mutate())
.build())
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build())
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();你还可以使用 DocumentPostProcessor API 在把检索到的文档传给模型之前对其做后处理。例如,你可以用这样的接口依据与查询的相关性对检索到的文档进行重排序、移除不相关或冗余的文档,或压缩每个文档的内容以减少噪声与冗余。
模块
Spring AI 实现了一种模块化 RAG 架构,其灵感来自论文 “Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks” 中所详述的模块化概念。
检索前(Pre-Retrieval)
检索前模块负责处理用户查询,以取得尽可能最佳的检索结果。
查询转换
一个用于转换输入查询、使其对检索任务更有效的组件,应对诸如查询表述不佳、 术语含糊、词汇复杂或语言不受支持等挑战。
WARNING
使用 QueryTransformer 时,建议把 ChatClient.Builder 配置为较低的温度(例如 0.0),以获得更确定、更准确的结果,从而提升检索质量。大多数聊天模型的默认温度对于最优的查询转换而言通常过高,会导致检索效果下降。
CompressionQueryTransformer
CompressionQueryTransformer 使用大语言模型把对话历史和一次追问 压缩成一个能捕捉对话要义的独立查询。
当对话历史很长且追问与对话上下文相关时,这个转换器很有用。
java
Query query = Query.builder()
.text("And what is its second largest city?")
.history(new UserMessage("What is the capital of Denmark?"),
new AssistantMessage("Copenhagen is the capital of Denmark."))
.build();
QueryTransformer queryTransformer = CompressionQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
Query transformedQuery = queryTransformer.transform(query);该组件所使用的提示词可通过构建器中提供的 promptTemplate() 方法定制。
RewriteQueryTransformer
RewriteQueryTransformer 使用大语言模型重写用户查询,以便在查询目标系统 (如向量库或网络引擎)时提供更好的结果。
当用户查询冗长、含糊或包含可能影响搜索结果质量的无关信息时, 这个转换器很有用。
java
Query query = new Query("I'm studying machine learning. What is an LLM?");
QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
Query transformedQuery = queryTransformer.transform(query);该组件所使用的提示词可通过构建器中提供的 promptTemplate() 方法定制。
TranslationQueryTransformer
TranslationQueryTransformer 使用大语言模型把一个查询翻译成目标语言,该语言受用于生成文档嵌入的 嵌入模型支持。如果查询已是目标语言,则原样返回。如果查询语言未知,也原样返回。
当嵌入模型只在某一特定语言上训练、而用户查询用的是另一种语言时, 这个转换器很有用。
java
Query query = new Query("Hvad er Danmarks hovedstad?");
QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.targetLanguage("english")
.build();
Query transformedQuery = queryTransformer.transform(query);该组件所使用的提示词可通过构建器中提供的 promptTemplate() 方法定制。
查询扩展
一个把输入查询扩展为查询列表的组件,通过提供替代的查询表述, 或把复杂问题分解为更简单的子查询,来应对诸如查询表述不佳等挑战。
MultiQueryExpander
MultiQueryExpander 使用大语言模型把一个查询扩展为多个语义上多样的变体, 以捕捉不同视角,便于检索额外的上下文信息并增加找到相关结果的机会。
java
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.numberOfQueries(3)
.build();
List<Query> queries = queryExpander.expand(new Query("How to run a Spring Boot app?"));默认情况下,MultiQueryExpander 会把原始查询包含在扩展查询列表中。你可以通过构建器中的 includeOriginal 方法禁用此行为。
java
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.includeOriginal(false)
.build();该组件所使用的提示词可通过构建器中提供的 promptTemplate() 方法定制。
检索(Retrieval)
检索模块负责查询诸如向量库等数据系统,并检索最相关的文档。
文档搜索
负责从一个底层数据源检索 Documents 的组件,例如搜索引擎、向量库、 数据库或知识图谱。
VectorStoreDocumentRetriever
VectorStoreDocumentRetriever 从向量库检索与输入查询语义相似的文档。 它支持基于元数据、相似度阈值和 top-k 结果的过滤。
java
DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.73)
.topK(5)
.filterExpression(new FilterExpressionBuilder()
.eq("genre", "fairytale")
.build())
.build();
List<Document> documents = retriever.retrieve(new Query("What is the main character of the story?"));过滤表达式可以是静态的或动态的。对于动态过滤表达式,你可以传入一个 Supplier。
java
DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.filterExpression(() -> new FilterExpressionBuilder()
.eq("tenant", TenantContextHolder.getTenantIdentifier())
.build())
.build();
List<Document> documents = retriever.retrieve(new Query("What are the KPIs for the next semester?"));你也可以通过 Query API、使用 FILTER_EXPRESSION 参数提供一个请求特定的过滤表达式。 如果同时提供了请求特定和检索器特定的过滤表达式,则请求特定的过滤表达式优先。
java
Query query = Query.builder()
.text("Who is Anacletus?")
.context(Map.of(VectorStoreDocumentRetriever.FILTER_EXPRESSION, "location == 'Whispering Woods'"))
.build();
List<Document> retrievedDocuments = documentRetriever.retrieve(query);文档合并
一个把基于多个查询、从多个数据源检索到的文档合并为单一文档集合的组件。 在合并过程中,它还能处理重复文档与互反排名(reciprocal ranking)策略。
ConcatenationDocumentJoiner
ConcatenationDocumentJoiner 通过把基于多个查询、从多个数据源检索到的文档拼接 为一个单一文档集合来合并它们。若出现重复文档,则保留第一次出现的那份。 每个文档的分数保持原样。
java
Map<Query, List<List<Document>>> documentsForQuery = ...
DocumentJoiner documentJoiner = new ConcatenationDocumentJoiner();
List<Document> documents = documentJoiner.join(documentsForQuery);检索后(Post-Retrieval)
检索后模块负责处理检索到的文档,以取得尽可能最佳的生成结果。
文档后处理
一个用于依据查询对检索到的文档进行后处理的组件,应对诸如中间信息丢失(lost-in-the-middle)、 模型施加的上下文长度限制,以及需减少检索信息中噪声与冗余等挑战。
例如,它可以依据与查询的相关性对文档排序、移除不相关或冗余的文档,或压缩每个文档的内容以减少噪声与冗余。
Generation
生成模块负责基于用户查询与检索到的文档生成最终响应。
查询增强
一个用额外数据增强输入查询的组件,用于向大语言模型提供 回答用户查询所必需的上下文。
ContextualQueryAugmenter
ContextualQueryAugmenter 用所提供文档内容中的上下文数据来增强用户查询。
java
QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder().build();默认情况下,ContextualQueryAugmenter 不允许检索到的上下文为空。当这种情况发生时, 它会指示模型不要回答用户查询。
你可以启用 allowEmptyContext 选项,让模型即便在检索到的上下文为空时也能生成响应。
java
QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build();该组件所使用的提示词可通过构建器中提供的 promptTemplate() 和 emptyContextPromptTemplate() 方法 定制。