SpringAIalibaba +milvus本地化全链路知识库系统

本地化全链路知识库系统

结合 Spring AIAlibaba 本地 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 服务默认监听 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

关键注意事项

  1. 模型性能优化
  • Qwen-7B 需至少 8GB 显存(INT4 量化版),建议使用 GPU 加速(需配置 DJL 支持 CUDA)。

  • 文本分割长度需与 Embedding 模型最大输入长度匹配(通义千问建议 ≤512 tokens)。

  1. Milvus 索引优化

    大规模数据时,为 Milvus 集合创建索引(如 IVF_FLAT)提升检索速度:

    // 在MilvusVectorStore初始化时添加索引

    milvusClient.createIndex(CreateIndexParam.newBuilder()

    复制代码
        .withCollectionName(collectionName)
    
        .withIndexType(IndexType.IVF\_FLAT)
    
        .withMetricType(MetricType.COSINE)
    
        .withParamsInJson("{\\"nlist\\": 1024}")
    
        .build());
  2. 错误处理

    增加模型加载超时、Milvus 连接失败的重试机制,确保服务稳定性。

通过以上方案,实现了 本地化全链路 的知识库系统:文档解析、向量生成、存储、检索、问答均在本地完成,结合 Spring AI 的抽象接口和 Milvus 的高效向量检索,兼顾开发便捷性与性能。

相关推荐
镜花水月linyi2 分钟前
ConcurrentHashMap 深入解析:从0到1彻底掌握(1.3万字)
java·后端
极客Bob3 分钟前
Java 集合操作完整清单(Java 8+ Stream API)
java
雨中飘荡的记忆3 分钟前
Javassist实战指南
java
Knight_AL11 分钟前
JWT 无状态认证深度解析:原理、优势
java·jwt
AI即插即用17 分钟前
即插即用系列 | CVPR 2025 WPFormer:用于表面缺陷检测的查询式Transformer
人工智能·深度学习·yolo·目标检测·cnn·视觉检测·transformer
寒山李白27 分钟前
IDEA中如何配置Java类注释(Java类注释信息配置,如作者、备注、时间等)
java
唐兴通个人27 分钟前
数字化AI大客户营销TOB营销客户开发专业销售技巧培训讲师培训师唐兴通老师分享AI销冠人工智能销售AI赋能销售医药金融工业品制造业
人工智能·金融
我要添砖java29 分钟前
<JAVAEE> 多线程4-wait和notify方法
android·java·java-ee
Rysxt_38 分钟前
Spring Boot SPI 教程
java·数据库·sql
海边夕阳200639 分钟前
主流定时任务框架对比:Spring Task/Quartz/XXL-Job怎么选?
java·后端·spring·xxl-job·定时任务·job