Spring AI Java程序员的AI之Spring AI(三)RAG实战

server/2024/10/20 16:27:36/
aidu_pl">

Spring AI之RAG实战与原理分析

  • 前言
  • RAG
    • Document
    • DocumentReader
    • DocumentTransformer
    • DocumentWriter
  • VectorStore
    • SimpleVectorStore
    • RedisVectorStore
    • 元数据搜索
    • 组装提示词

前言

检索增强生成(RAG)是一种结合信息检索和生成模型的技术,用于将相关数据嵌入到Prompts中,以提高AI模型的响应准确性。该方法包括一个批处理风格的编程模型,读取未结构化的数据,将其转换,然后写入向量数据库。总体上,这是一个ETL(Extract, Transform, Load)管道。向量数据库在RAG技术中用于检索部分。

简单解释就是:

搭建自己的知识库除了文档嵌入到向量数据库之外,就是RAG了。当用户提问的时候先从想来数据库搜索相关的资料,再把相关的资料拼接到用户的提问中,再让模型生成答案。

RAG

Document

Spring AI提供了:

  1. DocumentReader:用来读取TXT、PDF等文件内容
  2. DocumentTransformer:用来解析文件内容
  3. DocumentWriter:用来写入文件内容到向量数据库

DocumentReader

实现类有:
JsonReader:读取JSON格式的文件
TextReader:读取txt文件
PagePdfDocumentReader:使用Apache PdfBox读取PDF文件
TikaDocumentReader:使用Apache Tika来读取PDF, DOC/DOCX, PPT/PPTX, and HTML等文件

比如使用TextReader来读取meituan.txt文件内容:

java">package com.qjc.demo.service;import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;import java.util.List;/**** @projectName spring-ai-demo* @packageName com.qjc.demo.service* @author qjc* @description TODO* @Email qjc1024@aliyun.com* @date 2024-10-17 10:15**/
@Component
public class DocumentService {@Value("classpath:meituan-qa.txt") // This is the text document to loadprivate Resource resource;public List<Document> loadText() {TextReader textReader = new TextReader(resource);textReader.getCustomMetadata().put("filename", "meituan-qa.txt");List<Document> documents = textReader.get();return documents;}
}
java">@GetMapping("/document")
public List<Document> document() {return documentService.loadText();
}

得到的结果为:
在这里插入图片描述

所以DocumentReader只负责将文件转换为Document对象,如果要对文件进行切分,则需要使用DocumentTransformer。

DocumentTransformer

Spring AI默认提供了一个TokenTextSplitter,我们可以基于Document来进行切分:

java">public List<Document> loadText() {TextReader textReader = new TextReader(resource);textReader.getCustomMetadata().put("filename", "meituan.txt");List<Document> documents = textReader.get();TokenTextSplitter tokenTextSplitter = new TokenTextSplitter();List<Document> list = tokenTextSplitter.apply(documents);return list;
}

得到的结果如下:
在这里插入图片描述

发现它并不是按问题-答案对来进行切分的,TokenTextSplitter的工作原理:

  1. 先将文本encode为tokens
  2. 按指定的chunkSize(默认为800)对tokens进行切分,得到一个chunk
  3. 将chunk进行decode,得到原始文本
  4. 获取原始文本中最后一个’.‘、’?‘、’!‘、’\n’的位置,该位置表示一段话的结束。
  5. 如果结束位置超过了minChunkSizeChars,那么则进行切分得到一段话的chunk,否则不切分
  6. 将切分后的chunk记录到一个List中
  7. 然后跳转到第二步,处理剩余的tokens

如果要按问题-答案对来进行切分,需要自定义一个TextSplitter:

java">package com.qjc.demo.utils;import org.springframework.ai.transformer.splitter.TextSplitter;import java.util.List;/**** @projectName spring-ai-demo* @packageName com.qjc.demo.utils* @author qjc* @description TODO* @Email qjc1024@aliyun.com* @date 2024-10-17 10:18**/
public class QjcTextSplitter extends TextSplitter {@Overrideprotected List<String> splitText(String text) {return List.of(split(text));}public String[] split(String text) {return text.split("\\s*\\R\\s*\\R\\s*");}
}

然后直接调用就可以了:

java">package com.qjc.demo.service;import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;import java.util.List;/**** @projectName spring-ai-demo* @packageName com.qjc.demo.service* @author qjc* @description TODO* @Email qjc1024@aliyun.com* @date 2024-10-17 10:15**/
@Component
public class DocumentService {@Value("classpath:meituan-qa.txt") // This is the text document to loadprivate Resource resource;public List<Document> loadText() {TextReader textReader = new TextReader(resource);textReader.getCustomMetadata().put("filename", "meituan-qa.txt");List<Document> documents = textReader.get();QjcTextSplitter qjcTextSplitter = new QjcTextSplitter ();List<Document> list = qjcTextSplitter .apply(documents);return list;}
}

