SpringAI+DeepSeek大模型应用开发——5 ChatPDF

ChatPDF 知识库

RAG检索增强

由于训练大模型非常耗时,再加上训练语料本身比较滞后,所以大模型存在知识限制问题:

  • 知识数据比较落后,往往是几个月之前的;不包含太过专业领域或者企业私有的数据;

  • 为了解决这些问题,就需要用到RAG了。

RAG原理

RAG 的核心原理是将检索技术与生成模型相结合,结合外部知识库来检索相关信息来增强模型的输出,其实就是给大模型挂一个知识库

其核心工作流程分为三个阶段:

  1. 接收请求: 首先,系统接收到用户的请求(例如提出一个问题)
  2. 信息检索®: 系统从一个大型文档库中检索出与查询最相关的文档片段。这一步的目标是找到那些可能包含答案或相关信息的文档。这里不一定是从向量数据库中检索,但是向量数据库能反应相似度最高的几个文档(比如说法不同,意思相同),而不是精确查找
  3. 生成增强(A): 将检索到的文档片段与原始查询一起输入到大模型(如chatGPT)中,注意使用合适的提示词,比如原始的问题是XXX,检索到的信息是YY,给大模型的输入应该类似于: 请基于YYY回答XXXX。
  4. 输出生成(G): 大模型LLM 基于输入的査询和检索到的文档片段生成最终的文本答案,并返回给用户

注意:知识库不能写在提示词中,因为通常知识库数据量都是非常大的,而大模型的上下文是有大小限制的,那怎么办呢?

只要想办法从庞大的知识库中找到与用户问题相关的一小部分,组装成提示词,发送给大模型就可以了;那么该如何从知识库中找到与用户问题相关的内容呢?

  • 全文检索?但在这里是不合适的,因为全文检索是文字匹配,而这里要求的是内容上的相似度;
  • 而要从内容相似度来判断,这就不得不提到向量模型的知识了。

向量模型

向量是空间中有方向和长度的量,空间可以是二维,也可以是多维;向量既然是在空间中,那么两个向量之间就一定能计算距离

向量之间的距离一般有两种计算方法:

欧几里得距离

在n维空间中,两点间的直线距离。它是两点间最直接的距离测量方式。很适合用于RGB色彩空间中衡量两种颜色之间的差异

颜色可以用 RGB 值表示,然后通过计算两种颜色 RGB 值之间的欧几里得距离来判断它们的相似度。

在这里插入图片描述

  • R G B: 两个颜色的 RGB 分量(红色、绿色、蓝色)
  • d: 两个颜色之间的欧几里得距离。
  • 距离越小,表示颜色越相似; 距离越大,表示颜色越不同
余弦相似度

通过比较两个向量之间的夹角余弦值来衡量它们的方向是否相似,如果夹角余弦值越小,说明它们越相似,但这种方法不能考虑到向量的大小。

在颜色分析中,它可以用来比较颜色 色调的相似性,但是它对于亮度和饱和度的变化不敏感。
在这里插入图片描述
综上,如果能把文本转为向量,就可以通过向量距离来判断文本的相似度了;

现在有不少的专门的向量模型,就可以实现将文本向量化。一个好的向量模型,就是要尽可能让文本含义相似的向量,在空间中距离更近
在这里插入图片描述
阿里云百炼平台就提供了这样的模型,用于将文本向量化:
在这里插入图片描述
这里选择通用文本向量-v3,这个模型兼容OpenAI,所以我们依然采用OpenAI的配置;修改yml配置

spring:
  application:
    name: chart-robot
  ai:
    ollama:
      # Ollama服务地址
      base-url: http://localhost:11434
      chat:
        # 模型名称,可更改
        model: deepseek-r1:14b
        options:
          # 模型温度,值越大,输出结果越随机
          temperature: 0.8
    openai:
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      api-key: ${
   
   OPENAI_API_KEY} #API key
      chat:
        options:
          # 可选择的模型列表 https://help.aliyun.com/zh/model-studio/getting-started/models
          model: qwen-plus
      embedding:
        options:
          model: text-embedding-v3  #通用文本向量-v3
          dimensions: 1024
向量模型测试

文本向量化以后,就可以通过向量之间的距离来判断文本相似度;接下来,我们来测试下阿里百炼提供的向量大模型;

在项目中写一个工具类,用以计算向量之间的欧氏距离和**余弦距离。**新建一个ai.util包,在其中新建一个VectorDistanceUtils类:

public class VectorDistanceUtils {

    // 私有构造函数:防止该工具类被实例化。
    private VectorDistanceUtils() {}
    // 浮点数计算精度阈值,用于判断浮点数是否接近零。
    private static final double EPSILON = 1e-12;

    /**
     * 计算欧氏距离(Euclidean Distance)
     * 欧氏距离是两个向量之间的直线距离,常用于衡量多维空间中两点的距离。
     * @param vectorA 向量A(非空且与B等长)
     * @param vectorB 向量B(非空且与A等长)
     */
    public static double euclideanDistance(float[] vectorA, float[] vectorB) {
        // 校验输入向量的合法性
        validateVectors(vectorA, vectorB);

        double sum = 0.0; // 用于累加差值平方
        for (int i = 0; i < vectorA.length; i++) {
            double diff = vectorA[i] - vectorB[i]; // 计算对应维度上的差值
            sum += diff * diff; // 累加差值的平方
        }
        return Math.sqrt(sum); // 返回平方和的平方根,即欧氏距离
    }

