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 技术,我们可以轻松构建一个智能对话系统。该系统能够结合外部知识库生成准确、相关的回答,适用于问答系统、客服机器人等场景。

浙公网安备 33010602011771号