检索增强生成
检索增强生成(RAG)是一种有效克服大型语言模型在处理长文本内容、事实准确性以及上下文感知方面局限性的技术。
Spring AI 通过提供模块化架构来支持 RAG,允许您自行构建自定义的 RAG 流程,或使用 Advisor API 来使用开箱即用的 RAG 流程。
更多关于检索增强生成(Retrieval Augmented Generation)的信息,请查阅概念部分。
顾问
Spring AI 通过 Advisor API 为常见的 RAG 流程提供了开箱即用的支持。
要使用 QuestionAnswerAdvisor 或 VectorStoreChatMemoryAdvisor,你需要在项目中添加 spring-ai-advisors-vector-store 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
问题回答顾问
向量数据库存储了AI模型未知的数据。当用户问题发送到AI模型时,QuestionAnswerAdvisor会查询向量数据库以获取与用户问题相关的文档。
向量数据库的响应被附加到用户文本中,为AI模型生成回答提供上下文。
假设您已将数据加载到 VectorStore 中,可以通过向 ChatClient 提供 QuestionAnswerAdvisor 的实例来执行检索增强生成(RAG)。
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 个结果。
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
.build();
动态筛选表达式
在运行时使用 FILTER_EXPRESSION 顾问上下文参数更新 SearchRequest 过滤器表达式:
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 对象,来定制此行为。
这里提供的 PromptTemplate 用于自定义顾问如何将检索到的上下文与用户查询合并。这与在 ChatClient 上配置 TemplateRenderer(通过 .templateRenderer() 方法)不同,后者影响的是在顾问运行之前初始用户/系统提示内容的渲染。关于客户端级别的模板渲染,请参阅 ChatClient Prompt Templates 以获取更多详情。
自定义的 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认情况下,它使用基于 StringTemplate 引擎的 StPromptTemplate)。关键要求是模板必须包含以下两个占位符:
-
一个
query占位符,用于接收用户问题。 -
一个
question_answer_context占位符,用于接收检索到的上下文。
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();
QuestionAnswerAdvisor.Builder.userTextAdvise() 方法已被弃用,建议使用 .promptTemplate() 以获得更灵活的定制能力。
RetrievalAugmentationAdvisor
Spring AI 包含一个 RAG 模块库,可用于构建自定义的 RAG 流程。RetrievalAugmentationAdvisor 是一个 Advisor,它基于模块化架构,为最常见的 RAG 流程提供了一个开箱即用的实现。
要使用 RetrievalAugmentationAdvisor,你需要在项目中添加 spring-ai-rag 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
顺序RAG流程
Naive RAG
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 不允许检索到的上下文为空。当出现这种情况时,它会指示模型不要回答用户的查询。你可以通过以下方式允许空上下文。
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 顾问上下文参数来提供。
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
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 架构,其设计灵感来源于论文《模块化 RAG:将 RAG 系统转变为乐高式可重构框架》中详细阐述的模块化概念。
预检索
预处理模块负责处理用户查询,以获得最佳的检索效果。
查询转换
一个用于转换输入查询的组件,使其在检索任务中更加有效,解决查询表述不佳、术语模糊、词汇复杂或语言不受支持等挑战。
使用 QueryTransformer 时,建议将 ChatClient.Builder 的温度参数设置为较低值(例如 0.0),以确保结果更具确定性和准确性,从而提高检索质量。对于大多数聊天模型而言,其默认温度值通常过高,不利于实现最佳的查询转换效果,从而导致检索效率下降。
CompressionQueryTransformer
CompressionQueryTransformer 利用大型语言模型将对话历史和后续查询压缩成一个独立的查询,以捕捉对话的精髓。
当对话历史较长,且后续查询与对话上下文相关时,该转换器非常有用。
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 利用大型语言模型重写用户查询,以便在查询目标系统(如向量存储库或网络搜索引擎)时提供更好的结果。
当用户查询冗长、模糊或包含可能影响搜索结果质量的无关信息时,该转换器非常有用。
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利用大语言模型将查询翻译为目标语言,该目标语言需与用于生成文档嵌入的嵌入模型所支持的语言一致。若查询已为目标语言,则直接返回原查询。若查询语言未知,同样返回原查询。
当嵌入模型在特定语言上训练,而用户查询使用不同语言时,该转换器非常有用。
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 利用大型语言模型将查询扩展为多个语义多样化的变体,以捕捉不同的视角,有助于检索额外的上下文信息并提高找到相关结果的机会。
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 方法来禁用此行为。
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.includeOriginal(false)
.build();
此组件的提示词可通过构建器中的 promptTemplate() 方法进行自定义。
检索
检索模块负责查询向量存储等数据系统,并获取最相关的文档。
文档搜索
该组件负责从底层数据源(如搜索引擎、向量存储、数据库或知识图谱)中检索Documents。
VectorStoreDocumentRetriever
VectorStoreDocumentRetriever 从向量存储中检索与输入查询语义相似的文档。它支持基于元数据筛选、相似度阈值和 top-k 结果。
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。
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参数提供特定请求的过滤表达式。如果同时提供了特定请求和特定检索器的过滤表达式,特定请求的过滤表达式将优先生效。
Query query = Query.builder()
.text("Who is Anacletus?")
.context(Map.of(VectorStoreDocumentRetriever.FILTER_EXPRESSION, "location == 'Whispering Woods'"))
.build();
List<Document> retrievedDocuments = documentRetriever.retrieve(query);
文档连接
一个组件,用于将基于多个查询并从多个数据源检索到的文档合并为单一文档集合。作为合并过程的一部分,它还可以处理重复文档和互惠排名策略。
连接文档合并器
ConcatenationDocumentJoiner 通过将基于多个查询和从多个数据源检索到的文档串联起来,组合成一个单一的文档集合。如果出现重复文档,则保留第一次出现的文档。每个文档的评分保持原样。
Map<Query, List<List<Document>>> documentsForQuery = ...
DocumentJoiner documentJoiner = new ConcatenationDocumentJoiner();
List<Document> documents = documentJoiner.join(documentsForQuery);
检索后处理
后检索模块负责处理已检索到的文档,以达成最优的生成效果。
文档后处理
一个基于查询对检索到的文档进行后处理的组件,旨在应对诸如中间丢失、模型上下文长度限制以及减少检索信息中的噪声和冗余等挑战。
例如,它可以根据文档与查询的相关性对文档进行排序,移除不相关或冗余的文档,或者压缩每个文档的内容以减少噪声和冗余。
生成
生成模块负责根据用户查询和检索到的文档生成最终响应。
查询增强
一个用于增强输入查询的组件,可提供额外数据,有助于为大型语言模型提供回答用户查询所需的上下文。
ContextualQueryAugmenter
ContextualQueryAugmenter 通过所提供的文档内容中的上下文数据来增强用户查询。
QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder().build();
默认情况下,ContextualQueryAugmenter不允许检索到的上下文为空。当出现这种情况时,它会指示模型不回答用户查询。
你可以启用 allowEmptyContext 选项,以允许模型在检索到的上下文为空时也能生成回应。
QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build();
该组件使用的提示词可通过构建器中提供的 promptTemplate() 和 emptyContextPromptTemplate() 方法进行自定义。
章节总结
📄️ ETL 数据管道
提取、转换和加载(ETL)框架是检索增强生成(RAG)用例中数据处理的支柱。