SpringAI實戰鏈接
1.SpringAl實現AI應用-快速搭建-CSDN博客
2.SpringAI實現AI應用-搭建知識庫-CSDN博客
概述
想要使用SpringAI搭建知識庫,就要使用SpringAI中的TikaDocumentReader,它屬于ETL(提取、轉換、加載)框架中的提取(Extract)階段。
作用
TikaDocumentReader是Spring AI提供的一個文檔讀取器,它基于Apache Tika技術實現,能夠讀取并解析多種格式的文檔,包括但不限于PDF、DOC/DOCX、PPT/PPTX和HTML等。這使得TikaDocumentReader成為一個非常靈活和強大的工具,適用于構建知識庫或處理各種文檔數據。
使用場景
TikaDocumentReader的使用場景非常廣泛,包括但不限于:
構建知識庫:在構建知識庫時,需要從各種格式的文檔中提取文本內容。TikaDocumentReader能夠輕松地讀取這些文檔,并將其轉換為統一的格式,以便后續的處理和存儲。
文檔處理:在處理大量文檔時,如文檔分類、摘要生成等任務中,TikaDocumentReader可以作為一個預處理步驟,將文檔內容提取出來,為后續的處理提供便利。
數據清洗:在數據清洗過程中,有時需要從非結構化的文檔中提取關鍵信息。TikaDocumentReader能夠讀取這些文檔,并將其轉換為結構化的數據格式,以便進行后續的數據清洗和分析。
準備工作一
在制作本地知識庫的時候,還需要安裝矢量數據庫并下載插件vector,下載矢量化模型
矢量數據庫(PostgreSQL)下載地址:EDB: Open-Source, Enterprise Postgres Database Management
插件vector下載地址:vector: Open-source vector similarity search for Postgres / PostgreSQL Extension Network
矢量化模型下載地址:text2vec-base-chinese · 模型庫
遇到的問題
問題一
安裝完成PostgreSQL之后,想用自帶的管理器(pgAdmin4),但是報錯,解決了半天沒成功,直接改用navicat進行連接,但是連接的時報錯(datlastsysoid does not exist),是因為Postgres?15 從pg_database表中刪除了 datlastsysoid 字段引發此錯誤。(我安裝的是PostgreSQL17)
解決方式
方法一:升級navicat
方法二:安裝Postgres 15以下
方法三:修改navicat的dll文件
詳述方法三:找到navicat安裝的位置
找到libcc.dll文件(最好進行備份)
使用在線十六進制編輯器打開文件,在線地址:HexEd.it — 基于瀏覽器的十六進制編輯器
在文件中搜索“SELECT DISTINCT datlastsysoid”,并將其替換為“SELECT DISTINCT dattablespace”
最后另存并替換原來的文件,重啟navicat就可以使用了
問題二
安裝vector插件的時候遇到的問題,網上也有很多方法,自己百度吧,(真的是一步一個坑)我參考的是:Windows 安裝 PostgreSQL 并安裝 vector 擴展_win10上安裝postgresql的 vector擴展-CSDN博客
問題三
下載矢量化模型
準備工作二
PostgreSQL和插件vector都安裝好之后,創建矢量表
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";CREATE TABLE IF NOT EXISTS vector_store (id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,content text,metadata json,embedding vector(768)
);CREATE INDEX ON vector_store USING HNSW (embedding vector_cosine_ops);
搭建工程
通過第一篇帖子,已經可以創建一個SpringAI的demo了,現在需要將矢量化模型放在resources下
修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>SpringAI_Demo</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.5</version><relativePath/> <!-- lookup parent from repository --></parent><properties><java.version>17</java.version><spring-ai.version>1.0.0-M6</spring-ai.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- 常規jar--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- springAI--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency><!-- 向量存儲引擎--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-transformers-spring-boot-starter</artifactId></dependency><!-- 向量庫--><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency><!-- 文檔解析器--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency><!-- lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><resources><resource><directory>src/main/java</directory><!--所在的目錄--><includes><!--包括目錄下的.properties,.xml 文件都會被掃描到--><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.2.5</version></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories>
</project>
與第一篇相比,多了如下依賴
每個依賴的作用也已經在文件中加了注釋
修改application.yml
server:port: 3210spring:#向量庫datasource:url: jdbc:postgresql://localhost:5432/postgresusername: postgrespassword: #數據庫密碼driver-class-name: org.postgresql.Driverai:#調用ai大模型(可使用本地化部署模型,也可以使用線上的)openai:base-url: https://api.siliconflow.cnapi-key: #你自己申請的keychat:options:model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B#調用矢量化模型embedding:transformer:onnx:modelUri: classpath:/text2vec-base-chinese/onnx/model.onnxtokenizer:uri: classpath:/text2vec-base-chinese/onnx/tokenizer.json#矢量化配置vectorstore:pgvector:index-type: HNSWdistance-type: COSINE_DISTANCEdimensions: 768
EmbeddingController(矢量化接口)
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.util.List;import static java.util.stream.Collectors.toList;/*** @Author majinzhong* @Date 2025/4/30 14:00* @Version 1.0*/
@RestController
public class EmbeddingController {@AutowiredVectorStore vectorStore;@PostMapping("/ai/vectorStore")public List<String> vectorStore(@RequestParam(name = "file") MultipartFile file) throws Exception {// 從IO流中讀取文件TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(new InputStreamResource(file.getInputStream()));// 將文本內容劃分成更小的塊List<Document> splitDocuments = new TokenTextSplitter().apply(tikaDocumentReader.read());// 存入向量數據庫,這個過程會自動調用embeddingModel,將文本變成向量再存入。vectorStore.add(splitDocuments);return splitDocuments.stream().map(Document::getText).collect(toList());}@GetMapping("/ai/vectorSearch")public List<String> vectorSearch(@RequestParam(name = "text") String text) {List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder().query(text).topK(1).build());return documents.stream().map(Document::getText).collect(toList());}
}
修改SimpleAiController(AI接口)
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;import java.util.List;
import java.util.Map;/*** @Author majinzhong* @Date 2025/4/28 10:37* @Version 1.0* SpringAI對話樣例*/
@CrossOrigin
@RestController
public class SimpleAiController {@AutowiredVectorStore vectorStore;// 負責處理OpenAI的bean,所需參數來自properties文件private final ChatClient chatClient;//對話記憶private final InMemoryChatMemory inMemoryChatMemory;public SimpleAiController(ChatClient chatClient,InMemoryChatMemory inMemoryChatMemory) {this.chatClient = chatClient;this.inMemoryChatMemory = inMemoryChatMemory;}/*** 根據消息直接輸出回答* @param map* @return*/@PostMapping("/ai/call")public String call(@RequestBody Map<String,String> map) {String message = map.get("message");return chatClient.prompt().user(message).call().content().trim();}/*** 根據消息采用流式輸出* @param message* @return*/@PostMapping(value = "/ai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<String>> streamChat(@RequestParam(value = "message", defaultValue = "Hello!") String message) {return chatClient.prompt(message).stream().content().map(content -> ServerSentEvent.builder(content).event("message").build())//問題回答結速標識,以便前端消息展示處理.concatWithValues(ServerSentEvent.builder("").build()).onErrorResume(e -> Flux.just(ServerSentEvent.builder("Error: " + e.getMessage()).event("error").build()));}/*** 對話記憶(多輪對話)* @param message* @return* @throws InterruptedException*/@GetMapping(value = "/ai/streamresp", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<String>> streamResp(@RequestParam(value = "message", defaultValue = "Hello!") String message){Flux<ServerSentEvent<String>> serverSentEventFlux = chatClient.prompt(message).advisors(new MessageChatMemoryAdvisor(inMemoryChatMemory, "123", 10), new SimpleLoggerAdvisor()).stream().content().map(content -> ServerSentEvent.builder(content).event("message").build())//問題回答結速標識,以便前端消息展示處理.concatWithValues(ServerSentEvent.builder("").build()).onErrorResume(e -> Flux.just(ServerSentEvent.builder("Error: " + e.getMessage()).event("error").build()));return serverSentEventFlux;}/*** 整合知識庫和自己提問的問題一塊向AI提問* @param message* @return*/@GetMapping("/ai/vectorStoreChat")public Flux<String> ollamaApi(@RequestParam(value = "message") String message) {//從知識庫檢索相關信息,再將檢索得到的信息同用戶的輸入一起構建一個prompt,最后調用ollama apiList<Document> documents = vectorStore.similaritySearch(SearchRequest.builder().query(message).topK(1).build());String targetMessage = String.format("已知信息:%s\n 用戶提問:%s\n", documents.get(0).getText(), message);return chatClient.prompt(targetMessage).stream().content();}
}
與第一篇相比,多了如下代碼
測試
首先測試上傳文件到矢量庫
上傳成功之后,查看數據庫已經有數據了
然后測試查詢矢量庫
最后測試矢量庫和AI統一的接口
再來看一下如果只通過AI進行查詢會返回什么(沒有矢量庫)
如此看來,本地化知識庫成功,接下來就剩往里面一直添數據了。