SpringAI + Redis:构建高性能RAG问答系统的架构设计与实战

# SpringAI + Redis:构建高性能RAG问答系统的架构设计与实战

# 引言:RAG技术为何成为企业AI应用首选

实现成本降低千倍、响应速度秒级的企业级知识库解决方案

在当前AI技术飞速发展的背景下,企业面临着一个核心挑战:如何让大语言模型(LLM)准确掌握企业内部知识并避免产生幻觉(Hallucination)? 检索增强生成(Retrieval-Augmented Generation,RAG)技术应运而生,它通过将信息检索与生成模型相结合,有效解决了这一难题。

Spring AI作为Spring官方推出的AI开发框架,为Java开发者提供了构建AI应用的标准化方案。结合Redis这一高性能向量数据库,我们可以构建出响应迅速、成本可控、易于维护的RAG问答系统。本文将深入探讨这一技术组合的架构设计、核心实现和优化策略。

# 一、RAG技术架构设计

# 1.1 系统整体架构

基于Spring AI和Redis的RAG系统主要包含以下组件:

# 1.2 技术栈选型依据

  • Spring AI:提供统一的AI应用开发接口,支持多种大模型和向量数据库
  • Redis Stack:具备向量搜索能力的高性能内存数据库,适合实时检索场景
  • OpenAI API/本地模型:平衡性能与成本的需求

# 二、环境准备与核心配置

# 2.1 项目依赖配置


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    <!-- Redis 向量存储 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-vector-store-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-redis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
    <!-- 文档解析(支持 Word、Excel 等) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-tika-document-reader</artifactId>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 2.2 应用配置文件

# application.yml
spring:
  ai:
    openai:
      embedding:
        options:
          model: text-embedding-v4 # 使用百炼平台的嵌入模型
    vectorstore:
      redis:
        uri: redis://localhost:6379
        index: knowledge-base
        prefix: "doc:"
        initialize-schema: true

server:
  port: 8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 三、核心实现源码解析

# 3.1 数据加载服务实现

知识库的初始化是RAG系统的基础,需要将文档转换为向量并存储到Redis中。

@Service
@Slf4j
public class DataLoaderService {

    @Value("classpath:knowledge/*.pdf")
    private Resource[] knowledgeResources;

    @Autowired
    private VectorStore vectorStore;