得到的结果为:

在这里插入图片描述

DocumentWriter

得到按指定逻辑切分后的Document之后,就需要把它们做向量化并存入向量数据库了。DocumentWriter有一个子接口VectorStore,就表示向量数据库,而VectorStore有一个默认实现类SimpleVectorStore,可以先尝试使用它来进行Document的向量化和存储。

VectorStore

SimpleVectorStore

SimpleVectorStore只提供了一个构造方法:

java">public SimpleVectorStore(EmbeddingClient embeddingClient) {Objects.requireNonNull(embeddingClient, "EmbeddingClient must not be null");this.embeddingClient = embeddingClient;
}

因此可以直接定义一个SimpleVectorStore的Bean,利用构造注入得到EmbeddingClient:

java">@Bean
public SimpleVectorStore vectorStore(EmbeddingClient embeddingClient) {return new SimpleVectorStore(embeddingClient);
}

然后直接使用VectorStore就可以了:

java">package com.qjc.demo.service;import org.springframework.ai.document.Document;
import org.springframework.ai.document.DocumentWriter;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;import java.util.List;/**** @projectName spring-ai-demo* @packageName com.qjc.demo.service* @author qjc* @description TODO* @Email qjc1024@aliyun.com* @date 2024-10-17 10:15**/
@Component
public class DocumentService {@Value("classpath:meituan-qa.txt") // This is the text document to loadprivate Resource resource;@Autowiredprivate VectorStore vectorStore;public List<Document> loadText() {TextReader textReader = new TextReader(resource);textReader.getCustomMetadata().put("filename", "meituan-qa.txt");List<Document> documents = textReader.get();QjcTextSplitter qjcTextSplitter = new QjcTextSplitter ();List<Document> list = qjcTextSplitter .apply(documents);// 向量存储vectorStore.add(list);return list;}}

我们再提供一个Controller用来进行向量查找:

java">@GetMapping("/documentSearch")
public List<Document> documentSearch(@RequestParam String message) {return documentService.search(message);
}
java">public List<Document> search(String message){List<Document> documents = vectorStore.similaritySearch(message);return documents;
}

先进行向量存储,从控制台可以发现利用EmbeddingClient进行了多次文本向量化,因为我们把文本拆分成了多个问答对:
在这里插入图片描述

然后进行查询:
在这里插入图片描述
可以看出确实进行了相似搜索。

RedisVectorStore

SimpleVectorStore是利用了ConcurrentHashMap来进行存储,如果我们想换成Redis,只需要引入相关依赖和定义相关的Bean就可以了。

引入依赖:

java"><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-redis</artifactId>
</dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.1.0</version>
</dependency>

定义Bean:

java">@Bean
public RedisVectorStore vectorStore(EmbeddingClient embeddingClient) {RedisVectorStore.RedisVectorStoreConfig config = RedisVectorStore.RedisVectorStoreConfig.builder().withURI("redis://localhost:6379").withMetadataFields(RedisVectorStore.MetadataField.text("filename")).build();return new RedisVectorStore(config, embeddingClient);
}

在测试前,请先进入redis-cli执行redis-cli FT.DROPINDEX spring-ai-index DD对现有redis中的数据进行清空,不然会受到之前数据的影响。
在这里插入图片描述
测试发现,使用Redis时,meatdata中多了一个vector_score,表示相似度分数,这是RedisVectorStore帮我们设置的,和LangChain4j不一样的时,RedisVectorStore的vector_score是越低表示越相似。

元数据搜索

我们希望把每个问答对的问题存到元数据中,这样就可以使用问题的精准搜索了。我们先定义Redis中的元数据字段:

java">@Bean
public RedisVectorStore vectorStore(EmbeddingClient embeddingClient) {RedisVectorStore.RedisVectorStoreConfig config = RedisVectorStore.RedisVectorStoreConfig.builder().withURI("redis://localhost:6379").withMetadataFields(RedisVectorStore.MetadataField.text("filename"),RedisVectorStore.MetadataField.text("question")).build();return new RedisVectorStore(config, embeddingClient);
}

先删除Redis中的Index:

java">redis-cli FT.DROPINDEX spring-ai-index DD

然后设置每个Document的元数据:

java">public List<Document> loadText() {TextReader textReader = new TextReader(resource);textReader.getCustomMetadata().put("filename", "meituan-qa.txt");List<Document> documents = textReader.get();ZhouyuTextSplitter zhouyuTextSplitter = new ZhouyuTextSplitter();List<Document> list = zhouyuTextSplitter.apply(documents);// 把问题存到元数据中list.forEach(document -> document.getMetadata().put("question", document.getContent().split("\\n")[0]));// 向量存储vectorStore.add(list);return list;
}

