10 Retrieval Augmented Generation

48 阅读9分钟

检索增强生成 (Retrieval Augmented Generation)

检索增强生成(RAG)是一种有用的技术,用于克服大语言模型在长篇内容处理、事实准确性和上下文感知方面的局限性。

Spring AI 通过提供模块化架构来支持 RAG,该架构允许您自行构建自定义 RAG 流程,或使用 Advisor API 的现成 RAG 流程。

注意:在概念部分了解更多关于检索增强生成的信息。

顾问 (Advisors)

Spring AI 使用 Advisor API 为常见的 RAG 流程提供现成的支持。

要使用 QuestionAnswerAdvisorVectorStoreChatMemoryAdvisor,您需要在项目中添加 spring-ai-advisors-vector-store 依赖:

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

QuestionAnswerAdvisor

向量数据库存储了 AI 模型不知道的数据。当用户问题发送到 AI 模型时,QuestionAnswerAdvisor 查询向量数据库以查找与用户问题相关的文档。

来自向量数据库的响应会附加到用户文本,为 AI 模型生成响应提供上下文。

假设您已经将数据加载到 VectorStore 中,您可以通过向 ChatClient 提供 QuestionAnswerAdvisor 实例来执行检索增强生成(RAG)。

ChatResponse response = ChatClient.builder(chatModel)
        .build().prompt()
        .advisors(new QuestionAnswerAdvisor(vectorStore))
        .user(userText)
        .call()
        .chatResponse();

在此示例中,QuestionAnswerAdvisor 将对向量数据库中的所有文档执行相似性搜索。要限制搜索的文档类型,SearchRequest 采用类似 SQL 的过滤表达式,该表达式在所有 VectorStores 中都是可移植的。

此过滤表达式可以在创建 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();

// 在运行时更新过滤表达式
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 提示模板。

自定义 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 流程
朴素 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 在将检索到的文档传递给模型之前对其进行后处理。例如,您可以使用此类接口根据文档与查询的相关性对检索到的文档进行重新排序,移除不相关或冗余的文档,或压缩每个文档的内容以减少噪声和冗余。

模块 (Modules)

Spring AI 实现了模块化 RAG 架构,其灵感来自论文"Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks"中详述的模块化概念。

检索前处理 (Pre-Retrieval)

检索前处理模块负责处理用户查询以实现最佳的检索结果。

查询转换 (Query Transformation)

用于转换输入查询的组件,使其对检索任务更有效,解决查询格式不当、术语模糊、词汇复杂或语言不受支持等挑战。

重要:使用 QueryTransformer 时,建议配置具有低温度(例如 0.0)的 ChatClient.Builder,以确保更确定和准确的结果,提高检索质量。大多数聊天模型的默认温度通常对于最佳查询转换来说太高,导致检索效果降低。

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() 方法进行自定义。

查询扩展 (Query Expansion)

用于将输入查询扩展为查询列表的组件,通过提供替代查询表述或将复杂问题分解为更简单的子查询来解决查询格式不当等挑战。

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() 方法进行自定义。

检索 (Retrieval)

检索模块负责查询向量存储等数据系统并检索最相关的文档。

文档搜索 (Document Search)

负责从底层数据源(如搜索引擎、向量存储、数据库或知识图谱)检索 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?"));

您还可以使用 FILTER_EXPRESSION 参数通过 Query API 提供请求特定的过滤表达式。如果同时提供了请求特定的和检索器特定的过滤表达式,则请求特定的过滤表达式优先。

Query query = Query.builder()
    .text("Who is Anacletus?")
    .context(Map.of(VectorStoreDocumentRetriever.FILTER_EXPRESSION, "location == 'Whispering Woods'"))
    .build();
List<Document> retrievedDocuments = documentRetriever.retrieve(query);
文档连接 (Document Join)

用于将从多个查询和多个数据源检索到的文档合并为单个文档集合的组件。作为连接过程的一部分,它还可以处理重复文档和相互排名策略。

ConcatenationDocumentJoiner

ConcatenationDocumentJoiner 通过将从多个查询和多个数据源检索到的文档连接成单个文档集合来合并它们。在重复文档的情况下,保留第一次出现。每个文档的分数保持不变。

Map<Query, List<List<Document>>> documentsForQuery = ...
DocumentJoiner documentJoiner = new ConcatenationDocumentJoiner();
List<Document> documents = documentJoiner.join(documentsForQuery);

检索后处理 (Post-Retrieval)

检索后处理模块负责处理检索到的文档以实现最佳的生成结果。

文档后处理 (Document Post-Processing)

用于根据查询后处理检索到的文档的组件,解决诸如中间丢失、模型的上下文长度限制以及减少检索信息中噪声和冗余的需要等挑战。

例如,它可以根据文档与查询的相关性对文档进行排名,移除不相关或冗余的文档,或压缩每个文档的内容以减少噪声和冗余。

生成 (Generation)

生成模块负责根据用户查询和检索到的文档生成最终响应。

查询增强 (Query Augmentation)

用于用附加数据增强输入查询的组件,为大型语言模型提供回答用户查询所需的必要上下文。

ContextualQueryAugmenter

ContextualQueryAugmenter 使用提供文档内容中的上下文数据增强用户查询。

QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder().build();

默认情况下,ContextualQueryAugmenter 不允许检索到的上下文为空。当发生这种情况时,它指示模型不回答用户查询。

您可以启用 allowEmptyContext 选项,以允许模型即使在检索到的上下文为空时也能生成响应。

QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder()
        .allowEmptyContext(true)
        .build();

此组件使用的提示可以通过构建器中可用的 promptTemplate()emptyContextPromptTemplate() 方法进行自定义。