QuestionAnswerAdvisor 增强问答系统 RAG

QuestionAnswerAdvisor 类是一个用于增强问答系统的组件,它结合了检索增强生成(RAG, Retrieval-Augmented Generation)的能力,通过从外部知识库(如向量数据库)中检索相关信息,动态生成更准确的答案。

当用户提出问题时,QuestionAnswerAdvisor 会首先对知识库进行检索,并将匹配到的相关引用文本添加到用户提问的后面,从而为生成的回答提供更为丰富和准确的上下文。此外,该 Advisor 还设定了一个默认提示词,旨在确保回答的质量和相关性。如果在知识库中无法找到匹配的文本,系统将可能拒绝回答用户的问题。

整体来说,QuestionAnswerAdvisor 类的主要作如下:

  • 检索相关文档:从 VectorStore(如 Pinecone、Weaviate、Redis 等)中查找与问题最匹配的文本片段。

  • 增强 Prompt 上下文:将检索到的文档作为额外上下文注入到 AI 模型的 Prompt 中。

  • 生成更准确的答案:利用检索到的信息,让 AI 生成更符合事实、更少幻觉(Hallucination)的回答。

下面通过简单流程图介绍 QuestionAnswerAdvisor 类的作用:

QuestionAnswerAdvisor 增强问答系统 RAG

  

构造方法

和其它几个内置 Advisor 不同,QuestionAnswerAdvisor 提供了一个公开的构造方法,仅仅接收一个 VectorStore 示例,用来从向量数据库中检索相似的数据。定义如下:

public QuestionAnswerAdvisor(VectorStore vectorStore) {
    this(vectorStore, SearchRequest.builder().build(), DEFAULT_PROMPT_TEMPLATE, BaseAdvisor.DEFAULT_SCHEDULER, 0);
}

QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, @Nullable PromptTemplate promptTemplate, @Nullable Scheduler scheduler, int order) {
    Assert.notNull(vectorStore, "vectorStore cannot be null");
    Assert.notNull(searchRequest, "searchRequest cannot be null");
    this.vectorStore = vectorStore;
    this.searchRequest = searchRequest;
    this.promptTemplate = promptTemplate != null ? promptTemplate : DEFAULT_PROMPT_TEMPLATE;
    this.scheduler = scheduler != null ? scheduler : BaseAdvisor.DEFAULT_SCHEDULER;
    this.order = order;
}

虽然有了一个公开的构造方法,但是该类还是提供了一个静态 builder(VectorStore vectorStore) 方法,用来构建 QuestionAnswerAdvisor.Builder 对象,定义如下:

public static Builder builder(VectorStore vectorStore) {
    return new Builder(vectorStore);
}

而该 Builder 对象可以通过链式调用,对 QuestionAnswerAdvisor 进行参数配置,快速构建自定义配置的 QuestionAnswerAdvisor 对象。


QuestionAnswerAdvisor.Builder

QuestionAnswerAdvisor.Builder 是一个用于构建 QuestionAnswerAdvisor 实例的构建器类(Builder Pattern),提供了如下几个成员变量:

public static final class Builder {
    private final VectorStore vectorStore;
    private SearchRequest searchRequest = SearchRequest.builder().build();
    private PromptTemplate promptTemplate;
    private Scheduler scheduler;
    private int order = 0;

    private Builder(VectorStore vectorStore) {
        Assert.notNull(vectorStore, "The vectorStore must not be null!");
        this.vectorStore = vectorStore;
    }

    //...
}

其中:

  • vectorStore   必填,底层向量存储(如 Pinecone、Redis、PgVector 等),用于执行相似性搜索。

  • searchRequest   搜索请求配置(默认空配置),可设置 topK、相似度阈值等参数。

  • promptTemplate  提示模板,用于将搜索结果的文档和用户问题组合成最终提问。

  • scheduler  (可选)调度器,用于控制任务执行顺序(如异步处理)。

  • order  (可选)定义 Advisor 的执行顺序(默认 0)。

注意,如果用户没有提供 promptTempalte,则采用默认的 DEFAULT_PROMPT_TEMPLATE,代码如下:

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\n" +
    "Given the context and provided history information and not prior knowledge,\n" +
    "reply to the user comment. If the answer is not in the context, inform\n" +
    "the user that you can't answer the question.\n");

将上面提示词整理后如下:

{query}

Context information is below, surrounded by ---------------------

---------------------
{question_answer_context}
---------------------

Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context, inform
the user that you can't answer the question.

翻译成中文:

{query}

上下文信息如下, 由 --------------------- 包围

---------------------
{question_answer_context}
---------------------

根据提供的上下文和历史信息进行回复,而不依据已掌握的知识。
若答案不在上下文中,告知用户你无法回答该问题。

下面是一次发起请求的日志信息:

