一、说明
本文主要介绍spring ai alibaba结合rag实现本地文档知识库的存储和检索,主要技术栈有:spring ai alibaba、阿里百炼模型text-embedding-v3、向量数据库redis stack、deepseek。
本章代码统一使用同一个父工程pom,见saa第一期。
二、POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.saa</groupId>
<artifactId>saa-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.saa</groupId>
<artifactId>saa-rag4ops</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>saa-rag4ops</name>
<description>saa-rag4ops</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<!-- 添加 Redis 向量数据库依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>三、properties配置文件
server.port=8090
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
spring.application.name=saa-rag4ops
spring.ai.dashscope.api-key=sk-09c7b571687b46d5a2e25a03fbdd2fd1
spring.ai.dashscope.chat.options.model=deepseek-r1
spring.ai.dashscope.embedding.options.model=text-embedding-v3
spring.data.redis.host=localhost
spring.data.redis.port=16379
spring.data.redis.username=default
spring.data.redis.password=
spring.ai.vectorstore.redis.initialize-schema=true
spring.ai.vectorstore.redis.index-name=custom-index
spring.ai.vectorstore.redis.prefix=custom-prefix:四、错误编码说明文件
100001 系统内部错误,未知异常、服务崩溃、第三方依赖调用失败
100002 服务暂时不可用,服务降级、熔断、限流触发
100003 接口不存在,访问了未注册的接口路径
100004 请求方法不支持,如GET接口用POST请求
100005 权限不足,未登录、登录态过期、无接口访问权限
100006 请求频率限制,接口限流 (如1分钟内请求超过100次)
300001 请求参数格式错误,JSON格式非法、参数类型不匹配(如字符串转数字失败)
300002 必传参数缺失,如userld、token等必填参数未传
300003 参数值非法,参数格式合法但值无效(如手机号长度不对)五、配置类
chat model配置SaaLLMConfig:
package com.saa.rag4ops.config;
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.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhanxuewei
*/
@Configuration
public class SsaLLMConfig {
@Value("${spring.ai.dashscope.api-key}")
private String apiKey;
private static final String MODEL_DEEPSEEK = "deepseek-v3";
private static final String MODEL_QWEN = "qwen-max";
@Bean(name = "deepseek")
public ChatModel deepseek() {
return DashScopeChatModel.builder()
.dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build())
.defaultOptions(DashScopeChatOptions.builder().withModel(MODEL_DEEPSEEK).build())
.build();
}
@Bean(name = "qwen")
public ChatModel qwen() {
return DashScopeChatModel.builder()
.dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build())
.defaultOptions(DashScopeChatOptions.builder().withModel(MODEL_QWEN).build())
.build();
}
@Bean(name = "deepseekChatClient")
public ChatClient deepeekChatClient(@Qualifier("deepseek") ChatModel deepseek) {
return ChatClient.builder(deepseek)
.defaultOptions(ChatOptions.builder().model(MODEL_DEEPSEEK).build())
.build();
}
@Bean(name = "qwenChatClient")
public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) {
return ChatClient.builder(qwen)
.defaultOptions(ChatOptions.builder().model(MODEL_QWEN).build())
.build();
}
}Redis配置类RedisConfig:
package com.saa.rag4ops.config;
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 {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactor);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
初始化向量数据库InitVectorDatabaseConfig:
package com.saa.rag4ops.config;
import cn.hutool.crypto.SecureUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
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;
/**
* 初始化向量数据库
*/
@Slf4j
@Configuration
public class InitVectorDatabaseConfig {
@Autowired
private VectorStore vectorStore;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("classpath:resultCode.txt")
private Resource opsFile;
@PostConstruct
public void init() {
// 1.读取文件
TextReader textReader = new TextReader(opsFile);
textReader.setCharset(Charset.defaultCharset());
// 2.文件转换为向量(开启分词)
List<Document> list = new TokenTextSplitter().transform(textReader.read());
// 3.获取文件名称
String sourceMetadata = (String) textReader.getCustomMetadata().get("source");
String textHash = SecureUtil.md5(sourceMetadata);
String redisKey = "vector-hash:" + textHash;
// 判断是否存入过,redisKey如果可以成功插入表示以前没有过,可以假如向量数据
Boolean retFlag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1");
if (Boolean.TRUE.equals(retFlag)) {
vectorStore.add(list);
} else {
log.warn("向量初始化数据已经加载过,请不要重复操作");
}
}
}
六、控制器
package com.saa.rag4ops.controller;
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.beans.factory.annotation.Autowired;
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;
@RestController
public class RagController {
@Resource(name = "deepseekChatClient")
private ChatClient chatClient;
@Resource
private VectorStore vectorStore;
@GetMapping("/rag4aiops")
public Flux<String> rag(@RequestParam("message") String message) {
String systemPrompt = """
你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。
""";
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).build())
.build();
return chatClient
.prompt()
.system(systemPrompt)
.user(message)
.advisors(advisor)
.stream()
.content();
}
}七、测试
启动项目后,文本数据加载到
redis stack向量数据库
接口测试

接口测试
可以看到,咱们输入一个编码,接口对应回复编码向对应的描述信息。