Spring AI - 使用向量数据库实现检索式AI对话

Spring AI - 使用向量数据库实现检索式AI对话

 Spring AI 并不仅限于针对大语言模型对话API进行了统一封装,它还可以通过简单的方式实现LangChain的一些功能。本篇将带领读者实现一个简单的检索式AI对话接口。

一、需求背景

 在一些场景下,我们想让AI根据我们提供的数据进行回复。因为对话有最大Token的限制,因此很多场景下我们是无法直接将所有的数据发给AI的,一方面在数据量很大的情况下,会突破Token的限制,另一方面,在不突破Token限制的情况下也会有不必要的对话费用开销。因此我们如何在花费最少费用的同时又能让AI更好的根据我们提供的数据进行回复是一个非常关键的问题。针对这一问题,我们可以采用数据向量化的方式来解决。

二、实现原理

将我们个人数据存储到向量数据库中。然后,在用户想AI发起对话之前,首先从向量数据库中检索一组相似的文档。然后,将这些文档作为用户问题的上下文,并与用户的对话一起发送到 AI 模型,从而实现精确性的回复。这种方式称为检索增强生成(RAG)

第一步:数据向量化

 我们有很多种方式将数据向量化,最简单的就是通过调用第三方API来实现。以OpenAI的API为例,它提供了 https://api.openai.com/v1/embeddings 接口,通过请求该接口可以获取某段文本的向量化的数据。具体可参考官方API介绍:Create embeddings。在Spring AI中,我们不必调用该接口手动进行向量化处理,在存储到向量数据库的时候,Spring AI会自动调用的。

img.png

第二步:向量存储及检索

 在Spring AI中有一个VectorStore抽象接口,该接口定义了Spring AI与向量数据库的交互操作,我们只需通过简单的向量数据库的配置即可使用该接口对向量数据库进行操作。

