SpringAI-05-RAG检索增强生成

目录

一、RAG 是什么

​ RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索和文本生成的技术。简单说,就是先检索相关文档,再把文档内容作为上下文,让 AI 基于这些内容生成回答。

传统 AI 的问题:

  • 知识有截止日期(训练数据的时间点)
  • 无法获取实时信息
  • 可能产生幻觉(编造信息)

RAG 的优势:

  • 准确性提升:通过检索真实文档,提供更准确的答案
  • 知识更新:可以检索最新的文档,保持知识更新
  • 减少幻觉:基于真实文档生成,减少错误信息
  • 可解释性:可以展示检索到的文档,提高可解释性

RAG 的应用场景:

  • 知识问答:基于文档库进行问答
  • 文档摘要:基于文档生成摘要
  • 智能客服:基于知识库进行客服问答
  • 代码生成:基于代码库生成代码

二、RAG 的核心概念

向量(Embedding):

  • 将文本转换为数值向量
  • 语义相似的文本,向量也相似
  • 通过向量相似度检索相关文档

向量数据库(Vector Store):

  • 存储文档向量和元数据
  • 支持相似度检索
  • 常见的有 Milvus、Pinecone、Weaviate 等

检索(Retrieval):

  • 将用户查询转换为向量
  • 在向量数据库中检索相似文档
  • 返回最相关的文档片段

增强生成(Augmented Generation):

  • 将检索到的文档作为上下文
  • AI 基于上下文生成回答
  • 提高回答的准确性和相关性

三、RAG 工作流程

完整的 RAG 流程:

  1. 文档加载:从各种来源加载文档(文件、数据库、API 等)
  2. 文档分割:将文档分割成小块(chunks),便于检索
  3. 向量化:使用 Embedding 模型将文档块转换为向量
  4. 存储:将向量存储到向量数据库
  5. 查询向量化:将用户查询转换为向量
  6. 检索:在向量数据库中检索相似文档
  7. 增强生成:将检索到的文档作为上下文,生成回答

流程图:

1
2
3
文档 → 分割 → 向量化 → 存储到向量数据库

用户查询 → 向量化 → 检索相似文档 → 增强生成 → 回答

四、Spring AI RAG 组件

4.1 DocumentReader

​ 文档读取器,负责从各种来源读取文档。

支持的格式:

  • 文本文件(TXT、MD)
  • PDF 文件
  • HTML 页面
  • 数据库

示例:

1
2
3
4
// 从文件读取
Resource resource = new ClassPathResource("documents/knowledge.txt");
DocumentReader documentReader = new TextReader(resource);
List<Document> documents = documentReader.get();

4.2 TextSplitter

​ 文本分割器,负责将文档分割成小块。

为什么需要分割:

  • 文档可能很长,不适合直接向量化
  • 分割后可以更精确地检索相关片段
  • 提高检索效率

分割策略:

  • 固定大小分割:按固定 token 数或字符数分割
  • 语义分割:按语义边界分割,保持语义完整性
  • 自定义分割:实现自定义分割逻辑

4.3 EmbeddingModel

​ 嵌入模型,负责将文本转换为向量。

配置示例:

1
2
# 阿里百炼 Embedding 模型
spring.ai.dashscope.embedding.options.model=text-embedding-v4

使用:

1
2
3
4
5
@Autowired
private EmbeddingModel embeddingModel;

// 将文档转换为向量
List<Double> embedding = embeddingModel.embed("文档内容");

4.4 VectorStore

​ 向量存储,负责存储和检索向量。

Spring AI 提供的实现:

  • SimpleVectorStore:内存向量存储(适合开发测试)
  • MilvusVectorStore:Milvus 向量数据库
  • PineconeVectorStore:Pinecone 向量数据库

使用:

1
2
3
4
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}

4.5 RetrievalAugmentationAdvisor

​ 检索增强顾问,负责在生成过程中检索相关文档。

工作原理:

  1. 拦截用户查询
  2. 将查询转换为向量
  3. 在向量数据库中检索相似文档
  4. 将检索到的文档添加到 Prompt 中
  5. 调用 AI 模型生成回答

