SpringAI对话和知识库

第一章 RAG

  在当今这样一个快速发展的技术时代,人工智能(AI)已经成为各行各业的一种标配。而作为一款主流的Java应用开发框架Spring,肯定会紧跟时代的潮流,所以,推出了Spring AI框架。

1.1 什么是RAG

  RAG(Retrieval Augmented Generation,简称RAG),也叫检索增强生成。

  RAG是一种将大规模语言模型(LLM)与外部知识源的检索相结合,以改进问答能力的技术。

  注意,上面重复强调了几次 检索 这两个字,检索就是查询,说白了RAG就是一门让AI回答(查询)更加精准的技术,避免答非所问,或者瞎答(看似回答的相关性很高,实际上一点作用都没有)

  另外RAG是一个概念,并不是一门具体的技术。具体的技术比如Kafka,可以启动、发送消息、消费消费。RAG就不行,首先,光是启动就没有

1.2 为什么要知识库

  1、不管是deepseek还是OpenAI,都是通用大模型,看上去啥都会,但是很多地方回答地也不专业,因为缺乏专业知识训练。比如你问deepseek:歼20是怎么制造的、图纸长什么样子。这种国家机密你能在互联网找到吗?肯定找不到,没有专业知识训练,大模型肯定也无法知道答案

  2、对企业来说,公司内部数据不能外泄。比如你公司的五险一金基数和比例是什么?有没有足额缴纳?上班是双休还是单休?965还是996。这些信息能外泄在互联网上公开吗?肯定不能啊,你一公开,所有人都能查到你们公司五险一金按照2300的5%去交,尽管这已经不公开的秘密,但是晚一天公开,或许就能多压榨一天,多赚一天牛马的钱

  3、很多行业需要专业、严谨的回答,而不是大白话的那种,比如医疗、法律、财税等

1.3 RAG工作流程概述

第一,用户输入问题

​   用户在输入窗口输入自己的问题,这一数据被接收,并作为后续处理的查询入口

  例如:用户提问

“国内996、工资又低的公司有哪些?”

第二,问题向量化

  根据用户初始输入的问题,调用Embedding模型,将问题转换为高维向量,以便于后续的想来那个相似度检索。

文本:"国内996、工资又低的公司有哪些?"
→ 向量:[0.125, 0.502, ..., 0.001]

第三,向量数据库检索

  系统会连接到一个向量数据库(如FAISS、Milvus、Pinecone、Weaviate)。然后用刚才生成的问题向量,检索知识库中与之最相似的文档片段。我们这个案例是使用SimpleVectorStore(内存向量数据库,不需要安装)

  当检索的时候,常见的检索参数包括:

  • Tok-K :检索最相关的K条记录
  • 相似度阈值:控制检索到内容的相关性

  最后输出的结果往往是K条知识片段

1. "有软通动力,著名的外包公司,加班是常时,工资低的可怕。"
2. "广州骏伯网络科技,五险一金按照2300的5%来交。(为什么我知道,因为我面过)"
3. "华为OD,加班超级严重"

第四,构建上下文

  这一阶段需要组织提示词(Prompt),让LLM更好地理解背景信息。

  这一部分包括:

  • 系统提示词(System Prompt)

  提前告诉LLM需要遵循的行为规范,比如

你是一个专业的查询公司信息、网评的人。请基于提供的背景资料,准确回答用户的问题。如果资料中没有明确答案,请如实告诉用户而不是编造。

  系统提示词可以有效地设定模型角色、控制回答风格、防止幻觉

  • 构造最终输入(Final Prompt)

    一般会结合以上内容,按照如下格式进行组织

    【背景资料】
    1. 有软通动力,著名的外包公司,加班是常时,工资低的可怕。
    2. 广州骏伯网络科技,五险一金按照2300的5%来交。
    3. 华为OD,加班超级严重。
    
    【用户问题】
    国内996、工资又低的公司有哪些?
    
    【回答要求】
    请结合以上资料,用简洁明了的方式回答用户的问题。如果答案无法直接从资料中找到,请礼貌告知用户。
    

第五,调用LLM

  将构造好的Prompt提交给LLM(比如Deepseek、Qwen、GPT-4o、Claude等)

  • 模型读取检索到的内容和问题
  • 组织自然、连贯、准确的回答

  生成结果示例:

您好! 根据我们的资料,国内出了阿里、腾讯等几个互联网大厂给的工资足够高外,其他绝大部分公司都是福利待遇特别低,加班还超级严重,比如广州骏伯网络科技,五险一金按照2300的5%来交”

第六,返回最终回答给用户

  最终系统将生成的回答返回前端,展示给用户。

总结:

  在RAG工作时,其运行流程大致为:

  1. 用户输入问题
  2. 问题向量化
  3. 向量数据库检索
  4. 构建上下文(含系统提示词)
  5. 携带检索内容,调用大模型进行回答
  6. 返回最终答案给用户

第二章 Spring AI实现RAG

