Lzh on GitHub

检索增强生成

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

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

Spring AI 通过提供模块化架构支持 RAG,使你可以自行构建自定义 RAG 流程,或者使用 Advisor API 提供的开箱即用 RAG 流程。

概念 部分可以了解更多关于检索增强生成的内容。

顾问(Advisors)

Spring AI 提供了开箱即用的常用 RAG 流程支持,可通过 Advisor API 使用。

若要使用 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(QuestionAnswerAdvisor.builder(vectorStore).build())
        .user(userText)
        .call()
        .chatResponse();

在此示例中,QuestionAnswerAdvisor 会对向量数据库中的所有文档执行相似度搜索。若要限制搜索的文档类型,SearchRequest 可接受类似 SQL 的过滤表达式,该表达式可跨所有向量数据库使用。

该过滤表达式可以在创建 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())不同,后者仅影响顾问运行前的初始用户/系统提示内容渲染。

自定义 PromptTemplate 可以使用任意 TemplateRenderer 实现(默认使用基于 StringTemplate 引擎的 StPromptTemplate)。重要要求是模板必须包含以下两个占位符:

  • query 占位符,用于接收用户问题
  • question_answer_context 占位符,用于接收检索到的上下文

示例自定义模板:

PromptTemplate customPromptTemplate = PromptTemplate.builder()
    .renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
    .template("""
            <query>

            上下文信息如下:

            ---------------------
            <question_answer_context>
            ---------------------

            根据上下文信息回答问题,假设没有先验知识。

            遵循以下规则:

            1. 如果上下文中没有答案,就直接说不知道。
            2. 避免使用类似“根据上下文……”或“提供的信息……”的表述。
            """)
    .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)

检索前模块(Pre-Retrieval modules)负责处理用户查询,以获得尽可能最佳的检索结果。

查询转换(Query Transformation)

查询转换组件用于对输入查询进行改写,使其在检索任务中更有效,解决诸如查询表达不清、术语模糊、词汇复杂或语言不支持等问题。

使用 QueryTransformer 时,建议在 ChatClient.Builder 中配置较低的 temperature(例如 0.0),以确保结果更确定、更准确,从而提高检索质量。大多数聊天模型的默认 temperature 通常过高,可能导致查询转换效果不佳,降低检索效果。
压缩查询转换器(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(false) 方法禁用该行为:

MultiQueryExpander queryExpander = MultiQueryExpander.builder()
    .chatClientBuilder(chatClientBuilder)
    .includeOriginal(false)
    .build();

同样,可通过 promptTemplate() 方法自定义该组件使用的提示模板。

检索(Retrieval)

检索模块负责查询数据系统(如向量数据库)并获取最相关的文档。

该组件负责从底层数据源(如搜索引擎、向量数据库、关系数据库或知识图谱)中检索文档。

向量数据库文档检索器(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);

文档合并(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() 方法自定义。