ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

告别大模型幻觉!基于 Spring AI Alibaba 的RAG 运维问答实现

2026/8/21 10:31:36 拓冰建站 浏览量
告别大模型幻觉!基于 Spring AI Alibaba 的RAG 运维问答实现 前言随着大语言模型LLM的广泛应用如何让模型“知道”企业内部知识成为一个关键问题。RAGRetrieval-Augmented Generation技术通过将检索与生成相结合让模型能够基于外部知识库回答问题有效解决了大模型知识滞后和幻觉的问题。本文将手把手教你使用Spring AI Alibaba与Redis Stack 向量数据库构建一个智能运维故障解答系统。系统能够根据故障编码自动检索运维文档并给出对应的故障解释。技术栈Spring Boot 3.x Spring AI Alibaba DashScope 灵积模型服务 Redis Stack 向量存储一、项目背景与需求假设我们有一份运维故障编码说明文档ops.txt内容大致如下00000 系统正常C2222 CPU温度过高A1001 磁盘空间不足...我们要实现一个接口用户输入故障编码如C2222系统能够从文档中检索到对应说明并通过大模型整理成自然语言回复。二、环境准备与版本适配1. Redis Stack 安装Redis Stack 提供了向量检索能力可以直接使用 Docker 快速启动docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest2. 开通阿里云灵积模型服务在阿里云百炼平台 开通 DashScope 服务获取 API Key并确保账户有deepseek-r1、deepseek-v3、qwen-plus、text-embedding-v3等模型的调用权限。3. 重要版本适配说明必须使用 Redis Stack普通 Redis 不支持向量检索请确认运行的是redis-stack或redis-stack-server镜像。嵌入模型固定使用text-embedding-v3保证向量维度与后续检索匹配不可随意切换否则会导致维度不一致。多模型共存时必须指定 Bean 名称项目中同时配置了 DeepSeek 和 Qwen 模型通过Bean(name xxx)配合Qualifier明确注入避免启动时因类型冲突报错。三、项目依赖与配置1. Maven 依赖dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring AI Alibaba DashScope 支持 --dependencygroupIdcom.alibaba.cloud.ai/groupIdartifactIdspring-ai-alibaba-starter-dashscope/artifactId/dependency!-- Redis 向量数据库支持 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-vector-store-redis/artifactId/dependency/dependencies2. application.properties 配置server.port8012# 全局编码server.servlet.encoding.enabledtrueserver.servlet.encoding.forcetrueserver.servlet.encoding.charsetUTF-8spring.application.nameSAA-12RAG4AiDatabase# Spring AI Alibaba 配置spring.ai.dashscope.api-key${aliQwen-api}spring.ai.dashscope.chat.options.modeldeepseek-r1spring.ai.dashscope.embedding.options.modeltext-embedding-v3# Redis Stack 配置spring.data.redis.hostlocalhostspring.data.redis.port6379spring.data.redis.usernamedefaultspring.data.redis.passwordspring.ai.vectorstore.redis.initialize-schematruespring.ai.vectorstore.redis.index-nameatguigu-indexspring.ai.vectorstore.redis.prefixatguigu-prefix注意${aliQwen-api}需要在环境变量中设置避免密钥泄漏。四、核心配置类详解1. 多模型共存配置 – SaaLLMConfig在实际项目中我们可能需要同时接入多个模型如 DeepSeek 和 QwenSpring AI 支持通过Qualifier区分不同的ChatModel和ChatClient。Configurationpublic class SaaLLMConfig {private final String DEEPSEEK_MODEL deepseek-v3;private final String QWEN_MODEL qwen-plus;Bean(name deepseek)public ChatModel deepSeek() {return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()).defaultOptions(DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build()).build();}Bean(name qwen)public ChatModel qwen() {return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()).defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build()).build();}Bean(name deepseekChatClient)public ChatClient deepseekChatClient(Qualifier(deepseek) ChatModel deepSeek) {return ChatClient.builder(deepSeek).build();}Bean(name qwenChatClient)public ChatClient qwenChatClient(Qualifier(qwen) ChatModel qwen) {return ChatClient.builder(qwen).build();}}2. Redis 序列化配置 – RedisConfig为避免 Redis 中出现乱码我们需要自定义RedisTemplate的序列化方式。Configurationpublic class RedisConfig {Beanpublic RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) {RedisTemplateString, Object template new RedisTemplate();template.setConnectionFactory(factory);// key 使用 String 序列化template.setKeySerializer(new StringRedisSerializer());// value 使用 JSON 序列化template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());template.afterPropertiesSet();return template;}}五、文档向量化与初始化存储为什么会出现向量重复入库Spring AI 默认的VectorStore并没有幂等机制。如果你直接将文档切片后的ListDocument调用vectorStore.add()那么每次项目重启时都会重新读取文件、重新生成向量、再次插入数据库导致向量库数据冗余存储膨胀检索时召回多条完全相同或高度重复的片段干扰大模型判断结果重复错乱回答质量下降。本文的解决方案基于文件源 MD5 校验精准定位同一知识库文件无论重启多少次只要源文件未变就不会重复写入。利用 Redis 原子SETNX命令在分布式、单机环境下均能保证初始化操作的幂等性。无需手动清空向量库支持项目反复重启自动跳过已导入的数据。轻量无侵入不改变原有 RAG 检索逻辑只在初始化环节增加防重判断。具体实现如InitVectorDatabaseConfig所示Configurationpublic class InitVectorDatabaseConfig {Autowiredprivate VectorStore vectorStore;Autowiredprivate RedisTemplateString, String redisTemplate;Value(classpath:ops.txt)private Resource opsFile;PostConstructpublic void init() {// 1. 读取文件TextReader textReader new TextReader(opsFile);textReader.setCharset(Charset.defaultCharset());// 2. 分割文档为段落并转为 Document 列表ListDocument documents new TokenTextSplitter().transform(textReader.read());// 3. 生成唯一标识基于文件路径的 MD5String source (String) textReader.getCustomMetadata().get(source);String textHash SecureUtil.md5(source);String redisKey vector-xxx: textHash;// 4. 使用 Redis setIfAbsent 实现初始化防重Boolean firstTime redisTemplate.opsForValue().setIfAbsent(redisKey, 1);if (Boolean.TRUE.equals(firstTime)) {vectorStore.add(documents);System.out.println(向量数据初始化完成);} else {System.out.println(向量数据已存在跳过初始化);}}}核心防重流程使用TextReader获取源文件元数据中的路径source。对 source 进行 MD5 哈希生成固定长度的唯一标识。拼接 Redis Keyvector-xxx:md5值。调用setIfAbsent尝试写入若返回true表示键不存在执行向量写入返回false表示已写入过直接跳过。六、RAG 检索增强接口实现与原理解析6.1 接口实现代码Controller 层使用ChatClient与RetrievalAugmentationAdvisor实现 RAG 问答。RestControllerpublic class RagController {Resource(name qwenChatClient)private ChatClient chatClient;Resourceprivate VectorStore vectorStore;GetMapping(/rag4aiops)public FluxString rag(String msg) {String systemInfo 你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。;// 构建检索增强顾问指定向量数据库检索器RetrievalAugmentationAdvisor advisor RetrievalAugmentationAdvisor.builder().documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).build()).build();return chatClient.prompt().system(systemInfo).user(msg).advisors(advisor) // 注入 RAG 顾问.stream().content();}}流程说明用户传入故障编码msg。RetrievalAugmentationAdvisor调用VectorStoreDocumentRetriever从 Redis 向量库中检索与msg语义最相似的文档片段。将检索到的文档片段作为上下文与 system prompt 和 user message 一起发送给 Qwen 模型。模型根据上下文生成自然语言回答并以流式方式返回。6.2 为什么检索不到时会输出“找不到信息”在测试接口时如果输入的知识库中不存在的编码例如XYZ你会发现模型会直接返回“找不到对应信息”而不是胡编乱造。这背后是一整套严格 RAG 机制在起作用。完整 RAG 流程回顾整个 RAG检索增强生成链路如下用户提问例如问“XYZ 是什么故障”。向量检索把问题文本向量化去 Redis 向量库做相似度搜索。上下文组装将检索回来的文档片段与原始问题一起拼入 Prompt。大模型回答模型只被允许基于 Prompt 中的文档片段作答。产生“找不到信息”的三大原因① Prompt 强约束最核心RAG 系统会在 System Prompt 中明确限制模型的行为典型示例你只能使用【参考文档】里面提供的内容回答用户问题。如果参考文档为空或者没有和问题相关的信息请直接回复找不到对应信息不要编造、不要使用自己的固有知识。【参考文档】{retrieve_context}用户问题{user_query}当向量库检索不到任何内容时{retrieve_context}就会被替换为空字符串。大模型读到“参考文档为空”就会严格遵守指令输出“找不到对应信息”禁止调用模型自身的训练知识。很多新手会困惑大模型明明知道 “XYZ” 可能代表什么为什么不回答——就是因为 Prompt 锁死了不允许使用内部知识只能基于检索回来的上下文。② 向量库确实没有匹配片段如果知识库文件中根本没有与用户提问相关的主题比如根本没有 “XYZ” 这个编码那么文档切片、向量化入库后Redis 里就不存在与 “XYZ” 语义相近的向量用户提问向量化后去 Redis 做近似搜索返回的文档列表为空组装 Prompt 时参考文档部分就是空的最终触发“找不到信息”。③ 相似度阈值过滤向量检索通常会设置一个相似度阈值例如 0.7。即使向量库里有其他文档但如果它们与用户问题的向量相似度全部低于阈值这些文档也会被直接过滤掉最终交给模型的上下文仍然是空。Spring AI Alibaba 的VectorStoreDocumentRetriever支持配置similarityThreshold如果设置了该值那么只有相似度高于阈值的文档才会被采用。两种模式对比模式行为严格 RAG生产常用检索不到内容 → 返回 “找不到信息”禁止幻觉不瞎编宽松 RAG检索不到内容允许大模型使用自身知识回答问题本文的示例就是典型的严格 RAG通过 System Prompt 做了强限制。这样做的好处是杜绝大模型幻觉回答可控、可追溯所有答案都基于企业真实文档适合生产环境。七、测试与验证1. 启动服务确保 Redis Stack 已启动环境变量aliQwen-api已设置然后运行 Spring Boot 应用。启动日志中应看到“向量数据初始化完成”。2. 接口调用查询正常编码GET http://localhost:8012/rag4aiops?msg00000响应系统运行正常无故障。查询已知故障GET http://localhost:8012/rag4aiops?msgC2222响应编码C2222表示CPU温度过高请检查散热系统。查询未知编码GET http://localhost:8012/rag4aiops?msgXYZ响应找不到对应的故障信息。重启应用后再次调用接口观察日志应输出“向量数据已存在跳过初始化”且检索结果依旧准确不会出现冗余重复。八、总结本文通过一个简单的运维问答场景展示了如何利用 Spring AI Alibaba Redis Stack 实现本地知识库的 RAG 应用。核心步骤包括文档预处理读取、分割、向量化。向量存储利用 Redis Stack 存储向量索引。检索增强生成通过RetrievalAugmentationAdvisor自动完成检索与上下文增强。防重初始化使用 Redis 分布式锁思想保证数据只加载一次。严格 RAG 策略通过 Prompt 约束和阈值控制确保模型只基于知识库回答杜绝幻觉。