案例:
不是"创建集合的那一瞬间",而是"第一次使用时"
java
// ========== 应用启动时(MilvusConfig执行) ==========
// 步骤1: 创建milvusClient
@Bean
public MilvusServiceClient milvusClient() {
return new MilvusServiceClient(...); // ✓ 只是连接数据库
}
// 步骤2: 创建embeddingModel
@Bean
public EmbeddingModel embeddingModel() {
return new OpenAiEmbeddingModel(...); // ✓ 只是创建工具对象
}
// 步骤3: 创建vectorStore
@Bean
public VectorStore zyVectorStore(
MilvusServiceClient milvusClient,
EmbeddingModel embeddingModel) {
MilvusVectorStore vectorStore = MilvusVectorStore.builder(
milvusClient,
embeddingModel)
.collectionName("zhi_yan")
.initializeSchema(true) // ← 注意!这里只是设置参数
.build();
// ⚠️ 到这里为止,集合还没有创建!
// 只是创建了vectorStore对象,保存了配置
return vectorStore;
}
// ========== 启动完成,集合仍然不存在 ==========
集合真正创建的时机
第一次调用vectorStore.add()时
java
// ========== 用户上传文件后 ==========
DocumentServiceImpl.importMarkdownContent() {
// 1. 切片
List<Document> chunks = textSplitter.apply(List.of(doc));
// 2. 调用vectorStore.add() ← 这里才真正创建集合!
vectorStore.add(chunks);
}
// ========== vectorStore.add()内部执行流程 ==========
class MilvusVectorStore {
public void add(List<Document> documents) {
// 步骤1: 检查集合是否存在
boolean exists = checkCollectionExists("zhi_yan");
if (!exists && initializeSchema) {
// 步骤2: 集合不存在,需要创建
createCollection(); // ← 这里才真正创建!
}
// 步骤3: 插入数据
insertDocuments(documents);
}
private void createCollection() {
// ========== 这里才使用embeddingModel! ==========
// 1. 生成一个测试向量,获取维度
log.info("正在获取向量维度...");
List<Double> testVector = embeddingModel.embed("test");
int dimension = testVector.size(); // ← 得到1024
log.info("检测到向量维度: {}", dimension);
// 2. 创建Schema
List<FieldType> fields = new ArrayList<>();
// 主键字段
fields.add(FieldType.newBuilder()
.withName("id")
.withDataType(DataType.VarChar)
.withMaxLength(36)
.withPrimaryKey(true)
.build());
// 向量字段 ← 使用检测到的维度
fields.add(FieldType.newBuilder()
.withName("embedding")
.withDataType(DataType.FloatVector)
.withDimension(dimension) // ← 1024
.build());
// 其他字段...
// 3. 创建集合
CollectionSchemaParam schema = CollectionSchemaParam.newBuilder()
.withFieldTypes(fields)
.build();
CreateCollectionParam param = CreateCollectionParam.newBuilder()
.withCollectionName("zhi_yan")
.withSchema(schema)
.build();
milvusClient.createCollection(param);
log.info("集合创建成功! embedding字段维度: {}", dimension);
}
}
完整时间线
时间点1: 应用启动
├─ 创建milvusClient ✓
├─ 创建embeddingModel ✓ (配置为1024维)
├─ 创建vectorStore ✓ (保存配置)
└─ 集合状态: ❌ 不存在
时间点2: 应用启动完成
└─ 集合状态: ❌ 仍然不存在
时间点3: 用户上传第一个文件
├─ DocumentController接收文件
├─ DocumentServiceImpl.importMarkdownContent()
├─ textSplitter.apply() 切片
└─ vectorStore.add(chunks) ← 触发!
↓
时间点4: vectorStore.add()内部
├─ 检查集合是否存在 → 不存在
├─ 调用embeddingModel.embed("test") → 得到1024维向量
├─ 创建集合,embedding字段设为1024维 ✓
└─ 集合状态: ✓ 创建成功,1024维
时间点5: 后续上传文件
├─ vectorStore.add(chunks)
├─ 检查集合是否存在 → 已存在
├─ 跳过创建步骤
└─ 直接插入数据
验证:查看Spring AI源码逻辑
java
// Spring AI的MilvusVectorStore实际实现(简化版)
public class MilvusVectorStore implements VectorStore {
private final MilvusServiceClient milvusClient;
private final EmbeddingModel embeddingModel;
private final String collectionName;
private final boolean initializeSchema;
@Override
public void add(List<Document> documents) {
// 1. 确保集合存在
if (initializeSchema) {
createCollectionIfNotExists(); // ← 关键方法
}
// 2. 向量化并插入
for (Document doc : documents) {
List<Double> vector = embeddingModel.embed(doc.getContent());
insert(doc.getId(), vector, doc.getContent(), doc.getMetadata());
}
}
private void createCollectionIfNotExists() {
// 检查集合是否存在
R<Boolean> response = milvusClient.hasCollection(
HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
.build()
);
if (!response.getData()) {
// 集合不存在,创建它
log.info("集合 {} 不存在,正在创建...", collectionName);
// ========== 这里使用embeddingModel获取维度 ==========
List<Double> dimensionVector = embeddingModel.embed("dimension test");
int dimension = dimensionVector.size();
log.info("从EmbeddingModel检测到向量维度: {}", dimension);
// 创建Schema并创建集合
createCollectionWithDimension(dimension);
} else {
log.info("集合 {} 已存在,跳过创建", collectionName);
}
}
}
为什么这样设计?
1. 延迟初始化(Lazy Initialization)
好处:
✓ 启动更快 - 不需要在启动时创建集合
✓ 灵活性 - 可以先启动应用,后配置Milvus
✓ 自动适配 - 自动从embeddingModel获取维度,不需要手动配置
2. 自动维度检测
java
// 你不需要这样写:
MilvusVectorStore.builder(...)
.dimension(1024) // ❌ 不需要手动指定
.build();
// 而是自动检测:
MilvusVectorStore.builder(...)
.initializeSchema(true) // ✓ 自动从embeddingModel获取
.build();
// 内部会:
int dimension = embeddingModel.embed("test").size(); // 自动得到1024
实际操作演示
场景1: 首次启动,集合不存在
bash
# 1. 启动应用
mvn spring-boot:run
# 控制台输出:
# [INFO] 正在连接Milvus服务器: 10.0.0.15:19530
# [INFO] Milvus客户端连接成功
# [INFO] 正在创建OpenAI EmbeddingModel...
# [INFO] OpenAI EmbeddingModel创建成功
# [INFO] 正在创建Milvus VectorStore,集合名称: zhi_yan
# [INFO] Milvus VectorStore创建成功
# [INFO] 应用启动完成!
# ⚠️ 注意:这里没有"创建集合"的日志!
# 2. 上传第一个文件
curl -X POST http://localhost:8084/api/documents/import \
-F "file=@test.md"
# 控制台输出:
# [INFO] 正在导入Markdown文件: test.md
# [INFO] 开始导入Markdown内容,文件名: test.md
# [INFO] 文档已分割成 3 个块
# [INFO] 集合 zhi_yan 不存在,正在创建... ← 这里才创建!
# [INFO] 正在获取向量维度...
# [INFO] 从EmbeddingModel检测到向量维度: 1024
# [INFO] 创建集合Schema: embedding字段维度=1024
# [INFO] 集合创建成功!
# [INFO] 成功导入 3 个文档块到Milvus
场景2: 集合已存在
bash
# 上传第二个文件
curl -X POST http://localhost:8084/api/documents/import \
-F "file=@test2.md"
# 控制台输出:
# [INFO] 正在导入Markdown文件: test2.md
# [INFO] 开始导入Markdown内容,文件名: test2.md
# [INFO] 文档已分割成 4 个块
# [INFO] 集合 zhi_yan 已存在,跳过创建 ← 跳过创建步骤
# [INFO] 成功导入 4 个文档块到Milvus
总结
集合什么时候创建?
不是启动时,而是第一次调用vectorStore.add()时!
embeddingModel什么时候参与?
启动时: 只是创建embeddingModel对象,不调用
↓
第一次add()时:
├─ 调用embeddingModel.embed("test")
├─ 获取向量维度(1024)
└─ 创建集合,设置embedding字段为1024维
为什么是1024?
因为你配置了:
.dimensions(1024)
↓
embeddingModel.embed("test") 返回1024维向量
↓
vectorStore检测到1024维
↓
创建集合时设置embedding字段为1024维
关键代码
java
// initializeSchema(true) 的作用:
// 在第一次add()时,自动创建集合,并从embeddingModel获取维度
MilvusVectorStore.builder(...)
.initializeSchema(true) // ← 这个参数很关键!
.build();
集合是在第一次使用时才创建的,不是启动时! 🎯