詹学伟
詹学伟
Published on 2025-11-26 / 11 Visits
0
0

saa-rag

一、说明

本文主要介绍spring ai alibaba结合rag实现本地文档知识库的存储和检索,主要技术栈有:spring ai alibaba、阿里百炼模型text-embedding-v3、向量数据库redis stackdeepseek

本章代码统一使用同一个父工程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();
    }
}

七、测试

  1. 启动项目后,文本数据加载到redis stack向量数据库

  2. 接口测试

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


Comment