2.1 父工程的pom



<!--集成SpringAI 正式版-->
<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-bom</artifactId>
	<version>1.0.0</version>
	<type>pom</type>
	<scope>import</scope>
</dependency>

  注意我是使用SpringAI的正式版,网上很多教程都是针对Spring AI的1.0.0-M6或者1.0.0-M8版本,这些都是非正式版,今年5月才出的1.0.0正式版,版本号就是1.0.0,别写错了

2.2 子工程的pom

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

<!--SpringAI 读取PDF文件需要引入这个依赖,读其他文件,可能需要引入其他依赖-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

 <dependency>
     <groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

2.4 yml配置

server:
  port: 8007


spring:
  application:
    name: rent-ai

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/rent-house
    username: root
    password: admin123

  ai:
    openai:
      # 填写阿里云百炼平台的url,而不是openai的url
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      # 百炼平台上申请的key
      api-key: sk-c9b1616dcebt94d487eb924682c9f3a0a477
      chat:
        options:
          # 模型的名字
          model: qwen-max-latest
          # 模型温度,值越大,输出结果越随机
          temperature: 0.8
      #向量配置
      embedding:
        options:
          # 向量/嵌入模型名称,这里的向量模型名称叫:通用文本向量-v3
          model: text-embedding-v3
          # 向量维度,1024表示1024维空间的向量
          dimensions: 1024

  这里还是用的阿里百炼大模型,需要去申请api-key,怎么申请,看我前面的文章

2.5 配置类

package com.huqing.icu.rentai.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * @Description 知识库配置类,这个配置是参照尚硅谷的
 * @Author huqing
 * @Date 2025/6/7 14:07
 **/
@Configuration
public class RagConfig {


    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一个打工人,对用户的问题作出解答").build();
    }

    @Bean
    VectorStore vectorStore(EmbeddingModel embeddingModel) {
        SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(embeddingModel).build();

        //生成一个文本
        List<Document> documentList = List.of(
                new Document("打工人的理想就是: 不再打死工,开一家自己的公司,实现小级别的财富自己")
        );
        //把文本存到向量知识库
        simpleVectorStore.add(documentList);
        return simpleVectorStore;

    }
}

  Document:文本对象,存储文字内容

  simpleVectorStore:简单向量数据库,基于内存的,不需要安装部署,如果使用Redis、ES等作为向量数据库,那就得部署。并且Spring AI官方文档也说了,适合教育目的

image-20250607191142891

  向量知识库中存的是文本吗?错,存的是向量。可能大家都毕业大多年了,数学知识早就还给老师了。

  向量可以理解为(x, y)的坐标轴,这是二维向量,A和B两个点,在坐标周中越近,说明相关性很强,越远说明没啥关系,那A和B两个点在坐标轴上存的是文字吗?肯定不是,存的肯定是数字,比如A(1,1) B(2, 2)。同理,一段文字存到向量数据库中,存的就是数字。这是拿二维举例,三维也是同理,三维就是(x, y, z)了,也就是长宽高,我们人类的生存空间就是三维的。那有四维吗?有,在我们大脑的想象中,有三维就有四维,甚至千维。那维数越大,向量化后的数字就越精细、越精准,查询时自然也越精准

2.6 Controller

package com.huqing.icu.rentai.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

;

/**
 * @Description 知识库接口,这个接口是参考参照尚硅谷的
 * @Author huqing
 * @Date 2025/6/7 14:11
 **/
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/rag/chat")
@Tag(name = "AI对话接口")
public class RagController {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private VectorStore vectorStore;


    @GetMapping(value = "/v1", produces = "text/html;charset=utf-8")
    public Flux<String> chatV1() {
        String prompt = "打工人的理想是是什么";

        //.advisors(new QuestionAnswerAdvisor(vectorStore)),没有加这个代码,就不会去向量数据库检索,而是大模型自己回答
        return chatClient.prompt().user(prompt).stream().content();
    }

    @GetMapping(value = "/v2", produces = "text/html;charset=utf-8")
    public Flux<String> chatV2() {
        String prompt = "打工人的理想是什么";

        //.advisors(new QuestionAnswerAdvisor(vectorStore)),这个代码就是为了去向量数据库检索
        return chatClient.prompt().user(prompt).advisors(new QuestionAnswerAdvisor(vectorStore)).stream().content();
    }
}

  在对话时,怎么才能去知识库中查呢?使用QuestionAnswerAdvisor。当用户问题发送至AI模型时,QuestionAnswerAdvisor会查询向量数据库获取与问题有关的文档。如果不用QuestionAnswerAdvisor,比如v1接口,那AI模型就自由发挥了

2.7 测试

  先测试v1接口,不去知识库中查,由AI自由发挥

image-20250607205736178

  再测试v2接口,去知识库中查

image-20250607205425782

  可能有些人会说我问的问题跟写入知识库的文本几乎一模一样,那我现在换个问题

image-20250607210156908

  AI回答

image-20250607210219278

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