前言

什么是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篇晶圆制造技术专利摘要>  
【问题】请对比FinFETGAA晶体管的优缺点
  • 生成优化 结合检索内容进行事实校准,显著降低模型幻觉(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使用

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