配置:

1
2
3
4
5
6
7
8
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(
VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.vectorStore(vectorStore)
.topK(5) // 返回最相似的 5 个文档
.build();
}

五、快速开始

5.1 依赖配置

1
2
3
4
5
6
7
8
9
10
11
<!-- RAG 核心依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

<!-- Embedding 模型(阿里百炼) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

5.2 Embedding 模型配置

1
2
3
4
5
# DashScope API Key
spring.ai.dashscope.api-key=sk-your-api-key

# Embedding 模型配置
spring.ai.dashscope.embedding.options.model=text-embedding-v4

支持的 Embedding 模型:

  • text-embedding-v1:基础版本
  • text-embedding-v2:增强版本
  • text-embedding-v4:最新版本(推荐)

5.3 VectorStore 配置

内存向量存储(开发测试):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
public class RagApplication {

@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}

@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(
VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.vectorStore(vectorStore)
.topK(5)
.build();
}
}

注意:

  • SimpleVectorStore 是内存存储,重启后数据丢失
  • 生产环境建议使用专门的向量数据库(如 Milvus)

5.4 文档加载和索引

完整示例:

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
@Service
public class DocumentService {

@Autowired
private VectorStore vectorStore;

@Autowired
private EmbeddingModel embeddingModel;

/**
* 加载文档并建立索引
*/
@PostConstruct
public void loadDocuments() {
// 1. 读取文档
Resource resource = new ClassPathResource("documents/knowledge.txt");
DocumentReader documentReader = new TextReader(resource);
List<Document> documents = documentReader.get();

// 2. 分割文档
TextSplitter textSplitter = new TokenTextSplitter()
.setChunkSize(1000) // 每个块 1000 token
.setChunkOverlap(200); // 块之间重叠 200 token
List<Document> chunks = textSplitter.split(documents);

// 3. 存储到向量数据库
vectorStore.add(chunks);

log.info("文档加载完成,共 {} 个文档块", chunks.size());
}
}

关键点:

  • chunkSize:每个块的大小,建议 500-2000 token
  • chunkOverlap:块之间的重叠,避免边界信息丢失
  • add() 方法会自动进行向量化并存储

5.5 RAG 查询

Controller 实现:

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
@RestController
@RequestMapping("/api/rag")
public class RagController {

@Autowired
private ChatModel chatModel;

@Autowired
private RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;

@GetMapping(value = "/query", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> query(@RequestParam String question) {
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("""
你是一个知识问答助手,请根据检索到的文档回答问题。
如果文档中没有相关信息,请明确说明。
回答时请引用文档来源。
""")
.defaultAdvisors(retrievalAugmentationAdvisor)
.build();

return chatClient.prompt()
.user(question)
.stream()
.content();
}
}

使用示例:

1
GET /api/rag/query?question=Spring AI 是什么?

工作流程:

  1. 用户提问:”Spring AI 是什么?”
  2. RetrievalAugmentationAdvisor 拦截查询
  3. 将查询转换为向量
  4. 在向量数据库中检索相似文档
  5. 将检索到的文档添加到 Prompt 中
  6. AI 基于文档内容生成回答

六、文档分割策略

6.1 固定大小分割

​ 按固定大小分割文档,简单直接。

1
2
3
4
5
TextSplitter textSplitter = new TokenTextSplitter()
.setChunkSize(1000) // 每个块 1000 token
.setChunkOverlap(200); // 块之间重叠 200 token

List<Document> chunks = textSplitter.split(documents);

适用场景:

  • 结构化文档
  • 对语义完整性要求不高的场景

注意事项:

  • 可能截断句子
  • 语义可能不完整

6.2 语义分割

​ 按语义分割文档,保持语义完整性。

1
2
3
4
5
TextSplitter textSplitter = new SemanticTextSplitter()
.setChunkSize(1000)
.setChunkOverlap(200);

List<Document> chunks = textSplitter.split(documents);

适用场景:

