SpringAI+RAG

上一篇链接

依旧参照该课程

概述

  1. 目的:弥补训练过程中模型知识的不足,提升模型在特定领域的表现,尤其涉及到了私有数据

  2. 流程:

    简言之就是:文本切分,向量化,存入库,查询时把问题向量化,按相似度查询,拿到相关文本输入到模型上下文中,生成回答。

    • 如何向量化?有专用的embedding模型,其架构类似于transformer的编码器部分,输入文本,输出一个向量。(这里对应用来说不太重要)

准备工作

  1. 要在一个linux里安装docker,安装步骤:

    1. 首先需要一台虚拟机
      CentOS7安装教程
    2. 然后windows远程连接测试
      MobaXterm使用
    3. 安装
      docker安装官方步骤
    4. 安装以后记得配置镜像
      我用的毫秒镜像提供的脚本一键配置
  2. 然后选择一个向量数据库,可以选择redis-stack,安装命令:
    docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest

    在IDEAL中数据库新建一个redis数据源的,配置主机号(ifconfig查询),端口号(6379),用户密码都不用写,数据库写0,测试链接即可。

    除了redis,还可以选择elasticsearchneo4joracle等向量数据库,springAI都提供了相应的starter依赖。

  3. spring中引入 向量数据库 依赖:

    复制代码
    <dependency>
       <groupId>org.springframework.ai</groupId>
       <artifactId>spring-ai-starter-vector-store-redis</artifactId>
    </dependency>
  4. 配置yml文件,连接向量数据库:

    因为embedding模型deepseek没有,所以用的智谱的embedding模型,springai支持的embedding模型可以查阅官网

    yaml 复制代码
    spring:
      ai:
        zhipuai:
          api-key: 你的apikey
          embedding:
            options:
              model: embedding-3
              dimensions: 256
    
        deepseek:
          api-key: 你的apikey
    
        vectorstore:
          redis:
            initialize-schema: true
            prefix: "myrag:prefix"
            index-name: "myrag:index"
    
      data:
        redis:
          host: 你的主机号
          port: 6379
          password: 你的密码(如果有的话)
    server:
      port: 8080
      servlet:
        encoding:
          charset: UTF-8
          enabled: true
          force: true

VectorStore

通过流程发现,需要实现两个接口,一个是文本转向量加入数据库,一个是向量数据库的查询。引入了redis向量数据库依赖之后,自动在容器中添加了VectorStore的对象,这个对象可以实现这两个接口,直接调用即可。

java 复制代码
package com.example.springai.controller;

import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 两个接口:文本转向量加入数据库,向量数据库的查询
@RestController
@RequestMapping("/rag")
public class RAGController {

    // 引入了redis向量数据库依赖之后,自动在容器中添加了VectorStore的对象,这里直接注入即可
    private final VectorStore vectorStore;

    public RAGController(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    // 文本转向量加入数据库,调用vectorstore的add方法即可
    @PostMapping("/add")
    public String addTextToVectorStore(String text) {
        Document document = Document.builder().text(text).build();
        vectorStore.add(List.of(document));
        return "added";
    }

    // 向量数据库的查询
    @PostMapping("/query")
    public List<Document> queryVectorStore(String query) {

        SearchRequest searchRequest = SearchRequest.builder().topK(2).similarityThreshold(0.8) // 可以设置返回的结果数量和相似度阈值
                .query(query).build();
        List<Document> documents = vectorStore.similaritySearch(searchRequest); // 还可以继续.get(0).getText(),意思是获取list中第一个document的内容。

        return documents;

    }
}

案例 - 智能客服

目标

准备一个csv文件作为知识库。把这个csv文件的每一行作为一个document对象,调用vectorStore.add()方法把它们加入向量数据库中,

构建一个advisor,在调用模型之前先从向量数据库中查询相关的document,把查询到的document加入到输入中,再调用模型生成回答。

准备

csv解析依赖、RAG依赖

这里我贴完csv之后不知道为什么org.apache.commons.csv.CSVFormat;里面csv或者CSVFormat交替爆红,怎么reload都不管用,后来发现把依赖删了重新再粘一下再reload就好了。。。

xml 复制代码
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-csv</artifactId>
   <version>1.10.0</version>
</dependency>
xml 复制代码
<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-rag</artifactId>
</dependency>

RAG实现

CoffeeController.java

java 复制代码
package com.example.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/coffee")
public class CoffeeController {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public CoffeeController(
            VectorStore vectorStore,ChatClient.Builder
            chatClientBuilder)
    {
        this.vectorStore = vectorStore;

        VectorStoreDocumentRetriever vectorStoreDocumentRetriever =
                VectorStoreDocumentRetriever.builder()
                        .vectorStore(vectorStore)
                        .topK(3)
                        .similarityThreshold(0.5)
                        .build();

        RetrievalAugmentationAdvisor retrievalAugmentationAdvisor =
                RetrievalAugmentationAdvisor.builder()
                        .documentRetriever(vectorStoreDocumentRetriever)
                        .build();
        this.chatClient = chatClientBuilder
                .defaultAdvisors(retrievalAugmentationAdvisor)
                .build();
    }