public interface VectorStore {void add(List<Document> documents);Optional<Boolean> delete(List<String> idList);List<Document> similaritySearch(String query);List<Document> similaritySearch(SearchRequest request);
}

 向量数据库(Vector Database)是一种特殊类型的数据库,在人工智能应用中发挥着重要作用。在向量数据库中,查询操作与传统的关系数据库不同。它们是执行相似性搜索,而不是精确匹配。当给定向量作为查询时,向量数据库返回与查询向量“相似”的向量。通过这种方式,我们就能将个人的数据与AI模型进行集成。`

 常见的向量数据库有:ChromaMilvusPgvectorRedisNeo4j等。

三、代码实现

 本篇将实现基于ChatGPT的RAG和上传PDF文件存储至向量数据库的接口,向量数据库使用Pgvector。Pgvector是基于PostgreSQL进行的扩展,可以存储和检索机器学习过程中生成的embeddings。

源码已上传至GitHub: https://github.com/NingNing0111/vector-database-demo

版本信息

  • JDK >= 17
  • Spring Boot >= 3.2.2
  • Spring AI = 0.8.0-SNAPSHOT

1. 安装Pgvector

 Pgvector将使用Docker安装。docker-compose.yml文件如下:

version: '3.7'
services:postgres:image: ankane/pgvector:v0.5.0restart: alwaysenvironment:- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres- POSTGRES_DB=vector_store- PGPASSWORD=postgreslogging:options:max-size: 10mmax-file: "3"ports:- '5432:5432'healthcheck:test: "pg_isready -U postgres -d vector_store"interval: 2stimeout: 20sretries: 10

2. 创建Spring项目,添加依赖

 Spring 项目的创建过程略,pom.xml核心内容如下:

	<properties><java.version>17</java.version><!--  Spring AI的版本信息  --><spring-ai.version>0.8.0-SNAPSHOT</spring-ai.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- 使用OpenAI --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId><version>${spring-ai.version}</version></dependency><!-- 使用PGVector作为向量数据库 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId><version>${spring-ai.version}</version></dependency><!-- 引入PDF解析器 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pdf-document-reader</artifactId><version>${spring-ai.version}</version></dependency></dependencies><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories>

3. 配置API、Key、PGVector连接信息

server:port: 8801spring:ai:openai:base-url: https://api.example.comapi-key: sk-aec103e6cfxxxxxxxxxxxxxxxxxxxxxxx71da57adatasource:username: postgrespassword: postgresurl: jdbc:postgresql://localhost/vector_store

4. 创建VectorStore和文本分割器TokenTextSplitter

 这里我创建了一个ApplicationConfig配置类

package com.ningning0111.vectordatabasedemo.config;import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;@Configuration
public class ApplicationConfig {/*** 向量数据库进行检索操作* @param embeddingClient* @param jdbcTemplate* @return*/@Beanpublic VectorStore vectorStore(EmbeddingClient embeddingClient, JdbcTemplate jdbcTemplate){return new PgVectorStore(jdbcTemplate,embeddingClient);}/*** 文本分割器* @return*/@Beanpublic TokenTextSplitter tokenTextSplitter() {return new TokenTextSplitter();}
}

5. 构建PDF存储服务层

 在service层下创建一个名为PdfStoreService的类,用于将PDF文件存储到向量数据库中。

package com.ningning0111.vectordatabasedemo.service;import lombok.RequiredArgsConstructor;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;@Service
@RequiredArgsConstructor
public class PdfStoreService {private final DefaultResourceLoader resourceLoader;private final VectorStore vectorStore;private final TokenTextSplitter tokenTextSplitter;/*** 根据PDF的页数进行分割* @param url*/public void saveSourceByPage(String url){// 加载资源,需要本地路径的信息Resource resource = resourceLoader.getResource(url);// 加载PDF文件时的配置对象PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder().withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder().withNumberOfBottomTextLinesToDelete(3).withNumberOfTopPagesToSkipBeforeDelete(1).build()).withPagesPerDocument(1).build();PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(resource, loadConfig);// 存储到向量数据库中vectorStore.accept(tokenTextSplitter.apply(pagePdfDocumentReader.get()));}/*** 根据PDF的目录(段落)进行划分* @param url*/public void saveSourceByParagraph(String url){Resource resource = resourceLoader.getResource(url);PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder().withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder().withNumberOfBottomTextLinesToDelete(3).withNumberOfTopPagesToSkipBeforeDelete(1).build()).withPagesPerDocument(1).build();ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(resource,loadConfig);vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));}/*** MultipartFile对象存储,采用PagePdfDocumentReader* @param file*/public void saveSource(MultipartFile file){try {// 获取文件名String fileName = file.getOriginalFilename();// 获取文件内容类型String contentType = file.getContentType();// 获取文件字节数组byte[] bytes = file.getBytes();// 创建一个临时文件Path tempFile = Files.createTempFile("temp-", fileName);// 将文件字节数组保存到临时文件Files.write(tempFile, bytes);// 创建一个 FileSystemResource 对象Resource fileResource = new FileSystemResource(tempFile.toFile());PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder().withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder().withNumberOfBottomTextLinesToDelete(3).withNumberOfTopPagesToSkipBeforeDelete(1).build()).withPagesPerDocument(1).build();PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(fileResource, loadConfig);vectorStore.accept(tokenTextSplitter.apply(pagePdfDocumentReader.get()));}catch (IOException e){e.printStackTrace();}}
}

6. 构建对话服务

 创建ChatService类,该类提供了两种对话方式:不进行检索的普通对话模式对向量数据库进行检索的对话模式

package com.ningning0111.vectordatabasedemo.service;import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Service
@RequiredArgsConstructor
public class ChatService {// 系统提示词private final static String SYSTEM_PROMPT = """你需要使用文档内容对用户提出的问题进行回复,同时你需要表现得天生就知道这些内容,不能在回复中体现出你是根据给出的文档内容进行回复的,这点非常重要。当用户提出的问题无法根据文档内容进行回复或者你也不清楚时,回复不知道即可。文档内容如下:{documents}""";private final ChatClient chatClient;private final VectorStore vectorStore;// 简单的对话,不对向量数据库进行检索public String simpleChat(String userMessage) {return chatClient.call(userMessage);}// 通过向量数据库进行检索public String chatByVectorStore(String message) {// 根据问题文本进行相似性搜索List<Document> listOfSimilarDocuments = vectorStore.similaritySearch(message);// 将Document列表中每个元素的content内容进行拼接获得documentsString documents = listOfSimilarDocuments.stream().map(Document::getContent).collect(Collectors.joining());// 使用Spring AI 提供的模板方式构建SystemMessage对象Message systemMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documents));// 构建UserMessage对象UserMessage userMessage = new UserMessage(message);// 将Message列表一并发送给ChatGPTChatResponse rsp = chatClient.call(new Prompt(List.of(systemMessage, userMessage)));return rsp.getResult().getOutput().getContent();}
}

7. 构建Controller层

ChatController提供了对话接口:

package com.ningning0111.vectordatabasedemo.controller;import com.ningning0111.vectordatabasedemo.service.ChatService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/chat")
public class ChatController {private final ChatService chatService;@GetMapping("/simple")public String simpleChat(@RequestParam String message){return chatService.simpleChat(message);}@GetMapping("/")public String chat(@RequestParam String message){return chatService.chatByVectorStore(message);}
}

PdfUploadController提供了上传文件并保存到向量数据库中的接口

package com.ningning0111.vectordatabasedemo.controller;import com.ningning0111.vectordatabasedemo.service.PdfStoreService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;@Controller
@RequestMapping("/api/v1/pdf")
@RequiredArgsConstructor
public class PdfUploadController {private final PdfStoreService pdfStoreService;@PostMapping("/upload")public void upload(@RequestParam MultipartFile file){pdfStoreService.saveSource(file);}
}

三、效果图

 以24年合工大软工实训的pdf文件为例,通过向chatgpt提问与文档内容相关的问题。

 询问:2024年合工大软件工程实训中较难的项目有哪些?
img.png
 询问:介绍下2024年合工大软件工程实训中知识内容共享平台的需求
img_1.png
 询问:针对运营商云管平台工单处理子模块项目简介,给出这个项目的实现方案或技术栈
img_2.png

源码已上传至GitHub:https://github.com/NingNing0111/vector-database-demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/678598.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为机考入门python3--(10)牛客10-字符个数统计

分类&#xff1a;字符 知识点&#xff1a; 字符的ASCII码 ord(char) 题目来自【牛客】 def count_unique_chars(s): # 创建一个空集合来保存不同的字符 unique_chars set() # 遍历字符串中的每个字符 for char in s: # 将字符转换为 ASCII 码并检查是否在范围内 #…

KEIL-MDK的时间戳之time.h 结合gd32f1的RTC应用

KEIL-MDK的时间戳之time.h 的应用 1 时间戳介绍 现在物联网产品的在进行通讯的时候&#xff0c;需要加入时间戳的这个信息参数&#xff0c;方便服务器和产品之间交换时间信息。 时间戳是计算机系统中用来表示日期和时间的一种方式&#xff0c;通常是一个数字或者一串字符&am…

[优雅的面试] 进程 线程 协程分的清

面试官大佬&#xff1a;小伙子&#xff0c;咱今儿个先聊聊进程线程这块的知识哈&#xff0c;就先说说进程吧。 我&#xff1a;存储在硬盘中的代码是静态文件&#xff0c;运行中的程序被称为进程。进程之间数据是相互隔离的。 一般说来&#xff0c;一个进程并不是自始至终连续不…

Vits2.3-Extra-v2:中文特化,如何训练及推理(新手教程)

环境&#xff1a; Vits2.3-Extra-v2:中文特化修复版 auto_DataLabeling 干声10分钟左右.wav 问题描述&#xff1a; Vits2.3-Extra-v2:中文特化&#xff0c;如何训练及推理&#xff08;新手教程&#xff09; 解决方案&#xff1a; 一、准备数据集 切分音频 本次音频数据…

管理就是闭环

管理是什么&#xff1f;这个问题没有一个统一的答案。本文提供一个管中窥豹的答案&#xff1a;管理就是闭环。 作为基层管理者&#xff0c;日常管理事务&#xff0c;一个是目标闭环&#xff0c;一个是执行闭环。这分别对应敏捷PO和Scrum Master的职责。PO的职责是确保目标闭环&…

猫头虎分享已解决Bug ‍ || TypeError: props is not a function (React)

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

05.坐标系

1. 坐标系原点 坐标系原点就是屏幕/窗口的左上角&#xff0c;X向右增长&#xff0c;Y向下增长。 2.设置控件位置 设置控件位置&#xff0c;就相当于是需要指定控件的坐标&#xff0c;对于该控件来说&#xff0c;其坐标原点是其父窗口/父控件的左上角。 设置方法就是通过控件的…

创新S3存储桶检索:Langchain社区S3加载器搭载OpenAI API

在瞬息万变的数据存储和处理领域&#xff0c;将高效的云存储解决方案与先进的 AI 功能相结合&#xff0c;为处理大量数据提供了一种变革性的方法。本文演示了使用 MinIO、Langchain 和 OpenAI 的 GPT-3.5 模型的实际实现&#xff0c;重点总结了存储在 MinIO 存储桶中的文档。 …

leetcode:62.不同路径

动态规划 注意&#xff1a;用深搜或者广搜会超时 dp含义dp[i,j]&#xff1a;从&#xff08;0&#xff0c;0&#xff09;到&#xff08;i&#xff0c;j&#xff09;的路径个数 递推公式&#xff1a;dp[i][j] dp[i-1][j] dp[i][j-1] 初始化&#xff1a;for(i 0,i<m,i) …

[职场] 面试被问优点的回答参考 #知识分享#其他#学习方法

面试被问优点的回答参考 当面试官问你最大的优点是什么&#xff1f;回答1&#xff1a; 我擅长合理地安排时间&#xff0c; 作为助理&#xff0c; 我的杂事很多&#xff0c; 总是觉得手边有做不完的事情&#xff0c; 所以我特别注意时间管理&#xff0c; 这样才能高效地工作&am…

继承

1.继承的作用 有些类与类之间存在特殊关系&#xff0c;下级别的成员除了拥有上一级别的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承技术&#xff0c;减少重复代码。 总结&#xff1a; 继承的好处&#xff1a;可以减少重复的代码 class A : public B;…

猫头虎分享已解决Bug || KeyError: ‘The truth value of a Series is ambiguous‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

EasyExcel动态列导出

测试代码地址&#xff1a;https://gitee.com/wangtianwen1996/cento-practice/tree/master/src/test/java/com/xiaobai/easyexcel/dynamiccolumn 官方文档&#xff1a;https://easyexcel.opensource.alibaba.com/docs/2.x/quickstart/write 一、实现方式 1、根据需要导出的列…

假期刷题打卡--Day26

1、MT1212乘法表 请编写一个简单程序&#xff0c;输出九九乘法表。输入n&#xff0c;就输出乘法表到n的地方。 格式 输入格式&#xff1a; 输入整型 输出格式&#xff1a; 输出整型。形式如&#xff1a;1*11 样例 1 输入&#xff1a; 5输出&#xff1a; 1*11 2*12 …

vue3项目中的404页面

vue3项目中的404页面 春节前的最后一篇技术博客了 写了不少vue项目&#xff0c;发现一直没有正确处理404页面。404页面的出现有这么几种可能&#xff1a; 错误输入了页面地址路由连接跳转时&#xff0c;某些路由已经不存在了&#xff0c;而程序员并没有正确处理 也就是说40…

CSS基础---新手入门级详解

CSS:层叠样式表 CSS&#xff08;Cascading Style Sheets,层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档添加样式&#xff08;字体、间距和颜色&#xff09;的计算机语言&#xff0c;css扩展名为.css。 实例: <!DOCTYPE html><html> <head><…

OpenAI---提示词工程的6大原则

OpenAI在官方的文档里上线了Prompt engineering&#xff0c;也就是提示词工程指南&#xff0c;其中OpenAI有提到写提示词的6条大的原则&#xff0c;它们分别是&#xff1a; &#xff08;1&#xff09;Write clear instructions&#xff08;写出清晰的指令&#xff09; &#xf…

STM32CubeMX,定时器之定时功能,入门学习,如何设置prescaler,以及timer计算PWM输入捕获方法(重要)

频率变小&#xff0c;周期变长 1&#xff0c;参考链接&#xff08;重要&#xff09; STM32CubeMX——定时器之定时功能&#xff08;学习使用timer定时器的设置&#xff09; STM32测量PWM信息&#xff08;学习使用设置pwm输入捕获&#xff09; 通用定时器中两个重要参数的设置心…

Pytorch+NCCL源码编译

目录 环境1. 安装cudnn2. 使用pytorch自带NCCL库进行编译3. 修改NCCL源代码并重新编译后测试&#xff0c;体现出源码更改 环境 Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)cuda 11.8 cudnn 8python 3.10torch V2.0.1 nccl 2.14.3NVIDIA GeForce RTX 4090 *2 1.…

快速搭建 nfs 环境, 解决 nfs 搭建时的疑难杂症

博客原文 nfs 环境搭建 简介: NFS&#xff08;Network File System&#xff09;即网络文件系统&#xff0c;是FreeBSD支持的文件系统中的一种&#xff0c;它允许网络中的计算机之间共享资源。 在NFS的应用中&#xff0c;本地NFS的客户端应用可以透明地读写位于远端NFS服务器上…