Spring AI - 使用向量數(shù)據(jù)庫實(shí)現(xiàn)檢索式AI對(duì)話
?Spring AI 并不僅限于針對(duì)大語言模型對(duì)話API進(jìn)行了統(tǒng)一封裝,它還可以通過簡(jiǎn)單的方式實(shí)現(xiàn)LangChain的一些功能。本篇將帶領(lǐng)讀者實(shí)現(xiàn)一個(gè)簡(jiǎn)單的檢索式AI對(duì)話接口。
一、需求背景
?在一些場(chǎng)景下,我們想讓AI根據(jù)我們提供的數(shù)據(jù)進(jìn)行回復(fù)。因?yàn)閷?duì)話有最大Token的限制,因此很多場(chǎng)景下我們是無法直接將所有的數(shù)據(jù)發(fā)給AI的,一方面在數(shù)據(jù)量很大的情況下,會(huì)突破Token的限制,另一方面,在不突破Token限制的情況下也會(huì)有不必要的對(duì)話費(fèi)用開銷。因此我們?nèi)绾卧诨ㄙM(fèi)最少費(fèi)用的同時(shí)又能讓AI更好的根據(jù)我們提供的數(shù)據(jù)進(jìn)行回復(fù)是一個(gè)非常關(guān)鍵的問題。針對(duì)這一問題,我們可以采用數(shù)據(jù)向量化的方式來解決。
二、實(shí)現(xiàn)原理
將我們個(gè)人數(shù)據(jù)存儲(chǔ)到向量數(shù)據(jù)庫中。然后,在用戶想AI發(fā)起對(duì)話之前,首先從向量數(shù)據(jù)庫中檢索一組相似的文檔。然后,將這些文檔作為用戶問題的上下文,并與用戶的對(duì)話一起發(fā)送到 AI 模型,從而實(shí)現(xiàn)精確性的回復(fù)。這種方式稱為檢索增強(qiáng)生成(RAG)
。
第一步:數(shù)據(jù)向量化
?我們有很多種方式將數(shù)據(jù)向量化,最簡(jiǎn)單的就是通過調(diào)用第三方API來實(shí)現(xiàn)。以O(shè)penAI的API為例,它提供了 https://api.openai.com/v1/embeddings
接口,通過請(qǐng)求該接口可以獲取某段文本的向量化的數(shù)據(jù)。具體可參考官方API介紹:Create embeddings。在Spring AI中,我們不必調(diào)用該接口手動(dòng)進(jìn)行向量化處理,在存儲(chǔ)到向量數(shù)據(jù)庫的時(shí)候,Spring AI會(huì)自動(dòng)調(diào)用的。
第二步:向量存儲(chǔ)及檢索
?在Spring AI中有一個(gè)VectorStore
抽象接口,該接口定義了Spring AI與向量數(shù)據(jù)庫的交互操作,我們只需通過簡(jiǎn)單的向量數(shù)據(jù)庫的配置即可使用該接口對(duì)向量數(shù)據(jù)庫進(jìn)行操作。
public interface VectorStore {
void add(List<Document> documents);
Optional<Boolean> delete(List<String> idList);
List<Document> similaritySearch(String query);
List<Document> similaritySearch(SearchRequest request);
}
?向量數(shù)據(jù)庫(Vector Database)是一種特殊類型的數(shù)據(jù)庫,在人工智能應(yīng)用中發(fā)揮著重要作用。在向量數(shù)據(jù)庫中,查詢操作與傳統(tǒng)的關(guān)系數(shù)據(jù)庫不同。它們是執(zhí)行相似性搜索,而不是精確匹配。當(dāng)給定向量作為查詢時(shí),向量數(shù)據(jù)庫返回與查詢向量“相似”的向量。通過這種方式,我們就能將個(gè)人的數(shù)據(jù)與AI模型進(jìn)行集成。`
?常見的向量數(shù)據(jù)庫有:
Chroma
、Milvus
、Pgvector
、Redis
、Neo4j
等。
三、代碼實(shí)現(xiàn)
?本篇將實(shí)現(xiàn)基于ChatGPT的RAG和上傳PDF文件存儲(chǔ)至向量數(shù)據(jù)庫的接口,向量數(shù)據(jù)庫使用Pgvector
。Pgvector是基于PostgreSQL進(jìn)行的擴(kuò)展,可以存儲(chǔ)和檢索機(jī)器學(xué)習(xí)過程中生成的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.0
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=vector_store
- PGPASSWORD=postgres
logging:
options:
max-size: 10m
max-file: "3"
ports:
- '5432:5432'
healthcheck:
test: "pg_isready -U postgres -d vector_store"
interval: 2s
timeout: 20s
retries: 10
2. 創(chuàng)建Spring項(xiàng)目,添加依賴
?Spring 項(xiàng)目的創(chuàng)建過程略,pom.xml
核心內(nèi)容如下:
<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作為向量數(shù)據(jù)庫 -->
<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: 8801
spring:
ai:
openai:
base-url: https://api.example.com
api-key: sk-aec103e6cfxxxxxxxxxxxxxxxxxxxxxxx71da57a
datasource:
username: postgres
password: postgres
url: jdbc:postgresql://localhost/vector_store
4. 創(chuàng)建VectorStore和文本分割器TokenTextSplitter
?這里我創(chuàng)建了一個(gè)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 {
/**
* 向量數(shù)據(jù)庫進(jìn)行檢索操作
* @param embeddingClient
* @param jdbcTemplate
* @return
*/
@Bean
public VectorStore vectorStore(EmbeddingClient embeddingClient, JdbcTemplate jdbcTemplate){
return new PgVectorStore(jdbcTemplate,embeddingClient);
}
/**
* 文本分割器
* @return
*/
@Bean
public TokenTextSplitter tokenTextSplitter() {
return new TokenTextSplitter();
}
}
5. 構(gòu)建PDF存儲(chǔ)服務(wù)層
?在service層下創(chuàng)建一個(gè)名為PdfStoreService
的類,用于將PDF文件存儲(chǔ)到向量數(shù)據(jù)庫中。
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;
/**
* 根據(jù)PDF的頁數(shù)進(jìn)行分割
* @param url
*/
public void saveSourceByPage(String url){
// 加載資源,需要本地路徑的信息
Resource resource = resourceLoader.getResource(url);
// 加載PDF文件時(shí)的配置對(duì)象
PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(
new ExtractedTextFormatter
.Builder()
.withNumberOfBottomTextLinesToDelete(3)
.withNumberOfTopPagesToSkipBeforeDelete(1)
.build()
)
.withPagesPerDocument(1)
.build();
PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(resource, loadConfig);
// 存儲(chǔ)到向量數(shù)據(jù)庫中
vectorStore.accept(tokenTextSplitter.apply(pagePdfDocumentReader.get()));
}
/**
* 根據(jù)PDF的目錄(段落)進(jìn)行劃分
* @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對(duì)象存儲(chǔ),采用PagePdfDocumentReader
* @param file
*/
public void saveSource(MultipartFile file){
try {
// 獲取文件名
String fileName = file.getOriginalFilename();
// 獲取文件內(nèi)容類型
String contentType = file.getContentType();
// 獲取文件字節(jié)數(shù)組
byte[] bytes = file.getBytes();
// 創(chuàng)建一個(gè)臨時(shí)文件
Path tempFile = Files.createTempFile("temp-", fileName);
// 將文件字節(jié)數(shù)組保存到臨時(shí)文件
Files.write(tempFile, bytes);
// 創(chuàng)建一個(gè) FileSystemResource 對(duì)象
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. 構(gòu)建對(duì)話服務(wù)
?創(chuàng)建ChatService
類,該類提供了兩種對(duì)話方式:不進(jìn)行檢索的普通對(duì)話模式
和對(duì)向量數(shù)據(jù)庫進(jìn)行檢索的對(duì)話模式
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 {
// 系統(tǒng)提示詞
private final static String SYSTEM_PROMPT = """
你需要使用文檔內(nèi)容對(duì)用戶提出的問題進(jìn)行回復(fù),同時(shí)你需要表現(xiàn)得天生就知道這些內(nèi)容,
不能在回復(fù)中體現(xiàn)出你是根據(jù)給出的文檔內(nèi)容進(jìn)行回復(fù)的,這點(diǎn)非常重要。
當(dāng)用戶提出的問題無法根據(jù)文檔內(nèi)容進(jìn)行回復(fù)或者你也不清楚時(shí),回復(fù)不知道即可。
文檔內(nèi)容如下:
{documents}
""";
private final ChatClient chatClient;
private final VectorStore vectorStore;
// 簡(jiǎn)單的對(duì)話,不對(duì)向量數(shù)據(jù)庫進(jìn)行檢索
public String simpleChat(String userMessage) {
return chatClient.call(userMessage);
}
// 通過向量數(shù)據(jù)庫進(jìn)行檢索
public String chatByVectorStore(String message) {
// 根據(jù)問題文本進(jìn)行相似性搜索
List<Document> listOfSimilarDocuments = vectorStore.similaritySearch(message);
// 將Document列表中每個(gè)元素的content內(nèi)容進(jìn)行拼接獲得documents
String documents = listOfSimilarDocuments.stream().map(Document::getContent).collect(Collectors.joining());
// 使用Spring AI 提供的模板方式構(gòu)建SystemMessage對(duì)象
Message systemMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documents));
// 構(gòu)建UserMessage對(duì)象
UserMessage userMessage = new UserMessage(message);
// 將Message列表一并發(fā)送給ChatGPT
ChatResponse rsp = chatClient.call(new Prompt(List.of(systemMessage, userMessage)));
return rsp.getResult().getOutput().getContent();
}
}
7. 構(gòu)建Controller層
?ChatController
提供了對(duì)話接口:
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
提供了上傳文件并保存到向量數(shù)據(jù)庫中的接口
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年合工大軟工實(shí)訓(xùn)的pdf文件為例,通過向chatgpt提問與文檔內(nèi)容相關(guān)的問題。
?詢問:2024年合工大軟件工程實(shí)訓(xùn)中較難的項(xiàng)目有哪些?
?詢問:介紹下2024年合工大軟件工程實(shí)訓(xùn)中知識(shí)內(nèi)容共享平臺(tái)的需求
?詢問:針對(duì)運(yùn)營(yíng)商云管平臺(tái)工單處理子模塊項(xiàng)目簡(jiǎn)介,給出這個(gè)項(xiàng)目的實(shí)現(xiàn)方案或技術(shù)棧
文章來源:http://www.zghlxwxcb.cn/news/detail-850670.html
源碼已上傳至GitHub:https://github.com/NingNing0111/vector-database-demo文章來源地址http://www.zghlxwxcb.cn/news/detail-850670.html
到了這里,關(guān)于Spring AI - 使用向量數(shù)據(jù)庫實(shí)現(xiàn)檢索式AI對(duì)話的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!