    /**
     * 计算余弦距离(Cosine Distance)
     * 余弦距离基于余弦相似度计算,表示两个向量在方向上的差异。距离范围为[0, 2],
     * 其中0表示完全相同,2表示完全相反。
     */
    public static double cosineDistance(float[] vectorA, float[] vectorB) {
        // 校验输入向量的合法性
        validateVectors(vectorA, vectorB);

        double dotProduct = 0.0; // 点积
        double normA = 0.0;      // 向量A的模
        double normB = 0.0;      // 向量B的模

        // 遍历向量的每个维度,计算点积和模的平方
        for (int i = 0; i < vectorA.length; i++) {
            dotProduct += vectorA[i] * vectorB[i]; // 点积累加
            normA += vectorA[i] * vectorA[i];     // A模的平方累加
            normB += vectorB[i] * vectorB[i];     // B模的平方累加
        }

        // 计算向量的模
        normA = Math.sqrt(normA);
        normB = Math.sqrt(normB);

        // 如果任意一个向量为零向量,则无法计算余弦距离,抛出异常
        if (normA < EPSILON || normB < EPSILON) {
            throw new IllegalArgumentException("Vectors cannot be zero vectors");
        }

        // 计算余弦相似度,确保结果在[-1, 1]范围内(处理浮点误差)
        double similarity = dotProduct / (normA * normB);
        similarity = Math.max(Math.min(similarity, 1.0), -1.0);

        // 余弦距离 = 1 - 相似度,范围为[0, 2]
        return 1.0 - similarity;
    }

    /**
     * 参数校验统一方法
     * 确保输入向量满足以下条件:
     * 1. 不为空(null);
     * 2. 长度相等;
     * 3. 非空数组。
     */
    private static void validateVectors(float[] a, float[] b) {
        if (a == null || b == null) {
            throw new IllegalArgumentException("Vectors cannot be null");
        }
        if (a.length != b.length) {
            throw new IllegalArgumentException("Vectors must have same dimension");
        }
        if (a.length == 0) {
            throw new IllegalArgumentException("Vectors cannot be empty");
        }
    }
}

由于SpringBoot的自动装配能力,刚才配置的向量模型可以直接使用;

@SpringBootTest
...
// 自动注入向量模型
@Autowired
private OpenAiEmbeddingModel embeddingModel;
@Test
void contextLoads() {
   
   
    // 1.测试数据
    // 1.1.用来查询的文本,国际冲突
    String query = "global conflicts";

    // 1.2.用来做比较的文本
    String[] texts = new String[]{
   
   
        "哈马斯称加沙下阶段停火谈判仍在进行 以方尚未做出承诺",
        "土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判",
        "日本航空基地水井中检测出有机氟化物超标",
        "国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营",
        "我国首次在空间站开展舱外辐射生物学暴露实验",
    };
    // 2.向量化
    // 2.1.先将查询文本向量化
    float[] queryVector = embeddingModel.embed(query);

    // 2.2.再将比较文本向量化,放到一个数组
    List<float[]> textVectors = embeddingModel.embed(Arrays.asList(texts));

    // 3.比较欧氏距离
    // 3.1.把查询文本自己与自己比较,肯定是相似度最高的
    System.out.println(VectorDistanceUtils.euclideanDistance(queryVector, queryVector));
    
    // 3.2.把查询文本与其它文本比较
    for (float[] textVector : textVectors) {
   
   
        System.out.println(VectorDistanceUtils.euclideanDistance(queryVector, textVector));
    }
    System.out.println("------------------");

    // 4.比较余弦距离
    // 4.1.把查询文本自己与自己比较,肯定是相似度最高的
    System.out.println(VectorDistanceUtils.cosineDistance(queryVector, queryVector));
    // 4.2.把查询文本与其它文本比较
    for (float[] textVector : textVectors) {
   
   
        System.out.println(VectorDistanceUtils.cosineDistance(queryVector, textVector));
    }
}

运行结果:
在这里插入图片描述
可以看到,向量相似度确实符合我们的预期。有了比较文本相似度的办法,知识库的问题就可以解决了;前面说了,知识库数据量很大,无法全部写入提示词,而且庞大的知识库中与用户问题相关的其实并不多

所以,我们需要想办法从庞大的知识库中找到与用户问题相关的一小部分,组装成提示词,发送给大模型就可以了

现但是新的问题来了:向量模型是生成向量的,如此庞大的知识库,谁来从中比较和检索数据呢? 这就需要用到向量数据库

向量数据库
文本向量化

由于需要将已拆分的知识片段文本存储向量库,以便后续可以进行检索,而向量库存储的数据是向量不是文本

因此需要将文本进行向量化,即将一个字符串转换为一个N维数组,这个过程在自然语言处理(NLP)领域称为文本嵌入

不同的LLM对于文本嵌入的实现是不同的,ChatGPT的实现是基于transformer架构的,相关实现存储在服务端,每次嵌入都需要访问OpenAI的HTTP接口。

通过下面的例子可以看到OpenAi使用的模型是:text-embedding-ada-002,向量的维度是:1536

OpenAiEmbeddingModel embeddingModel = new OpenAiEmbeddingModel.OpenAiEmbeddingModelBuilder().apiKey(API_KEY).baseUrl(BASE_URL).build();
log.info
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值