重新进行向量化以及存储:
在这里插入图片描述
重新进行相似度搜索:
在这里插入图片描述
定义元数据搜索:

java">@GetMapping("/documentMetadataSearch")
public List<Document> documentMetadataSearch(@RequestParam String message, @RequestParam String question) {return documentService.metadataSearch(message, question);
}
java">public List<Document> metadataSearch(String message, String question) {return vectorStore.similaritySearch(SearchRequest.query(message).withTopK(5).withSimilarityThreshold(0.1).withFilterExpression(String.format("question in ['%s']", question)));
}

搜索结果
在这里插入图片描述

组装提示词

通过以上两个步骤,我们可以将自己的知识库进行向量化存储和搜索了,那么接下来,我们只需要将搜索结果和用户问题进行提示词组装送给大模型就可以得到答案了。

java">@GetMapping("/customerService")
public String customerService(@RequestParam String question) {// 向量搜索List<Document> documentList = documentService.search(question);// 提示词模板PromptTemplate promptTemplate = new PromptTemplate("{userMessage}\n\n 用以下信息回答问题:\n {contents}");// 组装提示词Prompt prompt = promptTemplate.create(Map.of("userMessage", question, "contents", documentList));// 调用大模型return chatClient.call(prompt).getResult().getOutput().getContent();
}

http://www.ppmy.cn/server/133388.html

相关文章

MYSQL 学习(四):数据库管理

MYSQL 学习&#xff08;四&#xff09;&#xff1a;数据库管理 文章目录 MYSQL 学习&#xff08;四&#xff09;&#xff1a;数据库管理1. 数据库表的创建与管理1.1 创建数据库命名规范创建语法 1.2 管理语法 2. 创建和管理表2.1 创建语法结构2.2 基于现有表创建表2.3 查看表结…

chat_gpt回答:python从bin文件里读四字节整型

要从一个二进制文件&#xff08;.bin 文件&#xff09;中读取四字节的整型数值&#xff0c;你可以使用 Python 的 struct 模块&#xff0c;这个模块专门用于处理二进制数据的打包和解包。 下面是一个简单的示例&#xff0c;展示如何从二进制文件中读取四字节整型&#xff1a; …

【代理模式使用场景】

一般来说&#xff0c;代理模式使用场景是远程代理、虚拟代理、安全代理等。下面来详细介绍下这三个场景是什么&#xff0c;实现原理和特点。不过在介绍三个场景前&#xff0c;我们还是先来回顾下代理模式。 代理模式 定义 是结构型设计模式&#xff0c;引入一个对象控制对另…

深度学习基础知识-02 数据预处理

深度学习的数据预处理通常包括&#xff1a; 1.数据清洗&#xff1a;去除错误或不完整的数据。 2.归一化&#xff1a;调整数据范围&#xff0c;如将像素值缩放到0-1。 3.数据增强&#xff1a;通过旋转、缩放等方法增加数据多样性。 4.数据划分&#xff1a;将数据分为训练集、验证…

使用LLM和RAG进行数据库查询(文本到SQL)的四大挑战及解决方案

大型语言模型&#xff08;LLM&#xff09;的出现展示了机器理解自然语言的能力。这些能力帮助工程师完成了许多令人惊叹的工作&#xff0c;比如编写代码文档和代码审查&#xff0c;而最常见的用例之一是代码生成&#xff1b;GitHub Copilot展示了AI理解工程师代码生成意图的能力…

微信开发者工具:音乐小程序报错

报错信息 GET http://localhost:3000/1.mp3 net::ERR CONNECTION REFUSED (env: Windows,mp,1.06.2303220;lib:3.6.0) 原因&#xff1a;小程序没有直接获取本地文件&#xff0c;为了提高访问速度&#xff0c;而采用放到网络服务器中网络访问的方式获取文件内容 解决办法&#…

为图片添加水印(Python)

简介 刚好学了一下tkinter.colorchooser&#xff0c;然后…… 优化了以前的代码&#xff0c;不过仍然是shi 功能 可自由添加水印内容、选择颜色、字体及字体大小、图片、水印的x、y位置 代码 # -*- coding: utf-8 -*- # Environment PyCharm # File_name visibleWat…

python 作业1

任务1: python为主的工作是很少的 学习的python的优势在于制作工具&#xff0c;制作合适的工具可以提高我们在工作中的工作效率的工具 提高我们的竞争优势。 任务2: 不换行 换行 任务3: 安装pycharm 进入相应网站Download PyCharm: The Python IDE for data science and we…