    @PostConstruct
    public void initializeKnowledgeBase() {
        log.info("开始初始化知识库...");

        for (Resource resource : knowledgeResources) {
            try {
                // 使用PDF文档阅读器
                PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(
                    resource,
                    PdfDocumentReaderConfig.builder()
                        .withPagesPerDocument(1)
                        .build()
                );

                // 文本分割器,确保文档块大小合适
                TokenTextSplitter textSplitter = new TokenTextSplitter(
                    1000,  // 最大token数
                    200,   // 重叠token数
                    true   // 分段存储
                );

                // 读取、分割并存储文档
                List<Document> documents = pdfReader.get();
                List<Document> chunks = textSplitter.apply(documents);

                vectorStore.add(chunks);
                log.info("已加载文档: {},分割为 {} 个块", 
                    resource.getFilename(), chunks.size());

            } catch (Exception e) {
                log.error("加载文档失败: {}", resource.getFilename(), e);
            }
        }
        log.info("知识库初始化完成");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 3.2 RAG服务核心逻辑

RAG服务的核心在于实现检索与生成的协同工作。


@Service
@Slf4j
public class RagService {

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private ChatClient chatClient;

    // 相似度搜索配置
    private static final int TOP_K = 5;
    private static final double SIMILARITY_THRESHOLD = 0.7;

    public Generation retrieve(String userQuery) {
        // 1. 向量相似度搜索
        SearchRequest searchRequest = SearchRequest.query(userQuery)
            .withTopK(TOP_K)
            .withSimilarityThreshold(SIMILARITY_THRESHOLD);

        List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);

        if (relevantDocs.isEmpty()) {
            return new Generation("未找到相关信息,请尝试其他问题。");
        }

        // 2. 构建增强提示
        String context = buildContext(relevantDocs);
        String enhancedPrompt = buildEnhancedPrompt(userQuery, context);

        // 3. 调用LLM生成回答
        Prompt prompt = new Prompt(enhancedPrompt);
        ChatResponse response = chatClient.call(prompt);

        return response.getResult();
    }

    private String buildContext(List<Document> documents) {
        StringBuilder contextBuilder = new StringBuilder();
        contextBuilder.append("相关参考信息:\n\n");

        for (int i = 0; i < documents.size(); i++) {
            Document doc = documents.get(i);
            contextBuilder.append(String.format("[%d] %s\n\n", i + 1, doc.getText()));
        }

        return contextBuilder.toString();
    }

    private String buildEnhancedPrompt(String userQuery, String context) {
        return String.format("""
            你是一个专业的客服助手,请根据以下参考信息回答问题。
            如果参考信息不足以回答问题,请明确说明。
            不要编造信息,保持回答准确、简洁。

            %s

            用户问题:%s

            请根据以上信息提供回答:
            """, context, userQuery);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 3.3 控制器层实现

@RestController
@RequestMapping("/api/rag")
@Slf4j
public class RagController {

    @Autowired
    private RagService ragService;

    @PostMapping("/chat")
    public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
        try {
            long startTime = System.currentTimeMillis();

            Generation generation = ragService.retrieve(request.getQuestion());

            long responseTime = System.currentTimeMillis() - startTime;
            log.info("问题处理完成: 问题长度={}, 响应时间={}ms", 
                request.getQuestion().length(), responseTime);

            ChatResponse response = new ChatResponse(
                generation.getOutput().getContent(),
                responseTime
            );

            return ResponseEntity.ok(response);

        } catch (Exception e) {
            log.error("处理问题时发生错误", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ChatResponse("系统繁忙,请稍后重试", -1));
        }
    }

    // DTO类
    @Data
    @AllArgsConstructor
    public static class ChatRequest {
        private String question;
    }

    @Data
    @AllArgsConstructor
    public static class ChatResponse {
        private String answer;
        private long responseTimeMs;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 四、高级特性与优化策略

# 4.1 使用QuestionAnswerAdvisor优化RAG流程

Spring AI提供了Advisor接口来标准化RAG流程的实现。

@Configuration
@Slf4j
public class RagAdvisorConfig {

    @Bean
    public QuestionAnswerAdvisor questionAnswerAdvisor(
            VectorStore vectorStore, 
            ChatClient chatClient) {

        return new QuestionAnswerAdvisor(vectorStore, chatClient) {
            @Override
            public Prompt before(String question) {
                // 自定义检索逻辑
                SearchRequest request = SearchRequest.query(question)
                    .withTopK(5)
                    .withSimilarityThreshold(0.75)
                    .withFilterExpression("category == 'technical'");

                List<Document> docs = vectorStore.similaritySearch(request);

                // 构建系统消息
                SystemMessage systemMessage = new SystemMessage(
                    "你是一个技术专家,请根据以下文档回答问题:\n" + 
                    docs.stream()
                        .map(Document::getText)
                        .collect(Collectors.joining("\n\n"))
                );

                UserMessage userMessage = new UserMessage(question);

                return new Prompt(List.of(systemMessage, userMessage));
            }

            @Override
            public String after(ChatResponse response) {
                // 后处理:添加引用和验证
                String answer = response.getResult().getOutput().getContent();
                return answer + "\n\n*以上信息仅供参考*";
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 4.2 性能优化实践

# 向量索引优化

spring:
  ai:
    vectorstore:
      redis:
        index-type: HNSW  # 使用分层导航小世界算法
        distance-metric: COSINE  # 余弦相似度
        index-options: |
          {
            "EF_CONSTRUCTION": 200,
            "M": 16
          }
1
2
3
4
5
6
7
8
9
10
11

# 缓存策略实现


@Service
@Slf4j
public class CachingRagService {

    @Autowired
    private RagService ragService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final long CACHE_TTL = 3600; // 1小时

    public Generation retrieveWithCache(String userQuery) {
        // 生成查询指纹作为缓存键
        String cacheKey = generateCacheKey(userQuery);

        // 尝试从缓存获取
        String cachedAnswer = redisTemplate.opsForValue().get(cacheKey);
        if (cachedAnswer != null) {
            log.debug("缓存命中: {}", cacheKey);
            return new Generation(cachedAnswer);
        }

        // 缓存未命中,执行RAG流程
        Generation generation = ragService.retrieve(userQuery);

        // 缓存结果
        if (shouldCache(generation)) {
            redisTemplate.opsForValue().set(
                cacheKey, 
                generation.getOutput().getContent(),
                Duration.ofSeconds(CACHE_TTL)
            );
        }

        return generation;
    }

    private String generateCacheKey(String query) {
        return "rag:cache:" + Integer.toHexString(query.hashCode());
    }

    private boolean shouldCache(Generation generation) {
        // 只缓存高质量的回答
        String content = generation.getOutput().getContent();
        return !content.contains("不确定") && !content.contains("无法回答");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 五、实战案例:企业知识库问答系统

基于Spring AI和Redis的RAG系统在实际应用中表现出色:

  • 精准问答:针对"公司请假流程是什么?"等问题,能直接从员工手册中检索相关信息生成准确回答
  • 多文档支持:支持PDF、Word、HTML等多种格式文档的自动处理和向量化
  • 实时更新:知识库更新后,系统能够立即感知并提供最新信息

# 六、总结与展望

Spring AI与Redis的结合为Java开发者提供了构建高性能RAG系统的理想方案。通过本文介绍的架构设计和实现方案,企业可以快速搭建属于自己的智能问答系统,显著提升知识管理效率。

未来,随着Spring AI生态的不断完善,我们可以期待更多高级特性的出现:

  • 多模态RAG:支持图像、表格等非文本内容的检索与生成
  • 自适应学习:系统能够根据用户反馈自动优化检索策略
  • 边缘部署:支持在资源受限环境中运行轻量级RAG系统