Springboot集成Milvus和Embedding服务,实现向量化检索
Milvus 是一款开源向量数据库,专为支持大规模向量检索而设计,特别适用于大模型领域中的应用。本文详细介绍如何利用 Spring Boot 框架集成 Milvus 向量数据库,并通过调用阿里云百炼大模型服务平台所提供的 Embedding服务,实现数据的向量化存储与高效检索。此过程不仅验证了 Milvus 向量数据库的基本能力,还展示了其与先进 AI 服务无缝对接的灵活性。
1、前提条件
- JDK为17以上版本,本人使用的jdk21版本;
- SpringBoot版本为3.x以上,本项目使用的是SpringBoot 3.3.3版本;
- 开通阿里大模型服务(目前是免费6个月),获取 API-KEY,后面代码里要使用。具体操作,请参考阿里云大模型服务平台百炼:如何获取API Key_大模型服务平台百炼(Model Studio)-阿里云帮助中心
- 提前安装部署好Milvus数据库,本文示例使用的Milvus2.5.4版本
2、添加Manve依赖
创建springboot工程后,在pom.xml文件里引入milvus-sdk-java和spring-ai-alibaba-starter。
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
本示例使用的是milvus2.5.4最新版本,Java sdk 接口参考文档:About - Milvus java sdk v2.5.x
注意使用sdk版本跟milvus版本的对应关系,milvus2.5.x版本建议使用sdk2.5.2以上版本,否则可能会出现一些诡异问题。
Pom.xml完整内容如下:
<?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>com.yuncheng</groupId>
<artifactId>spring-ai-helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-ai-alibaba.version>1.0.0-M3.3</spring-ai-alibaba.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.5.4</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>aliyun</id>
<name>aliyun Repository</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
3、配置yml文件
#配置milvus向量数据库的IP、端口以及阿里云AI服务的api-key
server:
port: 8080
milvus:
host: 192.168.3.17
port: 19530
spring:
application:
name: spring-ai-helloworld
ai:
dashscope:
api-key: sk-b90ad31bb3eb4a158524928354f37dc5
4、创建MilvusClient初始化类
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MilvusConfig {
@Value("${milvus.host}")
private String host;
@Value("${milvus.port}")
private Integer port;
@Bean
public MilvusClientV2 milvusClientV2() {
String uri = "http://"+host+":"+port;
ConnectConfig connectConfig = ConnectConfig.builder()
.uri(uri)
.build();
return new MilvusClientV2(connectConfig);
}
}
5、创建操作向量库的Seivce
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.yuncheng.milvus.TestRecord;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.InsertResp;
import io.milvus.v2.service.vector.response.SearchResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Component
public class MilvusEmbeddingService {
private static final Logger log = LoggerFactory.getLogger(MilvusEmbeddingService.class);
//类似于mysql中的表,定义一个名称为collection_02的集合
private static final String COLLECTION_NAME = "collection_02";
//向量维度定义1536,跟阿里巴巴embedding向量服务返回的维度保持一致
private static final int VECTOR_DIM = 1536;
private final MilvusClientV2 client;
//注入阿里巴巴EmbeddingModel
@Autowired
private EmbeddingModel embeddingModel;
public MilvusEmbeddingService(MilvusClientV2 client) {
this.client = client;
}
/**
* 创建一个Collection
*/
public void createCollection() {
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.VarChar)
.isPrimaryKey(true)
.autoID(false)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("title")
.dataType(DataType.VarChar)
.maxLength(10000)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("title_vector")
.dataType(DataType.FloatVector)
.dimension(VECTOR_DIM)
.build());
IndexParam indexParam = IndexParam.builder()
.fieldName("title_vector")
.metricType(IndexParam.MetricType.COSINE)
.build();
CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
.collectionName(COLLECTION_NAME)
.collectionSchema(schema)
.indexParams(Collections.singletonList(indexParam))
.build();
client.createCollection(createCollectionReq);
}
/**
* 往collection中插入一条数据
*/
public void insertRecord(TestRecord record) {
JsonObject vector = new JsonObject();
vector.addProperty("id", record.getId());
vector.addProperty("title", record.getTitle());
List<Float> vectorList = new ArrayList<>();
Gson gson = new Gson();
//调用阿里向量模型服务,返回1536维向量float
float[] floatArray = embeddingModel.embed(record.getTitle());
for (float f : floatArray) {
vectorList.add(f);
}
vector.add("title_vector", gson.toJsonTree(vectorList));
InsertReq insertReq = InsertReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(vector))
.build();
InsertResp resp = client.insert(insertReq);
}
/**
* 按照向量检索,找到相似度最近的topK
*/
public List<List<SearchResp.SearchResult>> queryVector(String queryText) {
//调用阿里向量模型服务,对查询条件进行向量化
float[] floatArray = embeddingModel.embed(queryText);
SearchResp searchR = client.search(SearchReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(new FloatVec(floatArray)))
.topK(3)
.outputFields(Collections.singletonList("*"))
.build());
List<List<SearchResp.SearchResult>> searchResults = searchR.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
for (SearchResp.SearchResult result : results) {
log.info("ID="+(String)result.getId() + ",Score="+result.getScore() + ",Result="+result.getEntity().toString());
}
}
return searchResults;
}
}
这里使用到的一个简单的pojo类
public class TestRecord {
private String id;
private String title;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
6、创建Controller类
import com.yuncheng.milvus.service.MilvusEmbeddingService;
import io.milvus.v2.service.vector.response.SearchResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/milvus")
public class MilvusEmbeddingController {
private static final Logger log = LoggerFactory.getLogger(MilvusEmbeddingController.class);
@Autowired
private MilvusEmbeddingService milvusEmbeddingService;
@GetMapping("/createCollection")
public void createCollection() {
milvusEmbeddingService.createCollection();
}
@GetMapping("/insertRecord")
public void insertRecord() throws IOException {
TestRecord record = new TestRecord();
record.setId("1");
record.setTitle("男士纯棉圆领短袖T恤 白色 夏季休闲");
milvusEmbeddingService.insertRecord(record);
record = new TestRecord();
record.setId("2");
record.setTitle("女士碎花雪纺连衣裙 长款 春装");
milvusEmbeddingService.insertRecord(record);
record = new TestRecord();
record.setId("3");
record.setTitle("男款运动速干短袖 黑色 透气健身服");
milvusEmbeddingService.insertRecord(record);
record = new TestRecord();
record.setId("4");
record.setTitle("女童蕾丝公主裙 粉色 儿童节礼服");
milvusEmbeddingService.insertRecord(record);
record = new TestRecord();
record.setId("5");
record.setTitle("男士条纹POLO衫 商务休闲 棉质");
milvusEmbeddingService.insertRecord(record);
}
@GetMapping("/queryVector")
public List<List<SearchResp.SearchResult>> queryVector() {
String queryText = "男款透气运动T恤";
List<List<SearchResp.SearchResult>> searchResults = milvusEmbeddingService.queryVector(queryText);
return searchResults;
}
}
7、测试验证向量化检索
首先确保Milvus2.5.4向量数据库正常运行,然后启动springboot工程。
本示例假设构建一个“商品相似度”搜索引擎,在向量数据库里先插入一些商品描述数据,然后输入商品名称,找到相似的商品。
7.1、创建Collection
http://localhost:8080/milvus/createCollection
执行后,登录milvus控制台webUI查看,点击collection名称,查看详细的结构定义,类似于mysql中的表结构定义:
其中,id、title、title_vector字段是程序里定义的字段,另外RowID和Timestamp字段是collection默认自带的字段。
7.2、插入数据
http://localhost:8080/milvus/insertRecord
为了便于测试,本示例插入了5条有一定语义的商品描述数据:
texts = [
"男士纯棉圆领短袖T恤 白色 夏季休闲", # 文本1
"女士碎花雪纺连衣裙 长款 春装", # 文本2
"男款运动速干短袖 黑色 透气健身服", # 文本3
"女童蕾丝公主裙 粉色 儿童节礼服", # 文本4
"男士条纹POLO衫 商务休闲 棉质" # 文本5
]
插入数据的时候,每条数据通过调用阿里巴巴的embeddingModel进行向量化,并存储到Milvus的向量字段中。代码段如下:
public void insertRecord(TestRecord record) {
JsonObject vector = new JsonObject();
vector.addProperty("id", record.getId());
vector.addProperty("title", record.getTitle());
List<Float> vectorList = new ArrayList<>();
Gson gson = new Gson();
//调用阿里向量模型服务,返回1536维向量float
float[] floatArray = embeddingModel.embed(record.getTitle());
for (float f : floatArray) {
vectorList.add(f);
}
vector.add("title_vector", gson.toJsonTree(vectorList));
InsertReq insertReq = InsertReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(vector))
.build();
InsertResp resp = client.insert(insertReq);
}
7.3、按向量检索相似度
检索关键词为: "男款透气运动T恤",检索相似度最高的前3条记录,期望输出结果按匹配度由高到低依次为:
- 男款运动速干短袖 黑色 透气健身服
- 男士纯棉圆领短袖T恤 白色 夏季休闲
- 男士条纹POLO衫 商务休闲 棉质
验证Milvus向量数据库实际返回结果.
http://localhost:8080/milvus/queryVector
返回结果集:
[
[{
"entity": {
"title_vector": [],
"id": "3",
"title": "男款运动速干短袖 黑色 透气健身服"
},
"score": 0.6541121,
"id": "3"
},
{
"entity": {
"title_vector": [],
"id": "1",
"title": "男士纯棉圆领短袖T恤 白色 夏季休闲"
},
"score": 0.56386036,
"id": "1"
},
{
"entity": {
"title_vector": [],
"id": "5",
"title": "男士条纹POLO衫 商务休闲 棉质"
},
"score": 0.42506745,
"id": "5"
}
]
]
根据真实向量检索测试的结果显示,Milvus 返回的检索数据与预期高度一致。其中,score代表向量之间的相似度得分,得分越高表示两个向量间的相似度越大。本次示例测试的结果进一步验证了阿里云AI平台中Embedding模型算法的有效性,以及Milvus在向量化存储和检索方面的卓越性能。这表明,通过结合先进的Embedding技术与高效的向量数据库管理,可以显著提升数据处理的准确性和效率,为实际应用提供了可靠的技术保障。