Spring AI 学习之路 提问中使用RAG(检索增强生成)

随着生成式 AI 的快速发展,RAG(Retrieval-Augmented Generation,检索增强生成)技术逐渐成为构建智能对话系统的关键。RAG 通过结合检索和生成两种技术,能够生成更准确、更相关的回答。Spring AI 提供了强大的 RAG 支持,其中 QuestionAnswerAdvisor 类是实现这一功能的核心组件。本文将详细介绍如何使用 QuestionAnswerAdvisor 在 Spring AI 中实现 RAG 功能,并构建一个智能对话系统。

1. 什么是 RAG?

RAG 是一种结合了检索(Retrieval)和生成(Generation)的技术,其核心思想是:
检索:根据用户的问题,从知识库中检索出相关的文档或段落。
生成:基于检索到的内容,生成一个更准确、更相关的回答。

RAG 的优势在于:

  • 生成的回答更加准确,因为它基于实际的知识库。
  • 可以动态更新知识库,而无需重新训练模型。
  • 适用于需要结合外部知识的场景,如问答系统、客服机器人等。

2. Spring AI 中的 RAG 支持

Spring AI 提供了对 RAG 的原生支持,核心类是 QuestionAnswerAdvisor。它封装了以下功能:
检索:从向量数据库中检索与用户问题相关的文档。
生成:使用生成模型(如 OpenAI GPT)生成回答。
上下文管理:将检索到的文档作为上下文传递给生成模型。

3. 实现步骤

3.1 环境准备

在开始之前,请确保您已经完成以下准备工作:
Java 开发环境:安装 JDK 8 或更高版本。
Maven 构建工具:用于管理项目依赖。
Spring Boot 项目:创建一个新的 Spring Boot 项目,或者使用现有的项目。
向量数据库:例如 Redis Stack,用于存储和检索文档。
生成模型:例如 OpenAI GPT,用于生成回答。

3.2 添加依赖

pom.xml 中添加 Spring AI 和相关依赖:

			<spring-ai.version>1.0.0-M5</spring-ai.version>
		<dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-starter</artifactId>
			<version>1.0.0-M5.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-redis-store</artifactId>
			<version>${spring-ai.version}</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-tika-document-reader</artifactId>
			<version>${spring-ai.version}</version>
		</dependency>

3.3 配置向量数据库

使用 Redis Stack 作为向量数据库,并配置 VectorStore:

@Configuration
// 禁用SpringAI提供的RedisStack向量数据库的自动配置,会和Redis的配置冲突。
@EnableAutoConfiguration(exclude = {RedisVectorStoreAutoConfiguration.class})
// 读取RedisStack的配置信息
@EnableConfigurationProperties({RedisVectorStoreProperties.class})
@AllArgsConstructor
public class RedisVectorConfig {

    /**
     * 创建并配置JedisPooled实例
     * 该方法通过Spring的@Bean注解定义了一个Bean,使得Spring IoC容器可以管理该Bean的生命周期和依赖
     * JedisPooled是一个封装了Jedis连接池的类,用于高效地管理Redis连接
     *
     * @param redisConnectionDetails Redis连接详情,包含了连接Redis所需的信息,如主机、端口、用户名和密码
     * @return 返回一个配置好的JedisPooled实例,用于与Redis进行交互
     */
    @Bean
    public JedisPooled jedisPooled(RedisConnectionDetails redisConnectionDetails) {
        // 使用从redisConnectionDetails中提取的连接信息初始化JedisPooled实例
        // 包括主机、端口、用户名和密码,这些信息是连接Redis服务器所必需的
        return new JedisPooled(redisConnectionDetails.getStandalone().getHost(),
                redisConnectionDetails.getStandalone().getPort(),
                redisConnectionDetails.getUsername(),
                redisConnectionDetails.getPassword());
    }

    /**
     * 创建RedisStack向量数据库
     *
     * @param embeddingModel 嵌入模型
     * @param properties     redis-stack的配置信息
     * @return vectorStore 向量数据库
     */
    @Bean(name = "redisVectorStore")
    public VectorStore vectorStore(JedisPooled jedisPooled, EmbeddingModel embeddingModel,
                                   RedisVectorStoreProperties properties,
                                   RedisConnectionDetails redisConnectionDetails) {
        return RedisVectorStore.builder(jedisPooled, embeddingModel)
                .indexName(properties.getIndex())                // Optional: defaults to "spring-ai-index"
                .prefix(properties.getPrefix())                  // Optional: defaults to "embedding:"
                .initializeSchema(true)                   // Optional: defaults to false
                .batchingStrategy(new TokenCountBatchingStrategy())// Optional: defaults to TokenCountBatchingStrategy
                .build();
    }
}