ChatClientRequest[prompt=Prompt{messages=[SystemMessage{textContent='使用中文进行回复', messageType=SYSTEM, metadata={messageType=SYSTEM}}, UserMessage{content='张三是谁?

Context information is below, surrounded by ---------------------

---------------------
张三,男,汉族,1996 年 7 月 26 日出生,现年 30 岁,毕业于四川大学计算机工程系。凭借扎实的专业教育背景,他在技术领域展现出强劲实力。
张三尤其擅长算法设计与优化,能以高效逻辑解决复杂问题;在编程语言方面,对 C/C++ 运用娴熟,凭借该语言的高性能优势,完成众多高质量项目。同时,他在游戏开发领域造诣颇深,从游戏逻辑搭建到画面渲染优化,都能出色完成。此外,他对 AI 有着深入研究,善于将 AI 技术融入游戏开发,为游戏增添智能化体验,是兼具多元技能与创新思维的复合型技术人才。
---------------------

Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context, inform
the user that you can't answer the question.
', properties={messageType=USER}, messageType=USER}], modelOptions=OpenAiChatOptions: {"streamUsage":false,"model":"gpt-3.5-turbo","temperature":0.7}}, context={chat_memory_conversation_id=sessionId-202572020514, qa_retrieved_documents=[Document{id='de8eb35d-cbe8-4b00-a8c7-56f782ffa7c6', text='张三,男,汉族,1996 年 7 月 26 日出生,现年 30 岁,毕业于四川大学计算机工程系。凭借扎实的专业教育背景,他在技术领域展现出强劲实力。
张三尤其擅长算法设计与优化,能以高效逻辑解决复杂问题;在编程语言方面,对 C/C++ 运用娴熟,凭借该语言的高性能优势,完成众多高质量项目。同时,他在游戏开发领域造诣颇深,从游戏逻辑搭建到画面渲染优化,都能出色完成。此外,他对 AI 有着深入研究,善于将 AI 技术融入游戏开发,为游戏增添智能化体验,是兼具多元技能与创新思维的复合型技术人才。', media='null', metadata={distance=0.1451374989197608}, score=0.8548625010802392}]}]

根据日志可知,检索到的文档内容被拼接在 UserMessage(用户消息)中。

  

SearchRequest

在 Spring AI 1.0.0 中,SearchRequest 是用于向向量数据库(VectorStore)发送搜索请求的核心类,它封装了搜索所需的各种参数,包括查询向量、返回结果数量、过滤条件等。

部分源码如下:

public class SearchRequest {
    public static final double SIMILARITY_THRESHOLD_ACCEPT_ALL = 0.0;
    public static final int DEFAULT_TOP_K = 4; // 默认 TOP_K 为 4
    private String query = "";
    // top-K
    private int topK = 4;
    // 相似度阈值
    private double similarityThreshold = 0.0;
    // 过滤表达式
    @Nullable
    private Filter.Expression filterExpression;

    //...
}


简单示例

下面通过使用 Spring AI 内置的 SimpleVectorStore 内存向量数据库来演示 QuestionAnswerAdvisor 的效果。在创建 SimpleVectorStore 时,手动向其中添加一个关于“张三”介绍的文档,后续使用。

创建配置

在 resources 目录下创建 application.yml 配置文件,如下:

spring:
  application:
    name: springai_demo1
  # AI配置
  ai:
    # openai相关配置
    openai:
      # 基础地址
      base-url: https://api.xty.app
      # AI KEY
      api-key: sk-vHTHX8D3w*******8A23e48AbB600
      # 聊天模型配置
      chat:
        options:
          model: gpt-3.5-turbo
      # 图片模型配置
      image:
        options:
          # 需要高级接口
          model: dall-e-3
    chat:
      memory:
        # 会话历史持久化
        repository:
          jdbc:
            # 启动时不自动执行初始化SQL脚本,手动创建数据表
            initialize-schema: never
      client:
        # true 开启,自动注入 ChatClient,false 关闭,需要手动创建 ChatClient
        enabled: true

# 日志配置
logging:
  charset:
    console: UTF-8
  level:
    root: info
    org.springframework.ai: debug


创建配置类

使用 @Configuration 注解创建名为 AiConfig 的配置类,然后创建 VectorStore、QuestionAnswerAdvisor 和 SimpleLoggerAdvisor 的实例,SimpleLoggerAdvisor 用来输出日志信息。如下:

package com.hxstrive.springai.springai_openai.advisor_QuestionAnswerAdvisor.config;

import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * AI配置
 * @author Administrator
 */
@Configuration
public class AiConfig {

    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
        // 使用内存实现的简单向量存储(生产环境需替换为 Pinecone 等)
        SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();

        List<Document> documentList = new ArrayList<>();
        documentList.add(new Document("张三,男,汉族,1996 年 7 月 26 日出生,现年 30 岁,毕业于四川大学计算机工程系。" +
                                      "凭借扎实的专业教育背景,他在技术领域展现出强劲实力。\n" +
                                      "张三尤其擅长算法设计与优化,能以高效逻辑解决复杂问题;在编程语言方面,对 C/C++ 运用娴熟,凭借该语言的高性能优势," +
                                      "完成众多高质量项目。同时,他在游戏开发领域造诣颇深,从游戏逻辑搭建到画面渲染优化,都能出色完成。" +
                                      "此外,他对 AI 有着深入研究,善于将 AI 技术融入游戏开发,为游戏增添智能化体验,是兼具多元技能与创新思维的复合型技术人才。"));
        vectorStore.add(documentList);

        return vectorStore;
    }

    @Bean
    public QuestionAnswerAdvisor questionAnswerAdvisor(VectorStore vectorStore) {
        return QuestionAnswerAdvisor.builder(vectorStore).build();
    }

    @Bean
    public SimpleLoggerAdvisor simpleLoggerAdvisor() {
        return new SimpleLoggerAdvisor(); // 默认配置
    }

}


创建 Controller

创建一个名为 AIController 的 controller,然后将前面创建的 SimpleLoggerAdvisor、QuestionAnswerAdvisor 注入到其中,最后,通过 ChatClient 的 defaultAdvisors() 方法添加默认的 Advisor,如下:

package com.hxstrive.springai.springai_openai.advisor_QuestionAnswerAdvisor.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AIController {

    @Autowired
    private ChatModel chatModel;

    @Autowired
    private SimpleLoggerAdvisor simpleLoggerAdvisor;

    @Autowired
    private QuestionAnswerAdvisor questionAnswerAdvisor;

    @GetMapping("/ai/simple")
    public String completion(@RequestParam("userText") String userText) {
        // 构建 RAG advisor 时需要完成 vectorStore 初始化
        ChatClient chatClient = ChatClient.builder(chatModel)
        .defaultSystem("使用中文进行回复")
        .defaultAdvisors(
            simpleLoggerAdvisor,
            questionAnswerAdvisor
        )
        .build();

        // conversationId 区分不同对话,定位对应的历史记录
        String conversationId = "sessionId-202572020514";
        return chatClient.prompt()
        // 运行时设置会话ID参数
        .advisors(advisor -> {
            advisor.param(ChatMemory.CONVERSATION_ID, conversationId);
        })
        .user(userText)
        .call()
        .content();
    }

}


运行&验证

运行程序,使用浏览器访问 http://localhost:8080/ai/simple 接口,问“张三是谁?”,如下图:

QuestionAnswerAdvisor 增强问答系统 RAG

AI 大模型成功回答了张三是谁,这些回答信息是不是很眼熟。不错,就是我们在创建 VectorStore 是手动添加的“关于张三介绍”的文档。后端服务输出日志如下:

QuestionAnswerAdvisor 增强问答系统 RAG

从日志中可以看出,采用了默认的提示语模板。

如果我们将“关于张三介绍”的文档注释掉,如下:

@Configuration
public class AiConfig {

    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
        // 使用内存实现的简单向量存储(生产环境需替换为 Pinecone 等)
        SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();

//        List<Document> documentList = new ArrayList<>();
//        documentList.add(new Document("张三,男,汉族,1996 年 7 月 26 日出生,现年 30 岁,毕业于四川大学计算机工程系。" +
//                "凭借扎实的专业教育背景,他在技术领域展现出强劲实力。\n" +
//                "张三尤其擅长算法设计与优化,能以高效逻辑解决复杂问题;在编程语言方面,对 C/C++ 运用娴熟,凭借该语言的高性能优势," +
//                "完成众多高质量项目。同时,他在游戏开发领域造诣颇深,从游戏逻辑搭建到画面渲染优化,都能出色完成。" +
//                "此外,他对 AI 有着深入研究,善于将 AI 技术融入游戏开发,为游戏增添智能化体验,是兼具多元技能与创新思维的复合型技术人才。"));
//        vectorStore.add(documentList);

        return vectorStore;
    }

    //...
}

再次重新运行程序,问“张三是谁?”,如下:

QuestionAnswerAdvisor 增强问答系统 RAG

这次,AI 大模型不知道张三是谁了,因为我们没有提供关于张三的知识库信息。后端输出日志如下:

QuestionAnswerAdvisor 增强问答系统 RAG

上述日志中,“Context information is below, surrounded by ---------------------”部分的内容为空。

还有一个疑问!

QuestionAnswerAdvisor 有会话记忆的功能吗?直接通过对话验证就是,问“我刚才问了什么?”,如下:

QuestionAnswerAdvisor 增强问答系统 RAG

从回答可知,AI 不记得刚刚上面我们问的“张三是谁?”这个问题,服务端日志如下:

QuestionAnswerAdvisor 增强问答系统 RAG

关于 QuestionAnswerAdvisor 的更多知识,请参考官方文档和研读源码。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号