    @GetMapping("/import")
    public String importCSV() {
        try {
            // 读取classpath下的QA.csv文件
            ClassPathResource resource = new ClassPathResource("QA.csv");
            InputStreamReader reader = new
                    InputStreamReader(resource.getInputStream());

            // 使用Apache Commons CSV解析CSV文件
            CSVParser csvParser = CSVFormat.DEFAULT
                    .builder()
                    .setHeader()  // 第一行作为标题
                    .setSkipHeaderRecord(true)  // 跳过标题行
                    .build()
                    .parse(reader);

            List<Document> documents = new ArrayList<>();

            // 遍历每一行记录
            for (CSVRecord record : csvParser) {
                // 获取问题和回答字段
                String question = record.get("问题");
                String answer = record.get("回答");

                // 将问题和回答组合成文档内容
                String content = "问题: " + question + "\n回答: " + answer;

                // 创建Document对象
                Document document = new Document(content);

                // 添加到文档列表
                documents.add(document);
            }

            // 关闭解析器
            csvParser.close();

            // 将文档存入向量数据库
            vectorStore.add(documents);

            return "成功导入 " + documents.size() + " 条记录到向量数据库";
        } catch (IOException e) {
            e.printStackTrace();
            return "导入失败: " + e.getMessage();
        }
    }

    /**
     * 新增的RAG问答接口,明确展示查询向量数据库的过程
     * @param question 用户的问题
     * @return AI基于检索到的信息生成的回答
     */
    @GetMapping("/rag-ask")
    public String ragAskQuestion(String question) {
        // 先从向量数据库中检索相关信息
        // 这里会使用RetrievalAugmentationAdvisor自动检索相关文档

        // 将问题和检索到的上下文一起发送给AI模型生成回答
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }

}

RetrievalAugmentationAdvisor流程:

  1. 构建初始 Query
    从 ChatClientRequest 的用户消息、指令/历史和上下文构造一个 Query。
  2. 查询预处理:转换
    按顺序应用 QueryTransformer 链对原始查询做转换(拼写、规范化、补充信息等)。
  3. 查询扩展
    使用 QueryExpander 将一个查询扩展成多个候选查询(可选,多查询检索策略)。
  4. 并发检索
    对每个扩展后的查询异步调用 DocumentRetriever.retrieve(通过 TaskExecutor 和 CompletableFuture 实现并发)。 getDocumentsForQuery 封装每个查询到文档集合的映射。
  5. 多源/多查询合并
    将不同查询/数据源返回的文档集合交给 DocumentJoiner(默认 ConcatenationDocumentJoiner)合并成最终文档列表。
  6. 文档后处理
    依次通过 DocumentPostProcessor 对文档做过滤、排序、去重或裁剪等处理。 把最终文档放入请求上下文(键为 rag_document_context)。
  7. 查询增强
    使用 QueryAugmenter(默认 ContextualQueryAugmenter)把检索到的文档上下文注入到原始查询文本,生成增强后的用户提示。
  8. 更新请求并继续调用链
    用增强后的文本构造新的 ChatClientRequest(通过 mutate()),并把文档上下文放入 context,然后返回给下游模型调用。
  9. 响应后处理(after)
    在 after 中把上下文里的 rag_document_context 放入 ChatResponse.metadata,使得最终响应包含检索到的文档信息。
相关推荐
冰小忆2 小时前
类变量在继承场景下的初始化规则是怎样的?
java·前端·数据库
Agent手记2 小时前
制造业生产安全隐患智能识别系统落地指南 —— 结合企业级Agent构建国产安全闭环防御体系
人工智能·安全·ai
搬砖的小码农_Sky2 小时前
NVIDIA Geforce RTX 5060 Ti显卡能本地部署的哪些AI应用?
人工智能·ai·gpu算力·agi
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第71题】【Mysql篇】第1题:索引是什么?
java·开发语言·b树·mysql·面试
AC赳赳老秦2 小时前
OpenClaw碎片时间利用:设置轻量化自动化任务,高效利用职场碎片时间
java·大数据·运维·服务器·数据库·自动化·openclaw
geinvse_seg2 小时前
QuantDinger 全网最全保姆级教程:5分钟搭建AI量化系统
人工智能·ai·蓝耘元生代·蓝耘maas
钮钴禄·爱因斯晨2 小时前
秋天的第一个项目,飞算JavaAI一小时拿下~
java·人工智能
九皇叔叔2 小时前
VMware 安装 麒麟操作系统
java·开发语言·虚拟机·麒麟操作系统·vmware安装
yuhuofei20212 小时前
【Python入门】Python中字符串相关拓展
android·java·python