一、系列回顾与本篇定位
1.1 系列回顾
本系列已覆盖 Spring AI 核心能力:从基础集成 ChatModel,到 ChatClient 封装,再到多模型共存、Prompt 工程、结构化输出、Tool Calling、Chat Memory 以及多模态能力。这些模块为构建复杂 AI 应用打下了坚实基础。
1.2 本篇定位
大模型虽具备强大的通用知识,但存在明确的能力边界:它不了解你的私有业务数据(如内部运维手册),知识有截止日期,且可能产生'幻觉'。
RAG(检索增强生成)正是解决这些问题的核心技术。通过'检索私有知识库 + 大模型基于检索结果生成回答',让大模型能基于你的私有数据回答问题,同时大幅减少幻觉。
本篇作为系列企业级核心收官篇,我们将完整实现一套基于 Spring AI 的 RAG 智能问答系统:从原理拆解到 RedisStack 向量存储落地,涵盖带去重逻辑的知识库初始化、RetrievalAugmentationAdvisor 配置,以及动态过滤、自定义 Prompt 模板等进阶场景,并补充生产环境最佳实践。
二、核心概念拆解:Spring AI RAG 全原理
2.1 什么是 RAG
RAG 的核心流程分为三步:
- 检索(Retrieval):用户提问时,先从向量数据库中检索出最相关的文档片段。
- 增强(Augmentation):将检索到的片段作为上下文,拼接到用户问题中。
- 生成(Generation):将包含上下文的 Prompt 发送给大模型,基于上下文回答问题。
简单来说:RAG = 向量检索 + 大模型生成。它让大模型能'查资料'后再回答,既保留了语言能力,又注入了私有知识。
2.2 Spring AI 模块化 RAG 架构
Spring AI 1.0+ 版本推出了模块化 RAG 架构,参考了论文《Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks》,将 RAG 拆分为多个可插拔模块,你可以像搭积木一样组合。
| 模块类型 | 作用 | 典型实现 |
|---|---|---|
| Pre-Retrieval(检索前) | 处理用户查询,提升检索质量 | RewriteQueryTransformer(查询改写)、MultiQueryExpander(查询扩展) |
| Retrieval(检索) | 从数据源检索相关文档 | VectorStoreDocumentRetriever(向量库检索) |
| Post-R retrieval(检索后) | 处理检索到的文档 | 文档重排序、去重、压缩 |
| Generation(生成) | 基于上下文生成最终回答 | ContextualQueryAugmenter(上下文增强) |
对于大多数常见场景,Spring AI 提供了两个开箱即用的 Advisor:
QuestionAnswerAdvisor:简单场景,适合快速上手。RetrievalAugmentationAdvisor:高级场景,支持模块化配置,适合生产环境。
三、实战落地:从零构建企业级 RAG 系统
3.1 环境前提
- JDK 17+、Spring Boot 3.2.x 环境
- 阿里云百炼 API Key 环境变量
DASHSCOPE_API_KEY - 已安装并启动 RedisStack(含向量搜索能力)
pom.xml依赖引入:
<!-- Spring AI Alibaba Starter -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.0.0.2</version>
</dependency>
<!-- Spring AI RAG Advisors -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<!-- Spring AI Redis Vector Store -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Hutool 工具类(用于 MD5 去重) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
3.2 第一步:配置 Redis 与向量存储
首先配置 Redis 连接,注册 RedisTemplate 用于去重逻辑,Spring AI 会自动配置 VectorStore(基于 RedisStack)。
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 配置类:用于向量存储与去重逻辑
*/
@Configuration
@Slf4j
public class RedisConfig {
/**
* 配置 RedisTemplate,用于去重逻辑
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置 Key 序列化方式:String
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 设置 Value 序列化方式:JSON
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
application.yml 配置:
spring:
data:
redis:
host: localhost
port: 6379
password: 123456
database: 0
ai:
vectorstore:
redis:
initialize-schema: true # 自动初始化向量库 Schema
3.3 第二步:知识库初始化(带去重逻辑)
这是生产环境非常重要的一步:避免重复加载文档。我们通过 Redis 的 setIfAbsent(SETNX)命令实现去重,确保同一文档只加载一次。
首先在 src/main/resources 目录下准备你的私有知识库文档(例如 code.txt,运维故障手册):
故障编码:C00001
故障描述:服务器 CPU 使用率超过 90%
解决方案:
1. 登录服务器,使用 top 命令查看 CPU 占用最高的进程
2. 如果是 Java 进程,使用 jstack 查看线程栈
3. 优化代码或扩容服务器
故障编码:C00002
故障描述:数据库连接池耗尽
解决方案:
1. 检查数据库连接池配置,适当增加最大连接数
2. 检查是否有连接泄漏,使用 druid 的监控功能
3. 优化慢 SQL,减少连接持有时间
然后编写知识库初始化配置类:
import cn.hutool.crypto.SecureUtil;
import jakarta.annotation.PostConstruct;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import java.nio.charset.Charset;
import java.util.List;
/**
* 知识库初始化配置类:带去重逻辑
*/
@Configuration
public class InitVectorDatabaseConfig {
@Autowired
private VectorStore vectorStore;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 注入私有知识库文档
@Value("classpath:code.txt")
private Resource opsFile;
@PostConstruct
public void init() {
// 1. 读取文档:使用 TextReader 读取文本文件
TextReader textReader = new TextReader(opsFile);
textReader.setCharset(Charset.defaultCharset());
// 2. 文档分块:使用 TokenTextSplitter 将长文档切分为适合向量化的片段
// 默认分块策略:每块 800 Token,重叠 200 Token
List<Document> documents = new TokenTextSplitter().transform(textReader.read());
// 3. 去重逻辑:基于 Redis SETNX 实现,避免重复加载同一文档
// 计算文档源文件的 MD5 作为唯一标识
String sourceMetadata = (String) textReader.getCustomMetadata().get("source");
String textHash = SecureUtil.md5(sourceMetadata);
String redisKey = "vector-initialized:" + textHash;
// SETNX:如果 Key 不存在则设置值并返回 true,否则返回 false
Boolean isFirstLoad = redisTemplate.opsForValue().setIfAbsent(redisKey, "1");
if (Boolean.TRUE.equals(isFirstLoad)) {
// 4. 首次加载:将文档向量化并存入向量数据库
vectorStore.add(documents);
System.out.println("✅ 知识库初始化成功,文档已向量化并存入 RedisStack");
} else {
System.out.println("ℹ️ 知识库已初始化过,跳过重复加载");
}
}
}
关键说明:
- 文档分块:
TokenTextSplitter是 Spring AI 提供的默认分块器,它会基于 Token 数量切分文档,默认每块 800 Token,重叠 200 Token(重叠可以避免上下文丢失)。 - 去重逻辑:通过计算文档源文件的 MD5,结合 Redis 的
setIfAbsent命令,确保同一文档只加载一次,避免服务重启后重复向量化。 - 元数据:
TextReader会自动添加source元数据(文档路径),我们可以基于此做更多扩展。
3.4 第三步:配置 RAG Advisor
Spring AI 提供了 RetrievalAugmentationAdvisor,这是一个模块化的 RAG Advisor,适合生产环境使用。我们将其集成到 ChatClient 中。
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ChatModel+ChatClient+RAG 配置类
*/
@Configuration
public class LLMConfig {
// 模型名称常量定义
private final String DEEPSEEK_MODEL = "deepseek-v3";
private final String QWEN_MODEL = "qwen-plus";
// ==================== ChatModel 实例注册 ====================
@Bean(name = "deepseek")
public ChatModel deepSeekChatModel() {
return DashScopeChatModel.builder()
.dashScopeApi(DashScopeApi.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build())
.defaultOptions(DashScopeChatOptions.builder()
.withModel(DEEPSEEK_MODEL)
.withTemperature(0.1) // RAG 场景建议调低温度,提升回答稳定性
.build())
.build();
}
@Bean(name = "qwen")
public ChatModel qwenChatModel() {
return DashScopeChatModel.builder()
.dashScopeApi(DashScopeApi.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build())
.defaultOptions(DashScopeChatOptions.builder()
.withModel(QWEN_MODEL)
.withTemperature(0.1) // RAG 场景建议调低温度
.build())
.build();
}
// ==================== 带 RAG 的 ChatClient 实例注册 ====================
@Bean(name = "qwenChatClient")
public ChatClient qwenChatClient(
@Qualifier("qwen") ChatModel qwenChatModel,
VectorStore vectorStore) {
// 配置 RAG Advisor
RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(5) // 返回最相关的 5 条文档
.similarityThreshold(0.7) // 相似度阈值,只返回相似度大于 0.7 的文档
.build())
.build();
return ChatClient.builder(qwenChatModel)
.defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build())
.defaultAdvisors(ragAdvisor) // 全局默认启用 RAG
.build();
}
@Bean(name = "deepseekChatClient")
public ChatClient deepseekChatClient(
@Qualifier("deepseek") ChatModel deepSeekChatModel,
VectorStore vectorStore) {
RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(5)
.similarityThreshold(0.7)
.build())
.build();
return ChatClient.builder(deepSeekChatModel)
.defaultOptions(ChatOptions.builder().model(DEEPSEEK_MODEL).build())
.defaultAdvisors(ragAdvisor)
.build();
}
}
关键说明:
- 温度设置:RAG 场景建议将
temperature调低至 0.1-0.3,降低大模型的随机性,让它更严格地基于上下文回答。 - TopK 与相似度阈值:
topK:设置返回最相关的 N 条文档,建议 5-10 条。similarityThreshold:设置相似度阈值,只返回相似度大于该值的文档,避免检索到不相关的内容。
3.5 第四步:实现 RAG 问答接口
最后编写 RAG 问答接口,实现基于私有知识库的智能问答。
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* RAG 智能问答接口
*/
@RestController
public class RagController {
@Resource(name = "qwenChatClient")
private ChatClient chatClient;
/**
* RAG 智能问答接口
* 访问示例:http://localhost:8012/ragaiops?msg=C00001
* 访问示例:http://localhost:8012/ragaiops?msg=数据库连接池耗尽怎么办
*/
@GetMapping("/ragaiops")
public Flux<String> rag(@RequestParam(name = "msg") String msg) {
// 系统提示词:设定 AI 角色,要求它基于上下文回答
String systemPrompt = """
你是一个专业的运维工程师,你的职责是根据提供的运维故障手册,回答用户的故障问题。
请严格遵循以下规则:
1. 如果故障信息在上下文中,请基于上下文给出清晰的解决方案。
2. 如果故障信息不在上下文中,请直接回复'抱歉,未找到该故障的相关信息',不要编造内容。
3. 回答要简洁、专业、有可操作性。
""";
return chatClient
.prompt()
.system(systemPrompt)
.user(msg)
.stream()
.content();
}
}
接口测试说明:
- 启动服务后,知识库会自动初始化(如果是首次加载)。
- 访问
http://localhost:8080/ragaiops?msg=C00001,大模型会基于检索到的故障手册,给出 CPU 使用率过高的解决方案。 - 访问
http://localhost:8080/ragaiops?msg=数据库连接池耗尽怎么办,大模型会基于语义检索,找到对应的故障手册并回答。
四、进阶场景
4.1 动态过滤:基于元数据过滤检索结果
如果你的文档有元数据(如 type、department、version),可以通过动态过滤表达式,只检索特定类型的文档。
@GetMapping("/rag4aiops/filtered")
public Flux<String> ragFiltered(@RequestParam(name = "msg") String msg) {
// 动态过滤:只检索 type 为"database"的文档
return chatClient
.prompt()
.user(msg)
.advisors(advisorSpec -> advisorSpec
.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, "type == 'database'"))
.stream()
.content();
}
4.2 自定义 Prompt 模板:定制上下文拼接方式
默认情况下,RetrievalAugmentationAdvisor 会使用默认的模板拼接上下文和用户问题。你可以通过自定义 PromptTemplate 来定制拼接方式。
// 在配置类中配置自定义 PromptTemplate
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
@Bean(name = "qwenChatClient")
public ChatClient qwenChatClient(
@Qualifier("qwen") ChatModel qwenChatModel,
VectorStore vectorStore) {
// 自定义 Prompt 模板:必须包含{query}和{question_answer_context}两个占位符
PromptTemplate customPromptTemplate = PromptTemplate.builder()
.template("""
你是一个专业的运维工程师。
以下是运维故障手册的相关内容:
---------------------
{question_answer_context}
---------------------
用户的问题是:{query}
请基于上述故障手册回答用户的问题。如果没有相关信息,请直接说'未找到'。
""")
.build();
// 配置 ContextualQueryAugmenter 使用自定义模板
ContextualQueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder()
.promptTemplate(customPromptTemplate)
.build();
RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(5)
.similarityThreshold(0.7)
.build())
.queryAugmenter(queryAugmenter) // 使用自定义的 QueryAugmenter
.build();
return ChatClient.builder(qwenChatModel)
.defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build())
.defaultAdvisors(ragAdvisor)
.build();
}
4.3 查询改写:提升检索质量
如果用户的问题比较模糊或冗长,可以使用 RewriteQueryTransformer 先改写用户问题,再进行检索,提升检索质量。
import org.springframework.ai.rag.preretrieval.query.RewriteQueryTransformer;
@Bean(name = "qwenChatClient")
public ChatClient qwenChatClient(
@Qualifier("qwen") ChatModel qwenChatModel,
VectorStore vectorStore) {
// 配置查询改写:使用大模型改写用户问题
RewriteQueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(ChatClient.builder(qwenChatModel))
.build();
RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(queryTransformer) // 添加查询改写模块
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(5)
.similarityThreshold(0.7)
.build())
.build();
return ChatClient.builder(qwenChatModel)
.defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build())
.defaultAdvisors(ragAdvisor)
.build();
}
五、实践建议
5.1 文档处理最佳实践
- 文档格式:优先使用纯文本(.txt)、Markdown(.md)格式,避免复杂的 Word、PDF 格式(如果必须使用,需要引入对应的文档加载器)。
- 分块策略:
- 通用文档:每块 512-1024 Token,重叠 100-200 Token。
- 代码文档:每块 256-512 Token,重叠 50-100 Token(代码上下文敏感)。
- 长文档:每块 1024-2048 Token,重叠 200-400 Token。
- 元数据管理:给文档添加丰富的元数据(如
type、department、version、author),便于后续动态过滤。
5.2 检索优化最佳实践
- 相似度阈值:根据业务场景调整相似度阈值,一般建议 0.6-0.8。阈值太高会漏检,阈值太低会引入不相关内容。
- TopK 设置:根据文档长度和模型上下文窗口调整 TopK,一般建议 5-10 条。确保检索到的文档总 Token 数不超过模型上下文窗口的 1/3。
- 向量模型选择:优先使用
text-embedding-v3,语义理解能力强,检索准确率高。 - 查询改写:对于用户问题模糊的场景,添加
RewriteQueryTransformer,提升检索质量。
5.3 运维监控最佳实践
- 知识库版本管理:给知识库添加版本号,更新知识库时先清空旧版本,再加载新版本,避免新旧数据混杂。
- 检索效果监控:定期抽查检索结果,记录准确率和召回率,及时调整分块策略和相似度阈值。
- 向量库监控:监控 RedisStack 的内存使用情况、向量数量、查询耗时,及时扩容或优化。
- 成本监控:监控向量模型的 Token 消耗和大模型的 Token 消耗,控制成本。
六、避坑指南
6.1 文档重复加载
- 现象:服务重启后,向量库中的文档数量翻倍。
- 原因:没有去重逻辑,每次启动都重新加载文档。
- 解决方案:参考我们的
InitVectorDatabaseConfig,使用 Redis 的setIfAbsent实现去重。
6.2 检索结果不准确
- 现象:检索到的文档与用户问题不相关。
- 原因:
- 分块策略不合理,文档切得太碎或太大。
- 相似度阈值设置得太低。
- 向量模型选择不当。
- 解决方案:
- 优化分块策略,调整块大小和重叠大小。
- 调高相似度阈值(如从 0.6 调到 0.75)。
- 更换语义理解能力更强的向量模型(如
text-embedding-v3)。
6.3 大模型仍然幻觉
- 现象:即使有 RAG,大模型仍然编造内容。
- 原因:
- 系统提示词不够严格。
- 温度设置得太高。
- 检索到的文档不相关。
- 解决方案:
- 在系统提示词中明确要求'如果没有相关信息,请直接说未找到,不要编造'。
- 调低温度至 0.1-0.3。
- 优化检索策略,确保检索到的文档相关。
6.4 向量库连接失败
- 现象:启动服务时报错,提示无法连接到向量库。
- 原因:
- RedisStack 未启动。
- 配置文件中的连接信息错误。
- 使用的是普通 Redis 而非 RedisStack。
- 解决方案:
- 检查 RedisStack 是否正常启动。
- 核对配置文件中的连接信息。
- 确保使用的是 RedisStack(包含向量搜索模块),而非普通 Redis。
七、本篇总结
本篇我们实现了一套基于 Spring AI 的 RAG 智能问答:
- 从核心原理出发,深度拆解了 RAG 的全流程与 Spring AI 的模块化 RAG 架构。
- 基于 RedisStack 实现了向量存储,完成了文档加载、分块、向量化、存储的全流程。
- 实现了带去重逻辑的知识库初始化,避免重复加载文档。
- 基于
RetrievalAugmentationAdvisor一行配置 RAG,实现了私有知识库问答。 - 覆盖了动态过滤、自定义 Prompt 模板、查询改写等进阶场景。
- 补充了生产环境最佳实践与高频踩坑避坑指南。
RAG 是企业级 AI 应用的核心能力,它让大模型能基于你的私有数据回答问题,同时大幅减少幻觉,是将大模型落地到业务系统的最常用方式。