3.4 配置生成模型的相关配置

spring:
  data:
    redis:
      host: 192.168.57.185
      port: 10033
      database: 0
      timeout: 10s
      lettuce:
        pool:
          # 连接池最大连接数
          max-active: 200
          # 连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms
          # 连接池中的最大空闲连接
          max-idle: 10
          # 连接池中的最小空闲连接
          min-idle: 0
      repositories:
        enabled: false
      password: 123456
	ai:
	dashscope:
		api-key: xxx-xxx
		chat:
			options:
				model: qwen-max

3.5 创建控制器

在控制器中添加对应的方法。示例代码如下:

private final VectorStore redisVectorStore;

/**
     * 从向量数据库中查找文档,并将查询的文档作为上下文回答。
     *
     * @param prompt 用户的提问
     * @return SSE流响应
     */
    @GetMapping(value = "chat/stream/database", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> chatStreamWithDatabase(@RequestParam String prompt) {
        // 1. 定义提示词模板,question_answer_context会被替换成向量数据库中查询到的文档。
        String promptWithContext = """
                下面是上下文信息
                ---------------------
                {question_answer_context}
                ---------------------
                给定的上下文和提供的历史信息,而不是事先的知识,回复用户的意见。如果答案不在上下文中,告诉用户你不能回答这个问题。
                """;
        return ChatClient.create(chatModel).prompt()
                .user(prompt)
                // 2. QuestionAnswerAdvisor会在运行时替换模板中的占位符`question_answer_context`,替换成向量数据库中查询到的文档。此时的query=用户的提问+替换完的提示词模板;
                .advisors(new QuestionAnswerAdvisor(redisVectorStore, SearchRequest.builder().build(), promptWithContext))
                .stream()
                // 3. query发送给大模型得到答案
                .content()
                .map(chatResponse -> ServerSentEvent.builder(chatResponse)
                        .event("message")
                        .build());
    }

4. 示例对话

假设向量数据库中存储了以下文档:
文档 1:Spring AI 是一个用于构建 AI 应用的框架。
文档 2:RAG 是一种结合检索和生成的技术。

当用户提问 什么是 RAG? 时,系统会:
检索到文档 2。
将文档 2 的内容作为上下文传递给生成模型。
生成回答:RAG 是一种结合检索和生成的技术,用于生成更准确的回答。

5. RAG 提问过程

5.1 用户提问

用户输入一个问题,例如 query = "什么是 RAG?"。

5.2 提示词模板

RAG 使用一个预定义的提示词模板(template),模板中通常包含一个占位符(如 question_answer_context),用于插入从向量数据库中检索到的相关文档。例如:

下面是上下文信息
                ---------------------
                {question_answer_context}
                ---------------------
                给定的上下文和提供的历史信息,而不是事先的知识,回复用户的意见。如果答案不在上下文中,告诉用户你不能回答这个问题。

5.3 向量数据库查询

将用户的问题 query 转换为向量,并在向量数据库中进行相似度搜索。
检索出与问题最相关的文档(context),例如:

context = "RAG 是一种结合检索和生成的技术,用于生成更准确的回答。"

5.4 替换占位符

将提示词模板中的占位符 question_answer_context 替换为检索到的文档 context,生成最终的上下文查询(contextQuery):

contextQuery = template.replace("question_answer_context", context);

例如:

下面是上下文信息
                ---------------------
                RAG 是一种结合检索和生成的技术,用于生成更准确的回答。
                ---------------------
                给定的上下文和提供的历史信息,而不是事先的知识,回复用户的意见。如果答案不在上下文中,告诉用户你不能回答这个问题。

5.5 发送给大模型

contextQuery 发送给大模型,模型基于提供的上下文和问题生成回答。

5.6 生成回答

大模型生成回答,例如:

回答:RAG 是一种结合检索和生成的技术,它通过从知识库中检索相关信息来增强生成模型的能力,从而生成更准确、更相关的回答。

6. 总结

通过 Spring AI 的 QuestionAnswerAdvisor 和 RAG 技术,我们可以轻松构建一个智能对话系统。该系统能够结合外部知识库生成准确、相关的回答,适用于问答系统、客服机器人等场景。

posted @ 2025-04-08 14:30  brother_four  阅读(1088)  评论(0)    收藏  举报