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条记录,期望输出结果按匹配度由高到低依次为:

  1. 男款运动速干短袖 黑色 透气健身服
  2. 男士纯棉圆领短袖T恤 白色 夏季休闲
  3. 男士条纹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技术与高效的向量数据库管理,可以显著提升数据处理的准确性和效率,为实际应用提供了可靠的技术保障。

相关推荐
来旺12 小时前
互联网大厂Java面试全解析及三轮问答专项
java·数据库·spring boot·安全·缓存·微服务·面试
摇滚侠12 小时前
Spring Boot 3零基础教程,yml文件中配置和类的属性绑定,笔记15
spring boot·redis·笔记
thginWalker12 小时前
使用Spring Boot构建消息通信层
spring boot
lang2015092813 小时前
Spring Boot 外部化配置最佳实践指南
java·spring boot
摇滚侠13 小时前
Spring Boot 3零基础教程,WEB 开发 HTTP 缓存机制 笔记29
spring boot·笔记·缓存
Knight_AL13 小时前
Spring Boot 中使用自定义注解和 AOP 实现微服务日志记录(包含 URL、状态码和耗时信息)
linux·spring boot·微服务
Q_Q196328847513 小时前
python+vue的在线租房 房屋租赁系统
开发语言·vue.js·spring boot·python·django·flask·node.js
摇滚侠13 小时前
Spring Boot 3零基础教程,WEB 开发 内容协商 接口返回 YAML 格式的数据 笔记35
spring boot·笔记·后端
java水泥工14 小时前
旅游管理系统|基于SpringBoot和Vue的旅游管理系统(源码+数据库+文档)
spring boot·vue·计算机毕业设计·java毕业设计·旅游管理系统
计算机学长felix15 小时前
基于SpringBoot的“基于数据安全的旅游民宿租赁系统”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·旅游