登烈建站,万网域名管理入口,大兴网站开发,显示网站运行时间代码Spring AI - 使用向量数据库实现检索式AI对话 Spring AI 并不仅限于针对大语言模型对话API进行了统一封装#xff0c;它还可以通过简单的方式实现LangChain的一些功能。本篇将带领读者实现一个简单的检索式AI对话接口。
一、需求背景 在一些场景下#xff0c;我们想让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会自动调用的。 第二步向量存储及检索 在Spring AI中有一个VectorStore抽象接口该接口定义了Spring AI与向量数据库的交互操作我们只需通过简单的向量数据库的配置即可使用该接口对向量数据库进行操作。
public interface VectorStore {void add(ListDocument documents);OptionalBoolean delete(ListString idList);ListDocument similaritySearch(String query);ListDocument similaritySearch(SearchRequest request);
}向量数据库Vector Database是一种特殊类型的数据库在人工智能应用中发挥着重要作用。在向量数据库中查询操作与传统的关系数据库不同。它们是执行相似性搜索而不是精确匹配。当给定向量作为查询时向量数据库返回与查询向量“相似”的向量。通过这种方式我们就能将个人的数据与AI模型进行集成。 常见的向量数据库有Chroma、Milvus、Pgvector、Redis、Neo4j等。 三、代码实现 本篇将实现基于ChatGPT的RAG和上传PDF文件存储至向量数据库的接口向量数据库使用Pgvector。Pgvector是基于PostgreSQL进行的扩展可以存储和检索机器学习过程中生成的embeddings。 源码已上传至GitHub: https://github.com/NingNing0111/vector-database-demo 版本信息
JDK 17Spring Boot 3.2.2Spring 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_USERpostgres- POSTGRES_PASSWORDpostgres- POSTGRES_DBvector_store- PGPASSWORDpostgreslogging:options:max-size: 10mmax-file: 3ports:- 5432:5432healthcheck:test: pg_isready -U postgres -d vector_storeinterval: 2stimeout: 20sretries: 102. 创建Spring项目添加依赖 Spring 项目的创建过程略pom.xml核心内容如下 propertiesjava.version17/java.version!-- Spring AI的版本信息 --spring-ai.version0.8.0-SNAPSHOT/spring-ai.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependency!-- 使用OpenAI --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-openai-spring-boot-starter/artifactIdversion${spring-ai.version}/version/dependency!-- 使用PGVector作为向量数据库 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-pgvector-store-spring-boot-starter/artifactIdversion${spring-ai.version}/version/dependency!-- 引入PDF解析器 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-pdf-document-reader/artifactIdversion${spring-ai.version}/version/dependency/dependenciesrepositoriesrepositoryidspring-milestones/idnameSpring Milestones/nameurlhttps://repo.spring.io/milestone/urlsnapshotsenabledfalse/enabled/snapshots/repositoryrepositoryidspring-snapshots/idnameSpring Snapshots/nameurlhttps://repo.spring.io/snapshot/urlreleasesenabledfalse/enabled/releases/repository/repositories3. 配置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) {// 根据问题文本进行相似性搜索ListDocument 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年合工大软件工程实训中较难的项目有哪些 询问:介绍下2024年合工大软件工程实训中知识内容共享平台的需求 询问针对运营商云管平台工单处理子模块项目简介给出这个项目的实现方案或技术栈 源码已上传至GitHubhttps://github.com/NingNing0111/vector-database-demo