spring-AI集成向量数据库redis使用实现RAG
本文介绍了SpringAI支持的两种向量数据库:SimpleVectorStore和RedisVectorStore。SimpleVectorStore是基于内存实现的测试/教学用库,通过配置类注入即可使用其文档存储与相似度搜索功能。RedisVectorStore需要安装Redis Stack并引入对应依赖,配置索引信息后可持久化存储向量数据。两种方案都遵循统一的VectorStore接口,支持
前言
什么是RAG,为什么需要RAG?
RAG(Retrieval-Augmented Generation)叫做检索增强生成。简单来说就是把信息检索技术
和大模型结合
的方案。
RAG(检索增强生成):打破大模型的知识边界
——当信息检索遇上生成式AI
一、大模型的知识困境
尽管大语言模型(LLM)在理解与生成能力上表现卓越,但其知识体系存在明显短板:
时间盲区 训练数据的截止日期(如GPT-3到2021年)使其无法感知近期事件,例如无法回答"2023年诺贝尔奖得主是谁?"
领域缺口 通用训练数据难以覆盖垂直领域(如医疗病历、法律条款),导致专业问答准确率骤降
上下文限制 即使最新模型支持200K token上下文(约15万字),仍无法直接处理整本《临床医学手册》级别的知识库
💡 常见误区:直接拼接海量文本到prompt
这会导致关键信息被截断,且模型可能因信息过载而出现"注意力涣散",回答质量反而下降
二、RAG的核心架构
通过检索与生成的协同工作流实现知识动态扩展:
1.检索模块——知识库的"搜索引擎"
智能分块 采用滑动窗口/**语义分割算法,保持文本逻辑完整性(如将论文按"摘要-方法-结论"拆分)
向量化编码 使用BERT/Embedding**模型将文本转换为高维向量,相似内容在向量空间中邻近聚集
精准召回 通过相似度计算(如余弦相似度)从百万级片段中毫秒级检索TOP-K相关内容
2.生成模块——知识的"智能加工厂"
- 上下文工程 采用"角色设定-检索内容-用户问题"的三段式prompt架构,例如:
【角色】你是一名具有半导体行业知识的分析师
【知识】<插入检索到的3篇晶圆制造技术专利摘要>
【问题】请对比FinFET和GAA晶体管的优缺点
- 生成优化 结合检索内容进行事实校准,显著降低模型幻觉(Hallucination)概率
三、RAG的典型应用场景
场景 | 传统LLM痛点 | RAG解决方案 |
---|---|---|
企业知识库问答 | 无法获取内部文档 | 接入Confluence/PDF等私有数据源 |
实时资讯分析 | 无法知晓最新事件 | 检索新闻数据库/社交媒体 |
跨语言专业咨询 | 非英语内容匮乏 | 混合检索多语言知识库 |
RAG就是利用信息检索技术来拓展大模型的知识库,解决大模型的知识限制。整体来说RAG分为两个模块:
- 检索模块(Retrieval):负责存储和检索拓展的知识库
- 文本拆分:将文本按照某种规则拆分为很多片段
- 文本嵌入(Embedding):根据文本片段内容,将文本片段归类存储
- 文本检索:根据用户提问的问题,找出最相关的文本片段
- 生成模块(Generation):
- 组合提示词:将检索到的片段与用户提问组织成提示词,形成更丰富的上下文信息
- 生成结果:调用生成式模型(例如DeepSeek)根据提示词,生成更准确的回答
由于每次都是从向量库中找出与用户问题相关的数据,而不是整个知识库,所以上下文就不会超过大模型的限制,同时又保证了大模型回答问题是基于知识库中的内容,完美!
流程如图:
而提到RAG,就不得不提到向量数据库了:
向量数据库(Vector Database)是RAG架构中的核心基础设施,其作用类似于AI的“外部记忆库”,专门用于高效存储和检索非结构化的语义信息。下面就开始讲解springAI
中向量数据库的使用,以及使用Redis向量数据库
来实现RAG
。
1.向量数据库
-
向量数据库的主要作用有两个:
- 存储向量数据;
- 基于相似度检索数据;
-
SpringAI支持很多向量数据库,并且都进行了封装,可以用统一的API去访问:下面讲解两种常见的向量数据库。
- Redis Vector Store - The Redis vector store.
- SimpleVectorStore - A simple implementation of persistent vector storage, good for educational purposes.
这些库都实现了统一的接口:VectorStore
,因此操作方式一模一样,只要学会任意一个,其它就都不是问题;
2. SimpleVectorStore
- 最后一个
SimpleVectorStore
向量库是基于内存实现,是一个专门用来测试、教学用的库,非常适合此处案例的使用; - 修改
配置类
,添加一个VectorStore
的Bean:
@Bean
public VectorStore vectorStore(OpenAiEmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
查看 VectorStore接口
:里面主要有这些方法
public interface VectorStore extends DocumentWriter {
default String getName() {
return this.getClass().getSimpleName();
}
// 保存文档到向量库
void add(List<Document> documents);
// 根据文档id删除文档
void delete(List<String> idList);
void delete(Filter.Expression filterExpression);
default void delete(String filterExpression) { ... };
// 根据条件检索文档
List<Document> similaritySearch(String query);
// 根据条件检索文档
List<Document> similaritySearch(SearchRequest request);
default <T> Optional<T> getNativeClient() {
return Optional.empty();
}
}
- 注意,
VectorStore
操作向量化的基本单位是Document
,在使用时需要将自己的知识库分割转换为一个个的Document
,然后写入VectorStore
; - 那么问题来了,该如何把各种不同的知识库文件转为Document呢?
在SpringAI中提供了各种文档读取的工具,可以参考官网:ETL Pipeline :: Spring AI Reference;
这里我们使用pdf-document-reade
首先应该引入依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
然后就可以利用工具把PDF文件读取并处理成Document了
编写一个单元测试
// 自动注入向量库
@Autowired
private VectorStore vectorStore;
@Test
public void testVectorStore(){
Resource resource = new FileSystemResource("E:\\aa\\中二知识笔记.pdf");
// 1.创建PDF的读取器
PagePdfDocumentReader reader = new PagePdfDocumentReader(
resource, // 文件源
PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(ExtractedTextFormatter.defaults())
.withPagesPerDocument(1) // 每1页PDF作为一个Document
.build()
);
// 2.读取PDF文档,拆分为Document
List<Document> documents = reader.read();
// 3.写入向量库
vectorStore.add(documents);
// 4.搜索
//List<Document> docs = vectorStore.similaritySearch("论语中教育的目的是什么"); //原始搜索
SearchRequest request = SearchRequest.builder()
.query("论语中教育的目的是什么") // 查询
.topK(1) // 返回的相似文档数量
.similarityThreshold(0.6) // 相似度阈值
.filterExpression("file_name == '中二知识笔记.pdf'") // 过滤条件
.build();
List<Document> docs = vectorStore.similaritySearch(request);
if (docs == null) {
System.out.println("没有搜索到任何内容");
return;
}
for (Document doc : docs) {
System.out.println(doc.getId());
System.out.println(doc.getScore());
System.out.println(doc.getText());
}
}
但是这个存在一个问题,就是不能够持久化保存,接下来将会用redis来讲解如何持久化保存向量
3.RedisVectorStore
3.1安装redis-stack
可以存储向量的redis模型与普通版本不太通,比较占用内存
- 需要安装一个Redis Stack,这是Redis官方提供的拓展版本,其中有向量库的功能;
- 可以使用Docker安装:
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
通过浏览器访问控制台:http://192.168.185.131:8001 (注意换成自己的ip)
3.2引入依赖
在项目中引入RedisVectorStore的依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>
引入这个依赖就不能用普通的redis依赖了,否则会报错
3.3相关配置
在application.yml
配置Redis:
spring:
ai:
vectorstore:
redis:
index: spring_ai_index # 向量库索引名
initialize-schema: true # 是否初始化向量库索引结构
prefix: "doc:" # 向量库key前缀
data:
redis:
host: 192.168.185.131 # redis地址
由于在spring-ai-redis-store-spring-boot-starter
引入了自动配置,所以无需声明bean,直接就可以直接使用VectorStore
了。
3.4测试
// 自动注入向量库
@Autowired
private VectorStore vectorStore;
@Test
public void testVectorStore(){
Resource resource = new FileSystemResource("E:\\aa\\中二知识笔记.pdf");
// 1.创建PDF的读取器
PagePdfDocumentReader reader = new PagePdfDocumentReader(
resource, // 文件源
PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(ExtractedTextFormatter.defaults())
.withPagesPerDocument(1) // 每1页PDF作为一个Document
.build()
);
// 2.读取PDF文档,拆分为Document
List<Document> documents = reader.read();
// 3.写入向量库
vectorStore.add(documents);
}
@Test
public void testRedisVectorStore(){
//List<Document> docs = vectorStore.similaritySearch("论语中教育的目的是什么");
SearchRequest request = SearchRequest.builder()
.query("论语中教育的目的是什么") // 查询
.topK(5) // 返回的相似文档数量
.similarityThreshold(0.6) // 相似度阈值
//.filterExpression("file_name == '中二知识笔记.pdf'") // 过滤条件
.build();
List<Document> docs = vectorStore.similaritySearch(request);
if (docs == null) {
System.out.println("没有搜索到任何内容");
return;
}
for (Document doc : docs) {
System.out.println(doc.getId());
System.out.println(doc.getScore());
System.out.println(doc.getText());
}
}
通过浏览器访问控制台:http://192.168.185.131:8001 (注意换成自己的ip)
可以看到向量切片已经存入到redis了
4.集成配置ChatClient
这里演示一个可以读取pdf的chatClient
@Bean
public ChatClient pdfChatClient(OpenAiChatModel model, ChatMemory chatMemory, VectorStore vectorStore) {
return ChatClient.builder(model)
.defaultSystem("请根据提供的上下文回答问题,遇到上下文没有的问题,不要自己随意编造。")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // 聊天记忆
new SimpleLoggerAdvisor(), //日志
new QuestionAnswerAdvisor(
vectorStore, // 向量库(这里注入的就是redisvectorStore)
SearchRequest.builder() // 向量检索的请求参数
.similarityThreshold(0.5d) // 相似度阈值
.topK(2) // 返回的文档片段数量
.build()
)
)
.build();
}
然后测试一下
@Autowired
private ChatClient pdfChatClient;
public String chatpdf(String prompt, String chatId) {
//3. 请求模型
return pdfChatClient.prompt()
.user(prompt)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)) //聊天记忆
.call() //阻塞式调用
.content();
}
@Test
public void testPdfChat() {
String prompt="论语中教育的目的是什么";
String chatId = "15";
System.out.println(chatpdf(prompt, chatId));
}
结果发现和pdf中的内容一致
如果不想下载redis,可以使用线上的向量数据库
pinecone
关于spring-AI集成向量数据库pinecone使用,可以查看博主的另外一篇文章spring-AI集成向量数据库pinecone使用
更多推荐
所有评论(0)