MilvusVectorStoreV2 是基于 MilvusClientV2 的对 VectorStore 的实现,其提供了基于 Milvus 的向量存储实现,支持密集向量、稀疏向量和多向量混合检索(多路召回+RFF),适用于大语言模型应用中的语义搜索、知识检索等场景。主要包含向量存储、集合管理、RAG检索增强等核心功能。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-vector-store</artifactId>
</dependency>MilvusVectorStoreV2 是基于 MilvusClientV2 实现的向量存储组件,实现了 Spring AI 的 VectorStore 接口,支持文档的增删改查和多种检索方式。
主要功能:
- 文档向量化存储
- 文档删除(支持 ID 和过滤条件删除)
- 密集向量的相似度搜索(
similaritySearch方法) - 全文检索(
fullTextSearch方法) - 多路召回检索(
multiChannelRecallSearch方法)
核心字段:
dense_collectionName:密集向量集合名称sparse_collectionName:稀疏向量集合名称multi_collectionName:多向量集合名称vectorFieldName:密集向量字段名称sparseVectorFieldName:稀疏向量字段名称partitionName:分区名称
CollectionManager 负责 Milvus 集合和索引的创建与管理,支持三种类型的集合:
集合类型:
- 向量集合(
VECTOR):存储密集向量,用于语义相似度检索 - 文本集合(
TEXT):支持中文分词和 BM25 检索,适用于全文搜索 - 多向量集合(
MULTI_VECTOR):同时支持密集向量和稀疏向量检索
索引配置:
- 向量索引:使用 HNSW 算法,余弦相似度度量
- 文本索引:使用 AUTOINDEX,BM25 相似度度量
- 多向量索引:同时配置密集向量和稀疏向量索引
RagAnswerMilvusAdvisor 实现了 Spring AI 的 BaseAdvisor 接口,用于为聊天客户端提供 RAG(检索增强生成)能力。
工作流程:
before阶段:根据用户问题从 Milvus 检索相关文档,将文档内容作为上下文附加到用户问题中after阶段:将检索到的文档信息添加到响应的元数据中,便于溯源
MilvusConfig 负责 Milvus 客户端和向量存储的配置与初始化,主要包括:
- Milvus 客户端连接池配置
- MilvusVectorStoreV2 Bean 定义
- 注入 OpenAI 嵌入模型和批处理策略
# Milvus 配置milvus:
url: http://host:port # Milvus 服务地址
token: root:Milvus # 认证 token
config:
maxIdlePerKey: 10 # 每个 key 的最大空闲客户端数
maxTotalPerKey: 20 # 每个 key 的最大客户端总数
maxTotal: 100 # 所有 key 的最大客户端总数
maxBlockWaitDuration: 5 # 获取客户端的最大等待时间(秒)
minEvictableIdleDuration: 10 # 最小可驱逐空闲时间(秒)MilvusVectorStoreV2 支持以下配置参数:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| dense_collectionName | String | text_dense_collection | 密集向量集合名称 |
| sparse_collectionName | String | text_sparse_collection | 稀疏向量集合名称 |
| multi_collectionName | String | multi_collection_01 | 多向量集合名称 |
| vectorFieldName | String | text_dense | 密集向量字段名 |
| sparseVectorFieldName | String | text_sparse | 稀疏向量字段名 |
| partitionName | String | _default | 分区名称 |
@Autowired
private MilvusVectorStoreV2 milvusVectorStoreV2; // 解析文档
TextReader textReader = new TextReader(txtResource);
textReader.getCustomMetadata().put("title", "test.txt");
List<Document> documentList = textReader.get();
// 分块
TokenTextSplitter splitter = new TokenTextSplitter(
50, // defaultChunkSize 目标Token数,即每个块的Token数
20, // minChunkSizeChars 最小字符数,当某块因分隔符导致不足chunkSize时,会至少保留minChunkSize的内容
1, // minChunkLengthToEmbed 最小有效分块长度,避免过滤短句
100, // maxNumChunks 最大分块数
false // keepSeparator 是否保留分隔符(如换行符)在文本块中,中文无需保留
);
List<Document> documents = splitter.split(documentList);
String ragTag = "milvus";
documents.forEach(document -> document.getMetadata().put("knowledge", ragTag));
log.info("分块后文档数: {}", documents.size());
milvusVectorStoreV2.setDense_collectionName("text_dense_collection");
// 添加文档到向量数据库
milvusVectorStoreV2.accept(documents);
log.info("insert doc");SearchRequest request = SearchRequest.builder()
.query("馨可宁单针多少元")
.topK(5)
.filterExpression(filterExpression) // 过滤条件
.build();
milvusVectorStoreV2.setDense_collectionName("text_dense_collection");
milvusVectorStoreV2.similaritySearch(request);SearchRequest request = SearchRequest.builder()
.query("2025年上半年工业生产总值是多少")
.topK(5)
.filterExpression(filterExpression) // 过滤条件
.build();
milvusVectorStoreV2.setSparse_collectionName("text_sparse_collection");
milvusVectorStoreV2.fullTextSearch(request);SearchRequest request = SearchRequest.builder()
.query("《民法典》中第352页第一条讲述了什么内容")
.topK(2)
.filterExpression(filterExpression) // 过滤条件
.build();
String collectionName = "multi_collection_01";
milvusVectorStoreV2.setMulti_collectionName(collectionName);OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl("https://api.chatanywhere.tech")
.apiKey("sk-PTovWzp1oSk8fbLjn4evx0ii1M0SM23xGEY4oLvlyADHGvPj")
.completionsPath("v1/chat/completions")
.embeddingsPath("v1/embeddings")
.build();
chatModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o")
.build())
.build();
chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(
MessageWindowChatMemory.builder()
.maxMessages(100)
.build()
).build())
.build();
// 用户输入
String message = "2025 年免疫类流感疫苗共有几款疫苗中标 ?";
// 指定文档搜索
SearchRequest request = SearchRequest.builder()
.query(message)
.topK(2)
.build();
String collectionName = "multi_collection_01";
// 执行混合检索
milvusVectorStoreV2.setMulti_collectionName(collectionName);
List<Document> documents = milvusVectorStoreV2.multiChannelRecallSearch(
request,
message // 混合检索中的全文搜索会使用分析器将输入文本标记为可搜索的单个术语,即关键词
);
// 构造上下文
String documentCollectors = documents.stream().map(Document::getText).collect(Collectors.joining());
Message ragMessage = new SystemPromptTemplate(Constants.RAG_PROMPT).createMessage(Map.of("documents", documentCollectors));
List<Message> messages = new ArrayList<>();
messages.add(new UserMessage(message));
messages.add(ragMessage);
// 调用 LLM
String endContent = chatClient.prompt(new Prompt(
messages,
OpenAiChatOptions.builder()
.model("gpt-4o")
.build()
)).call().content();
log.info("RAG answer: {}", endContent);通过 CollectionManager 创建 Milvus 集合和索引:
@Autowired
private CollectionManager collectionManager;
// 创建密集向量 collection
String collectionType = Constants.COLLECTION_TYPE.VECTOR.getCode();
String collectionName = "text_dense_collection";
collectionManager.createCollection(collectionType, collectionName);
// 创建稀疏向量 collection
String collectionType = Constants.COLLECTION_TYPE.TEXT.getCode();
String collectionName = "text_sparse_collection";
collectionManager.createCollection(collectionType, collectionName);
// 创建多向量 collection
String collectionType = Constants.COLLECTION_TYPE.MULTI_VECTOR.getCode();
String collectionName = "multi_collection_01";
collectionManager.createCollection(collectionType, collectionName);-
连接管理:项目使用 Milvus 客户端连接池,确保合理配置连接参数避免资源耗尽
-
索引优化:根据实际数据量和查询需求调整索引参数,如 HNSW 的 M 和 efConstruction 值
-
性能考虑:对于大规模数据,建议使用分区和过滤条件提高检索效率
-
向量维度:当前实现默认使用维度为 1024 的向量,如有需要请调整