目录
前言
做 AI 大模型技术调研时,参考的开源项目 Maxkb,它基于大模型做了一个知识库的应用,用户可构建自己的知识库,创建自己的应用然后关联知识库,这样可以基于知识库里的内容让大模型的回答更加符合我们地预期。
虽然 Maxkb 是使用 Python 写的,不过参考它用到的相关模型和数据库,可以用 Spring Boot 来构建一个自己的知识库服务。Spring Boot 也提供了 AI 相关的库,这使得接入 AI 大模型能力也十分方便。
以下是使用Spring Boot 构建一个知识库的样例。
环境准备
JDK17
Spring Boot 3.2.4
Ollama
如果没有安装,可参考以下博客安装并下载 qwen2 大模型
PostgreSQL16
如果没有安装,可参考以下博客安装并安装 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);
下载向量化模型
shibing624_text2vec-base-chinese
下载地址:
https://huggingface.co/shibing624/text2vec-base-chinesehttps://huggingface.co/shibing624/text2vec-base-chinese将下载的模型放到项目工程 resources 文件夹下
pom
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-transformers-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-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>
</dependencies>
<repositories>
<!-- 里程碑(Milestone)-->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<!-- 快照(Snapshot)-->
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
yml
spring: datasource: url: jdbc:postgresql://localhost:5432/postgres username: postgres password: postgres driver-class-name: org.postgresql.Driver ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen2 embedding: transformer: onnx: modelUri: classpath:/shibing624_text2vec-base-chinese/onnx/model.onnx tokenizer: uri: classpath:/shibing624_text2vec-base-chinese/onnx/tokenizer.json vectorstore: pgvector: index-type: HNSW distance-type: COSINE_DISTANCE dimensions: 768 server: port: 8888
更多ai模型和向量模型的详细配置可参照官方文档
ETL Pipeline :: Spring AI Referencehttps://docs.spring.io/spring-ai/reference/api/etl-pipeline.html
EmbeddingController
@RestController
public class EmbeddingController {
@Autowired
@Qualifier("embeddingModel")
EmbeddingModel embeddingModel;
@Autowired
VectorStore vectorStore;
@GetMapping("/ai/embedding")
public void embed(@RequestParam(value = "message") String message) {
System.out.println(embeddingModel.embed(message));
System.out.println("ok");
}
@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::getContent).collect(toList());
}
@GetMapping("/ai/vectorSearch")
public List<String> vectorSearch(@RequestParam(name = "text") String text) {
List<Document> documents = vectorStore.similaritySearch(SearchRequest.query(text).withTopK(1));
return documents.stream().map(Document::getContent).collect(toList());
}
}
向量化示例
使用 AI 随便生成一段文本内容,这里让 AI 生成了 ollama 相关的文本
向量化文本
上传文档到知识库,其实就是调用向量化接口 /ai/vectorStore
查询向量数据库,可以看到插入了两条数据
代码中使用了 TokenTextSplitter 进行了分段处理,文档内容过多会被切割成多个再进行向量化
向量化检索
调用 /ai/vectorSearch 接口进行向量化检索
查询 vector_store 表,返回最匹配的一行数据
ChatController
@RestController
public class ChatController {
@Autowired
OllamaChatModel ollamaChatModel;
@Autowired
OllamaApi ollamaApi;
@Autowired
VectorStore vectorStore;
@GetMapping("/ai/generate")
public String generate(@RequestParam(value = "message") String message) {
return ollamaChatModel.call(message);
}
@GetMapping("/ai/generateStream")
public Flux<ChatResponse> generateStream(@RequestParam(value = "message") String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return ollamaChatModel.stream(prompt);
}
@GetMapping("/ai/ollamaApi")
public OllamaApi.ChatResponse ollamaApi(@RequestParam(value = "message") String message) {
//从知识库检索相关信息,再将检索得到的信息同用户的输入一起构建一个prompt,最后调用ollama api
List<Document> documents = vectorStore.similaritySearch(SearchRequest.query(message).withTopK(1));
String targetMessage = String.format("已知信息:%s\n 用户提问:%s\n",
documents.get(0).getContent(), message);
OllamaApi.ChatRequest request = OllamaApi.ChatRequest.builder("qwen2")
.withStream(false) // not streaming
.withMessages(List.of(
OllamaApi.Message.builder(OllamaApi.Message.Role.USER)
.withContent(targetMessage)
.build()))
.withOptions(OllamaOptions.create().withTemperature(0.9f))
.build();
return ollamaApi.chat(request);
}
}
知识库示例
调用 /ai/ollamaApi 接口
先从知识库检索相关信息
再将检索得到的信息同用户的输入一起构建一个 prompt
最后调用 ollama api