  • 非结构化文档
  • 对语义完整性要求高的场景

注意事项:

  • 需要 Embedding 模型支持
  • 分割速度较慢

6.3 自定义分割器

​ 实现自定义分割逻辑。

1
2
3
4
5
6
7
8
9
10
public class CustomTextSplitter implements TextSplitter {
@Override
public List<Document> split(Document document) {
// 自定义分割逻辑
// 例如:按段落分割、按章节分割等
List<Document> chunks = new ArrayList<>();
// ... 分割逻辑
return chunks;
}
}

适用场景:

  • 特殊格式的文档
  • 需要特定分割规则的场景

七、向量检索策略

7.1 Top-K 检索

​ 返回最相似的 K 个文档。

1
2
3
4
5
6
7
8
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(
VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.vectorStore(vectorStore)
.topK(5) // 返回最相似的 5 个文档
.build();
}

K 值选择:

  • 太小:可能遗漏相关信息
  • 太大:可能包含不相关信息,增加 token 消耗
  • 建议:3-10 之间

7.2 相似度阈值

​ 设置相似度阈值,只返回相似度高于阈值的文档。

1
2
3
4
5
6
// 在检索时设置阈值
List<Document> documents = vectorStore.similaritySearch(
SearchRequest.query("查询内容")
.withTopK(5)
.withSimilarityThreshold(0.7) // 相似度阈值 0.7
);

阈值选择:

  • 太高:可能检索不到文档
  • 太低:可能包含不相关文档
  • 建议:0.6-0.8 之间

7.3 元数据过滤

​ 根据元数据过滤文档。

1
2
3
4
5
6
7
8
9
10
// 添加元数据
Document document = new Document("内容", Map.of("category", "技术文档"));
vectorStore.add(document);

// 检索时过滤
List<Document> documents = vectorStore.similaritySearch(
SearchRequest.query("查询内容")
.withTopK(5)
.withFilterExpression("category == '技术文档'")
);

适用场景:

  • 文档分类
  • 按来源过滤
  • 按时间过滤

八、实际案例

场景: 基于技术文档库进行问答。

文档准备:

1
2
3
4
documents/
├── spring-ai-basics.txt
├── spring-ai-chatclient.txt
└── spring-ai-tools.txt

文档加载:

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
@Service
public class DocumentService {

@Autowired
private VectorStore vectorStore;

@PostConstruct
public void loadDocuments() {
// 加载多个文档
List<Document> allDocuments = new ArrayList<>();

// 加载 Spring AI 基础文档
Resource resource1 = new ClassPathResource("documents/spring-ai-basics.txt");
DocumentReader reader1 = new TextReader(resource1);
allDocuments.addAll(reader1.get());

// 加载 ChatClient 文档
Resource resource2 = new ClassPathResource("documents/spring-ai-chatclient.txt");
DocumentReader reader2 = new TextReader(resource2);
allDocuments.addAll(reader2.get());

// 分割文档
TextSplitter textSplitter = new TokenTextSplitter()
.setChunkSize(1000)
.setChunkOverlap(200);
List<Document> chunks = textSplitter.split(allDocuments);

// 存储到向量数据库
vectorStore.add(chunks);

log.info("文档加载完成,共 {} 个文档块", chunks.size());
}
}

查询接口:

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
@RestController
@RequestMapping("/api/rag")
public class RagController {

@Autowired
private ChatModel chatModel;

@Autowired
private RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;

@GetMapping(value = "/query", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> query(@RequestParam String question) {
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("""
你是一个 Spring AI 技术专家,请根据检索到的文档回答问题。
如果文档中没有相关信息,请明确说明。
回答时请引用文档来源。
""")
.defaultAdvisors(retrievalAugmentationAdvisor)
.build();

return chatClient.prompt()
.user(question)
.stream()
.content();
}
}

使用示例:

1
2
用户:ChatClient 怎么使用?
AI:[检索相关文档] 根据文档,ChatClient 的使用方式如下...

九、常见问题

9.1 检索不到相关文档

问题: 查询时检索不到相关文档。

原因:

