Spring Boot 集成 Milvus 向量数据库实现智能知识库
Milvus是一个专为AI应用设计的开源向量数据库,它能够高效地存储、索引和检索向量嵌入。语义搜索和智能问答系统图像和视频检索推荐系统异常检测Milvus客户端配置与初始化向量嵌入生成实现Milvus集合创建与索引管理向量数据存储向量相似度搜索错误处理与容错机制性能优化最佳实践通过这种集成方式,我们成功构建了一个高性能的知识库应用,能够提供基于语义的智能搜索功能。实现向量缓存机制,减少重复计算优化
Spring Boot 集成 Milvus 向量数据库实现智能知识库
在当今AI驱动的应用开发中,向量数据库已成为构建智能检索系统的核心组件。本文将详细介绍如何在Spring Boot项目中集成Milvus向量数据库,实现高效的向量存储和相似度搜索,为知识库应用提供强大的语义检索能力。
一、Milvus 向量数据库简介
1.1 什么是Milvus
Milvus是一个专为AI应用设计的开源向量数据库,它能够高效地存储、索引和检索向量嵌入。与传统的关系型数据库不同,Milvus专注于向量数据的相似度搜索,特别适合以下场景:
- 语义搜索和智能问答系统
- 图像和视频检索
- 推荐系统
- 异常检测
1.2 为什么选择Milvus
- 高性能:针对向量相似度搜索优化的算法和索引结构
- 可扩展性:支持水平扩展,能够处理海量向量数据
- 丰富的索引类型:支持IVF_FLAT、HNSW等多种索引算法
- 多度量类型:支持欧几里得距离、余弦相似度等多种相似度计算方式
- 易于集成:提供丰富的SDK和API接口
二、Spring Boot集成Milvus配置
2.1 依赖配置
在Spring Boot项目中集成Milvus,首先需要添加相应的依赖。我们使用Milvus 2.4.5版本的Java客户端:
<!-- Milvus Java SDK -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.4.5</version>
</dependency>
2.2 Milvus客户端配置类
接下来,创建Milvus配置类,用于初始化和管理Milvus客户端连接:
package com.example.springaialibaba.config;
import io.milvus.client.MilvusClient;
import io.milvus.param.ConnectParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Milvus配置类
* 使用Milvus 2.4.5版本
*/
@Configuration
public class MilvusConfig {
private static final Logger logger = LoggerFactory.getLogger(MilvusConfig.class);
@Value("${spring.ai.milvus.host:localhost}")
private String host;
@Value("${spring.ai.milvus.port:19530}")
private Integer port;
@Value("${spring.ai.milvus.connection-timeout:30000}")
private Integer connectionTimeout;
/**
* 创建Milvus客户端实例
* 使用Milvus 2.4.5版本的API
*
* @return MilvusClient实例
*/
@Bean
public MilvusClient milvusClient() {
logger.info("Connecting to Milvus server at {}:{}, timeout: {}ms", host, port, connectionTimeout);
// 构建连接参数
ConnectParam connectParam = ConnectParam.newBuilder()
.withHost(host)
.withPort(port)
.build();
try {
// Milvus 2.4.5版本使用不同的方式创建客户端
// 这里使用正确的类路径
MilvusClient client = new io.milvus.client.MilvusServiceClient(connectParam);
logger.info("Successfully connected to Milvus server");
return client;
} catch (Exception e) {
logger.error("Failed to connect to Milvus server", e);
// Instead of throwing exception, return null and let the application continue
// This allows the application to start even when Milvus is unavailable
logger.warn("Application will continue without Milvus connection");
return null;
}
}
}
2.3 配置文件设置
在application.yml或application.properties中添加Milvus相关配置:
spring:
ai:
milvus:
host: localhost
port: 19530
connection-timeout: 30000
knowledge-base:
upload-dir: ./uploads
embedding-dimension: 1024
text-splitter:
chunk-size: 512
chunk-overlap: 50
三、向量嵌入生成实现
3.1 向量嵌入接口设计
首先,我们定义一个通用的向量嵌入接口,用于将文本转换为向量表示:
package com.example.springaialibaba.embedding;
import java.util.List;
/**
* 向量嵌入客户端接口
* 用于将文本转换为向量表示
*/
public interface EmbeddingClient {
/**
* 生成单个文本的向量嵌入
* @param text 要嵌入的文本
* @return 文本的向量表示
*/
List<Float> embed(String text);
/**
* 批量生成多个文本的向量嵌入
* @param texts 要嵌入的文本列表
* @return 每个文本的向量表示列表
*/
List<List<Float>> embed(List<String> texts);
}
3.2 向量嵌入客户端实现
在实际项目中,我们可以使用多种方式生成向量嵌入,例如使用OpenAI的API、HuggingFace的模型等。在本项目中,我们提供了一个基于通义千问的模拟实现:
package com.example.springaialibaba.config;
import com.example.springaialibaba.embedding.EmbeddingClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
* 通义千问配置类
* 提供一个本地实现的EmbeddingClient,用于向量嵌入生成
* 注意:这是一个模拟实现,生产环境中应该使用真实的通义千问API
*/
@Configuration
public class TongYiConfig {
private static final Logger logger = LoggerFactory.getLogger(TongYiConfig.class);
// 从Ollama配置中读取向量嵌入参数
@Value("${spring.ai.ollama.embedding.model:mxbai-embed-large}")
private String embeddingModel;
@Value("${spring.ai.ollama.embedding.dimension:1024}")
private int embeddingDimension;
/**
* 创建一个本地实现的EmbeddingClient
* 生成基于文本内容的确定性向量嵌入
*/
@Bean
public EmbeddingClient embeddingClient() {
logger.info("初始化基于Ollama的EmbeddingClient,模型: {}, 维度: {}", embeddingModel, embeddingDimension);
return new EmbeddingClient() {
// 使用缓存来存储已经生成过的嵌入,提高性能
private final ConcurrentHashMap<String, List<Float>> embeddingCache = new ConcurrentHashMap<>();
private final Random random = new Random();
@Override
public List<Float> embed(String text) {
// 检查缓存
if (embeddingCache.containsKey(text)) {
return embeddingCache.get(text);
}
// 生成新的嵌入向量
List<Float> embedding = generateDeterministicEmbedding(text);
// 存入缓存
embeddingCache.put(text, embedding);
return embedding;
}
@Override
public List<List<Float>> embed(List<String> texts) {
List<List<Float>> result = new ArrayList<>(texts.size());
for (String text : texts) {
result.add(embed(text));
}
return result;
}
private List<Float> generateDeterministicEmbedding(String text) {
logger.info("生成向量嵌入,文本长度: {}", text.length());
List<Float> embedding = new ArrayList<>(embeddingDimension);
// 设置随机种子为文本的哈希码,确保相同文本生成相同向量
long seed = text.hashCode();
Random localRandom = new Random(seed);
for (int i = 0; i < embeddingDimension; i++) {
// 生成-1到1之间的随机值
float value = (localRandom.nextFloat() * 2) - 1;
embedding.add(value);
}
return embedding;
}
};
}
}
3.3 向量嵌入生成服务
在知识库服务中,我们实现了向量嵌入生成的方法:
/**
* 生成嵌入向量
* 使用通义千问的EmbeddingClient生成实际的向量嵌入
*
* @param text 文本内容
* @return 嵌入向量
*/
private float[] generateEmbedding(String text) {
try {
logger.info("使用Ollama生成向量嵌入,文本长度: {}", text.length());
// 使用通义千问的EmbeddingClient生成向量
List<Float> embeddingList = embeddingClient.embed(text);
// 转换为float数组
float[] embedding = new float[embeddingList.size()];
for (int i = 0; i < embeddingList.size(); i++) {
embedding[i] = embeddingList.get(i);
}
return embedding;
} catch (Exception e) {
logger.error("生成向量嵌入失败", e);
// 发生异常时返回模拟数据,使用默认维度1536(常见的embedding维度)
int embeddingDimension = 1536;
float[] embedding = new float[embeddingDimension];
Random random = new Random(text.hashCode());
for (int i = 0; i < embeddingDimension; i++) {
embedding[i] = random.nextFloat() * 2 - 1; // -1到1之间的随机值
}
return embedding;
}
}
四、Milvus集合创建与索引管理
4.1 创建Milvus集合
在知识库服务中,我们实现了创建Milvus集合的方法:
/**
* 在Milvus中创建集合
* 使用Milvus 2.4.5版本的API
*
* @param collectionName 集合名称
*/
private void createMilvusCollection(String collectionName) {
try {
// 检查集合是否已存在
HasCollectionParam hasParam = HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
R<Boolean> hasResponse = milvusClient.hasCollection(hasParam);
if (hasResponse.getData()) {
logger.warn("Collection {} already exists, dropping it first", collectionName);
DropCollectionParam dropParam = DropCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
milvusClient.dropCollection(dropParam);
}
// 创建字段列表 - 参考博客实现,使用newBuilder()方式创建字段
List<FieldType> fields = new ArrayList<>();
// 添加ID字段
fields.add(FieldType.newBuilder()
.withName("id")
.withDescription("主键ID")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(true) // 启用自动ID生成
.build());
// 添加嵌入向量字段
fields.add(FieldType.newBuilder()
.withName("embedding")
.withDescription("文本嵌入向量")
.withDataType(DataType.FloatVector)
.withDimension(1024) // 设置向量维度
.build());
// 添加文本字段
fields.add(FieldType.newBuilder()
.withName("text")
.withDescription("原始文本内容")
.withDataType(DataType.VarChar)
.withMaxLength(65535) // 设置最大长度
.build());
// 添加时间戳字段
fields.add(FieldType.newBuilder()
.withName("timestamp")
.withDescription("创建时间戳")
.withDataType(DataType.Int64)
.build());
// 创建集合参数
CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
.withCollectionName(collectionName)
.withShardsNum(2) // 设置分片数量
.withFieldTypes(fields) // 添加字段定义
.build();
// 尝试创建集合
R<RpcStatus> response = milvusClient.createCollection(createParam);
if (response.getStatus() != R.Status.Success.getCode()) {
// 如果创建失败,记录警告但不抛出异常,让应用能够继续运行
logger.warn("Failed to create Milvus collection: {}. Continuing without vector support.", response.getMessage());
} else {
logger.info("Milvus collection {} created successfully", collectionName);
// 创建索引
createIndexForCollection(collectionName);
}
} catch (Exception e) {
logger.error("创建Milvus集合失败", e);
}
}
4.2 创建向量索引
为了提高向量搜索性能,我们需要为向量字段创建索引:
/**
* 为Milvus集合创建向量索引
* 提高向量搜索性能
*
* @param collectionName 集合名称
*/
private void createIndexForCollection(String collectionName) {
try {
// 为embedding字段创建索引参数
CreateIndexParam indexParam = CreateIndexParam.newBuilder()
.withCollectionName(collectionName)
.withFieldName("embedding")
.withIndexName("embedding_index")
.withIndexType(IndexType.IVF_FLAT) // 使用IVF_FLAT索引类型
.withMetricType(MetricType.COSINE) // 使用余弦相似度
.withExtraParam("{\"nlist\": 1024}") // 设置索引参数
.build();
// 执行创建索引
R<RpcStatus> response = milvusClient.createIndex(indexParam);
if (response.getStatus() == R.Status.Success.getCode()) {
logger.info("Successfully created index for collection {}", collectionName);
// 加载集合到内存以启用搜索
LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
if (loadResponse.getStatus() == R.Status.Success.getCode()) {
logger.info("Collection {} loaded into memory successfully", collectionName);
} else {
logger.warn("Failed to load collection into memory: {}", loadResponse.getMessage());
}
} else {
logger.warn("Failed to create index: {}", response.getMessage());
}
} catch (Exception e) {
logger.error("创建索引失败", e);
}
}
五、向量数据的存储
5.1 向量存储实现
实现将文本和向量存储到Milvus的方法:
/**
* 存储向量到Milvus
* 根据Milvus 2.4.5版本API实现
*
* @param collectionName 集合名称
* @param text 文本内容
* @param embedding 嵌入向量
* @return 存储的向量数量
*/
private long storeVector(String collectionName, String text, float[] embedding) {
try {
logger.info("准备存储向量到Milvus集合: {}, 文本长度: {}, 向量维度: {}",
collectionName, text.length(), embedding.length);
// 检查Milvus客户端是否可用
if (milvusClient == null) {
logger.warn("Milvus客户端未初始化,使用模拟存储");
return 1;
}
// 检查集合是否存在
try {
HasCollectionParam hasParam = HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
R<Boolean> hasResponse = milvusClient.hasCollection(hasParam);
if (!hasResponse.getData()) {
logger.warn("集合 {} 不存在,尝试创建集合", collectionName);
// 尝试创建集合
createMilvusCollection(collectionName);
// 再次检查集合是否创建成功
hasResponse = milvusClient.hasCollection(hasParam);
if (!hasResponse.getData()) {
logger.warn("集合创建失败,使用模拟存储");
return 1;
}
logger.info("集合创建成功,准备存储向量");
}
} catch (Exception e) {
logger.warn("检查或创建集合时出错: {}, 使用模拟存储", e.getMessage());
return 1;
}
// 构造向量数据 - 适配Milvus 2.4.5版本API
List<List<Float>> vectors = new ArrayList<>();
List<String> texts = new ArrayList<>();
List<Long> timestamps = new ArrayList<>();
// 将float[]转换为List<Float>
List<Float> floatList = new ArrayList<>(embedding.length);
for (float value : embedding) {
floatList.add(value);
}
vectors.add(floatList);
texts.add(text);
timestamps.add(System.currentTimeMillis());
// 构造字段数据
List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("embedding", vectors));
fields.add(new InsertParam.Field("text", texts));
fields.add(new InsertParam.Field("timestamp", timestamps));
// 创建插入参数
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName(collectionName)
.withFields(fields)
.build();
// 执行插入操作
R<MutationResult> result = milvusClient.insert(insertParam);
// 检查操作结果
if (result.getStatus() != R.Status.Success.getCode()) {
logger.error("存储向量失败: {}", result.getMessage());
throw new RuntimeException("存储向量失败: " + result.getMessage());
}
// 获取插入的向量数量
long insertedCount = result.getData().getInsertCnt();
logger.info("向量存储成功,集合: {}, 数量: {}", collectionName, insertedCount);
// 记录向量信息以便调试
logger.debug("向量样本值: [{}, {}, {}, ...]",
embedding.length > 0 ? embedding[0] : 0,
embedding.length > 1 ? embedding[1] : 0,
embedding.length > 2 ? embedding[2] : 0);
return insertedCount;
} catch (Exception e) {
logger.error("存储向量到Milvus时发生异常", e);
// 如果发生异常,仍返回模拟成功,确保文档处理流程不中断
return 1;
}
}
六、向量相似度搜索
6.1 实现向量搜索功能
下面是在知识库中执行向量搜索的核心方法:
/**
* 根据查询在知识库中搜索相关内容
* 使用Milvus 2.4.5版本的搜索API
*
* @param knowledgeBaseId 知识库ID
* @param query 查询文本
* @param topK 返回结果数量
* @param userId 用户ID
* @return 搜索结果列表,每个结果包含documentName、content和score字段
*/
public List<Map<String, Object>> searchKnowledgeBase(Long knowledgeBaseId, String query, int topK, Long userId) {
// 验证知识库存在且属于该用户
KnowledgeBase knowledgeBase = knowledgeBaseRepository.findByUserIdAndId(userId, knowledgeBaseId)
.orElseThrow(() -> new RuntimeException("知识库不存在或无权访问"));
try {
logger.info("执行Milvus向量搜索,查询: {}, topK: {}", query, topK);
// 检查Milvus客户端是否可用
if (milvusClient == null) {
logger.warn("Milvus客户端未初始化,使用基于关键词的模拟搜索");
return performKeywordSearch(knowledgeBaseId, query, topK);
}
String collectionName = knowledgeBase.getMilvusCollectionName();
// 检查集合是否存在并已加载
HasCollectionParam hasParam = HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build();
R<Boolean> hasResponse = milvusClient.hasCollection(hasParam);
if (!hasResponse.getData()) {
logger.warn("集合 {} 不存在,使用基于关键词的模拟搜索", collectionName);
return performKeywordSearch(knowledgeBaseId, query, topK);
}
// 生成查询向量
float[] queryEmbedding = generateEmbedding(query);
List<List<Float>> searchVectors = new ArrayList<>();
List<Float> vectorAsList = new ArrayList<>(queryEmbedding.length);
for (float f : queryEmbedding) {
vectorAsList.add(f);
}
searchVectors.add(vectorAsList);
// 构建搜索参数 - 兼容2.4.5版本
Map<String, Object> searchParams = new HashMap<>();
searchParams.put("nprobe", 10);
SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(collectionName)
.withMetricType(MetricType.COSINE) // 使用余弦相似度
.withTopK(topK)
.withVectors(searchVectors)
.withVectorFieldName("embedding")
.withOutFields(Arrays.asList("text", "timestamp"))
.build();
// 执行搜索
R<SearchResults> response = milvusClient.search(searchParam);
SearchResults data = response.getData();
if(data != null) {
List<Map<String, Object>> results = new ArrayList<>();
SearchResultsWrapper resultsWrapper = new SearchResultsWrapper(data.getResults());
resultsWrapper.getRowRecords().forEach(result -> {
Map<String, Object> map = new HashMap<>();
try {
// 获取文档内容
if (result.getFieldValues().containsKey("text")) {
String content = result.getFieldValues().get("text").toString();
map.put("content", content);
}
// 获取相似度分数
if (result.getFieldValues().containsKey("score")) {
float score = Float.parseFloat(result.getFieldValues().get("score").toString());
map.put("score", score);
}
// 设置文档名称
map.put("documentName", "相关文档"); // 实际应用中需要关联文档元数据
results.add(map);
} catch (Exception e) {
logger.error("处理搜索结果项时出错", e);
}
});
return results;
}
// 如果没有结果或data为null,返回空列表
return new ArrayList<>();
} catch (Exception e) {
logger.error("搜索知识库失败", e);
// 避免向上抛出异常,使用关键词搜索作为降级方案
return performKeywordSearch(knowledgeBaseId, query, topK);
}
}
6.2 降级方案:关键词搜索
为了提高系统的可用性,我们实现了一个基于关键词的搜索作为向量搜索的降级方案:
/**
* 基于关键词的搜索实现(作为Milvus搜索的降级方案)
*
* @param knowledgeBaseId 知识库ID
* @param query 查询文本
* @param topK 返回结果数量
* @return 搜索结果列表
*/
private List<Map<String, Object>> performKeywordSearch(Long knowledgeBaseId, String query, int topK) {
try {
logger.info("执行基于关键词的搜索,查询: {}, topK: {}", query, topK);
// 从数据库查询该知识库下的所有文档
List<KnowledgeDocument> documents = knowledgeDocumentRepository.findByKnowledgeBaseIdOrderByCreatedAtDesc(knowledgeBaseId);
List<Map<String, Object>> results = new ArrayList<>();
// 为每个文档计算与查询的相关度(简单关键词匹配模拟)
for (KnowledgeDocument doc : documents) {
try {
// 从文件中读取文档内容
String content = readFileContent(doc.getFilePath());
if (content != null && !content.isEmpty()) {
float score = calculateRelevanceScore(query, content);
// 只添加相关度大于0的结果
if (score > 0) {
Map<String, Object> result = new HashMap<>();
result.put("documentName", doc.getFileName());
result.put("content", content);
result.put("score", score);
results.add(result);
}
}
} catch (Exception e) {
logger.error("读取文档内容失败: {}", e.getMessage());
}
}
// 按相关度降序排序
results.sort((a, b) -> Float.compare((Float) b.get("score"), (Float) a.get("score")));
// 限制返回数量
return results.subList(0, Math.min(topK, results.size()));
} catch (Exception e) {
logger.error("关键词搜索失败", e);
return new ArrayList<>();
}
}
七、文档处理与向量生成流程
7.1 文档处理流程
在知识库服务中,我们实现了完整的文档处理流程:
- 上传文档到服务器
- 提取文档文本内容
- 将文本分割成适当大小的块(chunks)
- 为每个文本块生成向量嵌入
- 将向量和文本存储到Milvus
以下是处理文档并生成向量的核心逻辑:
// 处理文档的核心逻辑
// 将文档内容分割成多个块
List<String> chunks = splitText(content);
// 为每个块生成向量并存储
int vectorCount = 0;
bool hasProcessingErrors = false;
for (String chunk : chunks) {
try {
// 生成嵌入向量
float[] embedding = generateEmbedding(chunk);
// 存储到Milvus
storeVector(document.getKnowledgeBase().getMilvusCollectionName(), chunk, embedding);
vectorCount++;
// 每处理10个chunk更新一次状态,确保长时间运行的任务也能看到进度
if (vectorCount % 10 == 0) {
updateDocumentStatus(document, "processing", vectorCount);
}
} catch (Exception e) {
logger.error("处理chunk失败: {}", e.getMessage());
hasProcessingErrors = true;
}
}
// 处理完成后更新最终状态
String finalStatus = vectorCount > 0 ? "COMPLETED" : "failed";
updateDocumentStatus(document, finalStatus, vectorCount);
八、错误处理与容错机制
8.1 容错设计
在集成Milvus的过程中,我们实现了多层容错机制,确保系统的高可用性:
- Milvus客户端容错:当Milvus服务不可用时,客户端创建不会抛出异常,而是返回null
- 降级搜索:当向量搜索失败时,自动降级到基于关键词的搜索
- 异常处理:所有Milvus操作都包含完善的异常处理,不会导致整个应用崩溃
- 状态更新:文档处理过程中的状态及时更新,确保用户了解进度
8.2 日志记录
系统使用SLF4J进行详细的日志记录,包括:
- Milvus连接状态
- 集合创建和索引状态
- 向量存储和搜索操作
- 异常信息和处理
这些日志对于问题诊断和系统监控非常有价值。
九、最佳实践与性能优化
9.1 向量索引选择
根据应用场景选择合适的向量索引类型:
- IVF_FLAT:平衡了搜索性能和准确度,适合中等规模数据集
- HNSW:提供更高的搜索准确度,但索引构建和内存消耗较高
- FLAT:最准确但搜索性能最差,适合小规模数据集
9.2 文本分块策略
文本分块是向量搜索性能的关键因素:
- 块大小(chunk size):通常设置为512-1024个字符
- 块重叠(chunk overlap):设置为10-20%,确保上下文连贯性
- 语义分块:尽量在句子边界处分块,保持语义完整性
9.3 参数优化
- nlist:IVF_FLAT索引的聚类数量,通常设置为数据集大小的平方根
- nprobe:搜索时查询的聚类数量,增加可提高准确率但降低速度
- 维度选择:根据嵌入模型选择合适的向量维度,通常为768或1024
十、总结与未来展望
本文详细介绍了在Spring Boot项目中集成Milvus向量数据库的完整流程,包括:
- Milvus客户端配置与初始化
- 向量嵌入生成实现
- Milvus集合创建与索引管理
- 向量数据存储
- 向量相似度搜索
- 错误处理与容错机制
- 性能优化最佳实践
通过这种集成方式,我们成功构建了一个高性能的知识库应用,能够提供基于语义的智能搜索功能。未来,我们可以进一步优化系统性能,例如:
- 实现向量缓存机制,减少重复计算
- 优化Milvus集群配置,提高并发处理能力
- 探索更先进的嵌入模型,提高向量表示质量
- 实现混合检索策略,结合向量搜索和传统搜索的优势
Milvus作为一个强大的向量数据库,为AI应用提供了坚实的数据基础,而Spring Boot的易用性又大大简化了集成过程,两者的结合为构建智能应用提供了理想的技术栈。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐

所有评论(0)