Spring AI 实战系列(九):RAG检索实战 —— 私有知识库

张开发
2026/4/3 17:57:09 15 分钟阅读
Spring AI 实战系列(九):RAG检索实战 —— 私有知识库
系列栏目Spring AISpring AI 实战教程一入门示例Spring AI 实战系列二ChatClient封装告别大模型开发样板代码Spring AI 实战系列三多模型共存双版本流式输出Spring AI 实战系列四Prompt工程深度实战Spring AI 实战系列五结构化输出让大模型严格适配你的业务数据模型Spring AI 实战系列六Tool Calling深度实战让大模型自动调用你的业务接口Spring AI实战系列七Chat Memory实战基于Redis实现持久化多轮对话Spring AI 实战系列八多模态能力--文生图、语音合成与向量嵌入实战Spring AI 实战系列九RAG检索实战 —— 私有知识库一、系列回顾与本篇定位1.1 系列回顾第一篇完成 Spring AI 与阿里云百炼的基础集成基于ChatModel实现同步对话。第二篇解锁ChatClient高层级 API告别样板代码。第三篇实现 DeepSeek/Qwen 双模型共存与流式输出。第四篇深度拆解 Prompt 工程掌握与大模型高效沟通的方法论。第五篇掌握结构化输出实现大模型与业务系统的无缝对接。第六篇掌握 Tool Calling让大模型自动调用业务接口。第七篇掌握 Chat Memory基于 Redis 实现持久化多轮对话。第八篇解锁多模态能力 —— 文生图、语音合成与向量嵌入。1.2 本篇定位大模型虽然具备强大的通用知识但它也有明确的能力边界它不知道你的私有业务数据如内部运维手册、产品文档、客户资料。它的知识有截止日期不知道最新的实时信息。它可能会 **“幻觉”**Hallucination编造不存在的事实。而RAGRetrieval Augmented Generation检索增强生成正是解决这些问题的核心技术它通过 “检索私有知识库 大模型基于检索结果生成回答” 的方式让大模型能基于你的私有数据回答问题同时大幅减少幻觉。本篇是系列企业级核心收官篇我们将完整实现一套基于 Spring AI 的 RAG 智能问答系统从核心原理出发深度拆解 RAG 的全流程与 Spring AI 的模块化 RAG 架构。基于 RedisStack 实现向量存储完成文档加载、分块、向量化、存储的全流程。实现带去重逻辑的知识库初始化避免重复加载文档。基于RetrievalAugmentationAdvisor一行配置 RAG实现私有知识库问答。覆盖动态过滤、自定义 Prompt 模板、查询改写等进阶场景。补充生产环境最佳实践与高频踩坑避坑指南。二、核心概念拆解Spring AI RAG 全原理2.1 什么是 RAGRAG 的核心流程非常简单分为三步检索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 拆分为多个可插拔的模块你可以像搭积木一样组合出适合自己业务的 RAG 流程。模块类型作用典型实现Pre-Retrieval检索前处理用户查询提升检索质量RewriteQueryTransformer查询改写、MultiQueryExpander查询扩展、TranslationQueryTransformer查询翻译Retrieval检索从数据源检索相关文档VectorStoreDocumentRetriever向量库检索Post-Retrieval检索后处理检索到的文档提升生成质量文档重排序、去重、压缩Generation生成基于上下文生成最终回答ContextualQueryAugmenter上下文增强而对于大多数常见场景Spring AI提供了两个开箱即用的Advisor一行代码即可完成 RAG 配置QuestionAnswerAdvisor简单场景的RAG Advisor适合快速上手。RetrievalAugmentationAdvisor高级场景的RAG Advisor支持模块化配置适合生产环境。三、实战落地从零构建企业级 RAG 系统3.1 环境前提已完成 JDK 17、Spring Boot 3.2.x 环境搭建已配置阿里云百炼 API Key 环境变量DASHSCOPE_API_KEY已安装并启动RedisStack包含向量搜索能力的 Redis 版本已在pom.xml中引入以下依赖!-- Spring AI Alibaba Starter -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version1.0.0.2/version /dependency !-- Spring AI RAG Advisors -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-advisors-vector-store/artifactId /dependency !-- Spring AI Redis Vector Store -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-redis-store/artifactId /dependency !-- Spring Data Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Hutool工具类用于MD5去重 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.26/version /dependency3.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 RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplateString, 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 # 自动初始化向量库Schema3.3 第二步知识库初始化带去重逻辑这是生产环境非常重要的一步避免重复加载文档。我们通过 Redis 的setIfAbsentSETNX命令实现去重确保同一文档只加载一次。首先在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 RedisTemplateString, 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 ListDocument 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 AdvisorSpring 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; /** * ChatModelChatClientRAG配置类 */ 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.ai.rag.advisor.RetrievalAugmentationAdvisor; import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever; import org.springframework.ai.vectorstore.VectorStore; 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/rag4aiops?msgC00001 * 访问示例http://localhost:8012/rag4aiops?msg数据库连接池耗尽怎么办 */ GetMapping(/ragaiops) public FluxString 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?msgC00001大模型会基于检索到的故障手册给出 CPU 使用率过高的解决方案。访问http://localhost:8080/ragaiops?msg数据库连接池耗尽怎么办大模型会基于语义检索找到对应的故障手册并回答。四、进阶场景4.1 动态过滤基于元数据过滤检索结果如果你的文档有元数据如type、department、version可以通过动态过滤表达式只检索特定类型的文档。GetMapping(/rag4aiops/filtered) public FluxString 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来定制拼接方式。// 在SaaLLMConfig中配置自定义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-1024Token重叠 100-200 Token。代码文档每块256-512Token重叠 50-100 Token代码上下文敏感。长文档每块1024-2048Token重叠 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应用的核心能力它让大模型能基于你的私有数据回答问题同时大幅减少幻觉是将大模型落地到业务系统的最常用方式。八、下篇预告在Tool Calling实战中我们的工具是与Spring Boot应用强绑定的无法跨语言、跨服务复用也无法接入社区丰富的第三方工具生态。下一篇让大模型能以统一的方式连接外部工具、数据源和服务--MCP (Model Context Protocol)传送门Spring AI 实战系列十MCP深度集成 —— 工具暴露与跨服务调用如果本系列教程对你有帮助欢迎点赞、收藏、评论。

更多文章