  • 文档未正确加载
  • 查询和文档不匹配
  • 相似度阈值过高

解决:

  1. 检查文档是否已加载:查看日志
  2. 优化查询语句:使用更具体的关键词
  3. 降低相似度阈值:从 0.7 降到 0.6

9.2 检索到不相关文档

问题: 检索到的文档和查询不相关。

原因:

  • 文档分割不合理
  • Embedding 模型质量不高
  • Top-K 值太大

解决:

  1. 优化文档分割策略:使用语义分割
  2. 使用更好的 Embedding 模型
  3. 减小 Top-K 值:从 10 降到 5

9.3 生成答案不准确

问题: AI 生成的答案不准确。

原因:

  • 检索到的文档不准确
  • Prompt 设计不合理
  • 上下文过长

解决:

  1. 提高检索质量:优化分割策略、调整 Top-K
  2. 优化 Prompt:明确指示 AI 如何使用文档
  3. 控制上下文长度:减小 Top-K 或 chunkSize

9.4 性能问题

问题: RAG 查询速度慢。

原因:

  • 向量数据库性能问题
  • Embedding 模型调用慢
  • 文档数量过多

解决:

  1. 使用高性能向量数据库(如 Milvus)
  2. 使用本地 Embedding 模型
  3. 优化文档数量:只加载必要的文档

9.5 内存占用高

问题: 使用 SimpleVectorStore 内存占用高。

原因:

  • 内存向量存储,所有数据在内存中
  • 文档数量多

解决:

  1. 生产环境使用专门的向量数据库
  2. 减少文档数量
  3. 优化文档分割:减小 chunkSize

十、最佳实践

10.1 文档质量

确保文档质量:

  • 准确性:文档内容准确
  • 完整性:文档内容完整
  • 时效性:及时更新文档

10.2 分割策略

选择合适的分割策略:

  • 固定大小:适合结构化文档
  • 语义分割:适合非结构化文档
  • 自定义分割:适合特殊需求

分割参数建议:

  • chunkSize:500-2000 token
  • chunkOverlap:100-300 token

10.3 Embedding 模型

选择合适的 Embedding 模型:

  • 中文场景:使用支持中文的模型
  • 领域场景:使用领域特定的模型
  • 多语言场景:使用多语言模型

10.4 向量数据库

选择合适的向量数据库:

  • 开发测试SimpleVectorStore(内存)
  • 生产环境:Milvus、Pinecone、Weaviate 等

10.5 检索策略

优化检索策略:

  • Top-K 选择:根据场景选择合适的 K 值(3-10)
  • 相似度阈值:设置合理的阈值(0.6-0.8)
  • 元数据过滤:使用元数据提高精度

10.6 Prompt 设计

优化 Prompt 设计:

  • 明确指令:明确指示 AI 如何使用文档
  • 格式要求:指定回答格式
  • 引用要求:要求 AI 引用文档来源

示例:

1
2
3
你是一个知识问答助手,请根据检索到的文档回答问题。
如果文档中没有相关信息,请明确说明。
回答时请引用文档来源,格式:[来源1] [来源2]

十一、总结

  1. RAG 原理:通过检索相关文档来增强生成过程,提高回答的准确性
  2. 核心组件:DocumentReader、TextSplitter、EmbeddingModel、VectorStore、RetrievalAugmentationAdvisor
  3. 工作流程:文档加载 → 文档分割 → 向量化 → 存储 → 检索 → 增强生成
  4. 文档分割:固定大小分割、语义分割、自定义分割
  5. 向量检索:Top-K 检索、相似度阈值、元数据过滤
  6. 配置要点:Embedding 模型配置、VectorStore 配置、RetrievalAugmentationAdvisor 配置
  7. 最佳实践:文档质量、分割策略、Embedding 模型、向量数据库、检索策略、Prompt 设计

​ RAG 是 Spring AI 的一个重要功能,可以显著提高 AI 应用的准确性和实用性。在实际项目中,需要根据具体场景选择合适的策略和参数。