本地化全链路知识库系统
结合 Spring AI 、Alibaba 本地 AI 模型 (如通义千问)和 Milvus 向量数据库 实现本地知识库,可构建一套数据本地化、检索高效、兼容 Spring 生态的方案。核心是利用 Milvus 存储向量,通义千问模型处理文本嵌入和问答,Spring AI 简化组件整合。以下是具体实现方案:
核心技术栈
| 环节 | 技术选型 | 说明 |
|---|---|---|
| 开发框架 | Spring Boot 3.x + Spring AI 0.8.1+ | 提供依赖注入、抽象接口(向量存储、嵌入等) |
| 文档解析 | Apache Tika + Alibaba EasyExcel | 支持 PDF/Word/Excel/TXT 等格式解析 |
| 文本分割 | Spring AI TextSplitter | 按语义分割文本为 chunk(适配向量模型长度) |
| 向量嵌入(Embedding) | 通义千问 Embedding 本地模型 + DJL | 阿里巴巴开源模型,本地生成文本向量 |
| 向量数据库 | Milvus 2.3+(本地 Docker 部署) | 高效向量检索,支持大规模向量存储 |
| 大语言模型(LLM) | 通义千问 Qwen-7B(本地部署) + DJL | 基于检索到的上下文生成回答 |
实现步骤
1. 环境准备
-
Milvus 本地部署 :通过 Docker 启动 Milvus 单机版(参考 官方文档):
拉取 Milvus 单机版配置
wget https://github.com/milvus-io/milvus/releases/download/v2.3.4/milvus-standalone-docker-compose.yml -O docker-compose.yml
启动
docker-compose up -d
验证:Milvus 服务默认监听 localhost:19530。
-
模型下载:
-
通义千问 Embedding 模型(如
qwen-embedding-v1) -
通义千问 LLM(如
qwen-7b-chat,建议量化版)下载地址:ModelScope(需遵守开源协议)。
-
2. 项目依赖配置(pom.xml)
核心依赖包括 Spring AI、Milvus SDK、DJL(模型运行)、文档解析工具:
<!-- Spring Boot 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>0.8.1</version>
</dependency>
<!-- Milvus 向量数据库 SDK -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.3.4</version>
</dependency>
<!-- 文档解析 -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.0</version> <!-- 处理Excel -->
</dependency>
<!-- 本地模型运行(DJL) -->
<dependency>
<groupId>ai.djl</groupId>
<artifactId>api</artifactId>
<version>0.25.0</version>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-auto</artifactId>
<version>2.0.1</version>
<scope>runtime</scope>
</dependency>
3. 核心组件实现
3.1 文档加载与分割
支持多格式文档解析,并分割为适合嵌入的文本块:
import com.alibaba.excel.EasyExcel;
import org.apache.tika.Tika;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.splitter.TextSplitter;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class DocumentProcessor {
private final Tika tika = new Tika();
private final TextSplitter textSplitter = new TextSplitter(500, 50); // 500字符/块,重叠50字符
public List<Document> process(MultipartFile file) throws Exception {
String filename = file.getOriginalFilename();
String content = "";
// 按文件类型解析
if (filename.endsWith(".xlsx") || filename.endsWith(".xls")) {
// EasyExcel解析Excel
InputStream is = file.getInputStream();
List<ExcelData> dataList = EasyExcel.read(is).head(ExcelData.class).sheet().doReadSync();
content = dataList.stream().map(ExcelData::getContent).collect(Collectors.joining("\n"));
} else {
// Tika解析其他格式(PDF/Word/TXT等)
content = tika.parseToString(file.getInputStream());
}
// 分割为文档块
return textSplitter.split(new Document(filename, content));
}
// Excel数据模型
public static class ExcelData {
private String content;
// getter/setter
}
}
3.2 通义千问 Embedding 客户端(实现 Spring AI 接口)
使用 DJL 加载本地 Embedding 模型,将文本转为向量:
import ai.djl.inference.Predictor;
import ai.djl.modality.nlp.embedding.TextEmbedding;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class QwenEmbeddingClient implements EmbeddingClient {
@Value("\${qwen.embedding.model.path}")
private String embeddingModelPath; // 本地模型路径(application.properties配置)
private TextEmbedding embeddingModel;
@PostConstruct
public void init() throws Exception {
// 加载通义千问Embedding模型
Criteria<String, float\[]> criteria = Criteria.builder()
.setTypes(String.class, float\[].class)
.optModelUrls("file://" + embeddingModelPath)
.build();
ZooModel<String, float\[]> model = criteria.loadModel();
this.embeddingModel = model.newPredictor();
}
@Override
public EmbeddingResponse embed(EmbeddingRequest request) {
// 生成向量
List<float\[]> embeddings = request.getInputs().stream()
.map(text -> embeddingModel.embedText(text))
.collect(Collectors.toList());
// 包装为Spring AI的响应格式
return new EmbeddingResponse(
embeddings.stream()
.map(emb -> new EmbeddingResponse.Embedding(emb))
.collect(Collectors.toList())
);
}
}
3.3 Milvus 向量存储(实现 Spring AI VectorStore 接口)
封装 Milvus 操作,实现向量的存储与检索:
import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import io.milvus.param.collection.CreateCollectionParam;
import io.milvus.param.dml.InsertParam;
import io.milvus.param.dml.SearchParam;
import io.milvus.response.SearchResultsWrapper;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Component
public class MilvusVectorStore implements VectorStore {
@Value("\${milvus.host:localhost}")
private String milvusHost;
@Value("\${milvus.port:19530}")
private int milvusPort;
private final String collectionName = "local\_knowledge"; // 集合名(类似表)
private final int vectorDimension = 768; // 通义千问Embedding向量维度
private MilvusClient milvusClient;
@PostConstruct
public void init() {
// 连接Milvus
milvusClient = new MilvusServiceClient(
ConnectParam.newBuilder()
.withHost(milvusHost)
.withPort(milvusPort)
.build()
);
// 创建集合(若不存在)
milvusClient.createCollection(CreateCollectionParam.newBuilder()
.withCollectionName(collectionName)
.withDimension(vectorDimension)
.build());
}
// 存储文档向量到Milvus
@Override
public void add(List<Document> documents) {
for (Document doc : documents) {
// 生成向量(通过QwenEmbeddingClient)
float\[] vector = qwenEmbeddingClient.embed(doc.getContent()).getEmbeddings().get(0).getOutput();
// 构造插入参数
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName(collectionName)
.withFields(List.of(
UUID.randomUUID().toString(), // 文档ID
doc.getContent(), // 文本内容
vector // 向量
))
.build();
milvusClient.insert(insertParam);
}
// 插入后刷新集合(确保数据可查)
milvusClient.flush(collectionName);
}
// 检索相似文档
@Override
public List<Document> similaritySearch(String query, int topK) {
// 生成查询向量
float\[] queryVector = qwenEmbeddingClient.embed(query).getEmbeddings().get(0).getOutput();
// 构造检索参数(余弦相似度)
SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(collectionName)
.withQueryVectors(List.of(queryVector))
.withTopK(topK)
.withMetricType(SearchParam.MetricType.COSINE) // 余弦相似度
.build();
// 执行检索
SearchResultsWrapper results = new SearchResultsWrapper(
milvusClient.search(searchParam).getData()
);
// 转换结果为Document列表
List<Document> docs = new ArrayList<>();
results.getFieldData("content", String.class).forEach(content -> {
docs.add(new Document(UUID.randomUUID().toString(), content));
});
return docs;
}
}
3.4 通义千问 LLM 问答服务
使用本地 Qwen-7B 模型生成回答:
import ai.djl.modality.nlp.generate.TextGenerator;
import ai.djl.repository.zoo.ZooModel;
import org.springframework.ai.document.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
@Service
public class QwenLlmService {
@Value("\${qwen.llm.model.path}")
private String llmModelPath; // 本地Qwen-7B模型路径
private TextGenerator llmGenerator;
@PostConstruct
public void init() throws Exception {
// 加载Qwen-7B模型
ZooModel<String, String> model = Criteria.builder()
.setTypes(String.class, String.class)
.optModelUrls("file://" + llmModelPath)
.build()
.loadModel();
this.llmGenerator = model.newPredictor();
}
// 结合上下文生成回答
public String generateAnswer(String question, List<Document> contextDocs) {
String context = contextDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// 构建提示词
String prompt = String.format("""
基于以下上下文回答问题,仅使用上下文信息,不编造内容:
上下文:%s
问题:%s
回答:
""", context, question);
return llmGenerator.generate(prompt);
}
}
4. 知识库服务整合与接口
封装完整流程,提供文档上传和问答接口:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.\*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
@RequestMapping("/kb")
public class KnowledgeBaseController {
private final DocumentProcessor documentProcessor;
private final MilvusVectorStore vectorStore;
private final QwenLlmService llmService;
// 依赖注入
public KnowledgeBaseController(DocumentProcessor documentProcessor,
MilvusVectorStore vectorStore,
QwenLlmService llmService) {
this.documentProcessor = documentProcessor;
this.vectorStore = vectorStore;
this.llmService = llmService;
}
// 上传文档到知识库
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
List<Document> docs = documentProcessor.process(file);
vectorStore.add(docs);
return ResponseEntity.ok("文档处理完成,已添加 " + docs.size() + " 个片段到知识库");
}
// 基于知识库问答
@GetMapping("/query")
public ResponseEntity<String> query(@RequestParam String question) {
List<Document> contextDocs = vectorStore.similaritySearch(question, 3); // 取top3相似文档
String answer = llmService.generateAnswer(question, contextDocs);
return ResponseEntity.ok(answer);
}
}
5. 配置文件(application.properties)
# 通义千问模型路径
qwen.embedding.model.path=/path/to/qwen-embedding-v1
qwen.llm.model.path=/path/to/qwen-7b-chat-int4 # 建议使用INT4量化版节省资源
# Milvus配置
milvus.host=localhost
milvus.port=19530
关键注意事项
- 模型性能优化:
-
Qwen-7B 需至少 8GB 显存(INT4 量化版),建议使用 GPU 加速(需配置 DJL 支持 CUDA)。
-
文本分割长度需与 Embedding 模型最大输入长度匹配(通义千问建议 ≤512 tokens)。
-
Milvus 索引优化:
大规模数据时,为 Milvus 集合创建索引(如 IVF_FLAT)提升检索速度:
// 在MilvusVectorStore初始化时添加索引
milvusClient.createIndex(CreateIndexParam.newBuilder()
.withCollectionName(collectionName) .withIndexType(IndexType.IVF\_FLAT) .withMetricType(MetricType.COSINE) .withParamsInJson("{\\"nlist\\": 1024}") .build()); -
错误处理:
增加模型加载超时、Milvus 连接失败的重试机制,确保服务稳定性。
通过以上方案,实现了 本地化全链路 的知识库系统:文档解析、向量生成、存储、检索、问答均在本地完成,结合 Spring AI 的抽象接口和 Milvus 的高效向量检索,兼顾开发便捷性与性能。