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.hqwc.cn/news/461653.html

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

相关文章

PlantUML绘制UML图教程

UML&#xff08;Unified Modeling Language&#xff09;是一种通用的建模语言&#xff0c;广泛用于软件开发中对系统进行可视化建模。PlantUML是一款强大的工具&#xff0c;通过简单的文本描述&#xff0c;能够生成UML图&#xff0c;包括类图、时序图、用例图等。PlantUML是一款…

springboot微信小程序 uniapp学习资料分享系统v9uy4

理论意义 当今网络教学已成为各国教育改革 和发展的趋势。因此&#xff0c;构建一个适合交互式课堂教学模式的教学平台就成了当务之 急。 在国内高校&#xff0c;目前交互平台主要用于网络学院的远程教学&#xff0c;至于校园内的正规教学&#xff0c;老师自发建立课程主页的比…

盘点2024年1月Sui生态发展,了解Sui近期成长历程!

2024年1月是Sui生态开门大吉的一个月&#xff0c;在2023交出优异的成绩单后&#xff0c;2024年的首月也圆满结尾。在BTC现货ETF通过的大背景之下&#xff0c;Web3行业多项指标持续展现增长趋势。Sui也紧随行业脚步&#xff0c;开展了一系列活动助力Web3行业和生态持续发展。 其…

uniapp的配置和使用

①安装环境和编辑器 注册小程序账号 微信开发者工具下载 uniapp 官网 HbuilderX 下载 首先先下载Hbuilder和微信开发者工具 &#xff08;都是傻瓜式安装&#xff09;&#xff0c;然后注册小程序账号&#xff1a; 拿到appid&#xff1a; ②简单通过demo使用微信开发者工具和…

Vue源码系列讲解——虚拟DOM篇【二】(Vue中的DOM-Diff)

目录 1. 前言 2. patch 3. 创建节点 4. 删除节点 5. 更新节点 6. 总结 1. 前言 在上一篇文章介绍VNode的时候我们说了&#xff0c;VNode最大的用途就是在数据变化前后生成真实DOM对应的虚拟DOM节点&#xff0c;然后就可以对比新旧两份VNode&#xff0c;找出差异所在&…

D7 Elasticsearch-Mongodb(搜索记录)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

【第三十五节】idea项目的创建以及setting和Project Structure的设置

项目创建 Project Structure的设置 点击file ~ Project Structure 进入 进入view/Appearance 选中Toolbar 就会出现状态栏

PHP入门指南:进阶篇

PHP入门指南&#xff1a;进阶篇 PHP入门指南&#xff1a;进阶篇1. 面向对象编程&#xff08;OOP&#xff09;1.1 类和对象的基本概念1.2 构造函数和析构函数1.3 属性和方法的访问控制1.4 继承与多态 2. 错误和异常处理2.1 错误处理机制2.2 异常处理机制2.3 自定义异常类 3. PHP…

EasyRecovery免费版2024电脑数据恢复利器

在数字化时代&#xff0c;我们的生活和工作都离不开电脑&#xff0c;电脑硬盘中的数据却时常面临丢失的风险&#xff0c;无论是因为误删除、格式化、病毒感染还是硬件故障&#xff0c;都可能让我们付出沉重的代价&#xff0c;在这种情况下&#xff0c;一款强大的数据恢复软件就…

新型Black Matter勒索病毒,勒索300万美金

前言 BlackMatter勒索病毒是一款基于RAAS模式的新型勒索病毒&#xff0c;该勒索病毒组织成立于2021年7月&#xff0c;该勒索病毒黑客组织对外宣称&#xff0c;已经整合了DarkSide、REvil和LockBit等勒索病毒的最佳功能特点。 勒索病毒黑客组织曾表示不会对医疗保健、关键基础设…

Nodejs基础6之HTTP模块的获取请求行和请求头、获取请求体、获取请求路径和查询字符串、http请求练习、设置HTTP响应报文、http响应练习

Nodejs基础 HTTP模块获取请求行和请求头获取请求体获取请求路径和查询字符串方式一方式二 http请求练习设置HTTP响应报文状态码响应状态描述响应头响应体 HTTP响应练习 HTTP模块 含义语法重点掌握请求方法request.method*请求版本request.httpVersion请求路径request.url*URL …

零基础学Python(9)— 流程控制语句(下)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。流程控制语句是编程语言中用于控制程序执行流程的语句&#xff0c;本节课就带大家认识下Python语言中常见的流程控制语句&#xff01;~&#x1f308; 目录 &#x1f680;1.while循环 &#x1f680;2.for循环 &#x1…