Spring AI Qdrant 配置了 initializeSchema 却不自动建集合:build() 只是 new,建集合在 afterPropertiesSet()

Spring AI Qdrant 配了 initializeSchema 却不自动建集合

背景

使用 Spring AI 2.0.1 的 spring-ai-qdrant-store,按官方示例手动构建向量库:

this.qdrantVectorStore = QdrantVectorStore.builder(qdrantClient, embeddingModel)
        .collectionName(vector.getId().toString())
        .contentFieldName("content")
        .initializeSchema(true)   // 我以为这会帮我建集合
        .build();

initializeSchema(true) 明明配了,但第一次写入时就报:

Collection '<UUID>' doesn't exist!

集合从来没有被自动创建。而网上示例、Spring AI 的自动配置(starter)看起来都是开箱即用、全自动建集合——为什么到我这里就失效?

排查过程

连接没问题(gRPC 能通,否则报的是连接错误而不是集合不存在),问题一定出在初始化时机上。拆开 spring-ai-qdrant-store-2.0.1-sources.jarQdrantVectorStore 源码,三个关键点:

  1. QdrantVectorStore 实现了 InitializingBeanextends AbstractObservationVectorStore implements InitializingBean)。
  2. Builder.build() 只是 new QdrantVectorStore(this)什么都不执行initializeSchema(true) 也只是把这个布尔值存进字段。
  3. 真正建集合的代码全部在 afterPropertiesSet() 里:
@Override
public void afterPropertiesSet() throws Exception {
    if (!this.initializeSchema) {
        return;   // 没开开关,直接跳过
    }
    if (!isCollectionExists()) {   // 先 listCollections 查一下
        var vectorParams = VectorParams.newBuilder()
            .setDistance(Distance.Cosine)
            .setSize(this.embeddingModel.dimensions())
            .build();
        this.qdrantClient.createCollectionAsync(this.collectionName, vectorParams).get();
    }
}

根因

afterPropertiesSet() 是 Spring 的 InitializingBean 生命周期回调:只有对象被注册成 Spring 管理的 Bean、由容器在初始化阶段调用,它才会执行。

我们这里是手动 new 出来的普通对象(构造函数里 build(),对象不属于 Spring 容器),容器永远不会来调 afterPropertiesSet(),于是:

  • initializeSchema(true) 只是立了个"开关";
  • 开关永远没人去拨动;
  • 集合从未创建;
  • 第一次 upsert 打过去,Qdrant 返回 Collection doesn't exist!

"配了自动初始化"和"真正执行初始化"之间,差了一个容器生命周期回调

为什么 starter 自动配置就能用

Spring AI 的自动配置(spring-ai-starter-vector-store-qdrant)会把 store 声明成一个 @Bean

@Bean
public VectorStore vectorStore(QdrantClient client, EmbeddingModel model) { ... }

Bean 是容器管理的,初始化时容器自动调用 afterPropertiesSet() → 集合被创建。这就是"依赖 starter、自动注入就一切正常;手动 new 就完全没有"的原因——不是配置差异,是谁在调用生命周期回调的差异。

解决方案

方案一(推荐):build() 之后手动补调 afterPropertiesSet()

它本身就是幂等的:集合已存在就跳过,不存在才创建。每次构建 store 调一次都安全。

this.qdrantVectorStore = QdrantVectorStore.builder(qdrantClient, embeddingModel)
        .collectionName(vector.getId().toString())
        .contentFieldName("content")
        .initializeSchema(true)
        .build();
try {
    this.qdrantVectorStore.afterPropertiesSet();   // 手动触发建集合
} catch (Exception e) {
    throw new RuntimeException("初始化Qdrant集合失败: " + vector.getId(), e);
}

注意 afterPropertiesSet() 声明了 throws Exception,必须 try/catch。

方案二:不走回调,显式建集合

initializeSchema(false),直接用原始 QdrantClient 自己完成"查集合 → 建集合":

String name = vector.getId().toString();
if (qdrantClient.listCollectionsAsync().get().stream().noneMatch(name::equals)) {
    qdrantClient.createCollectionAsync(name, VectorParams.newBuilder()
            .setDistance(Distance.Cosine)
            .setSize(dim)   // 嵌入模型维度
            .build()).get();
}

效果等同源码内部那段逻辑,只是把控制权拿回自己手里。

方案三:交给容器(不推荐本场景)

把 store 声明成 @Bean,让容器自动调回调。但集合名是 vector.getId() 动态生成的(一条向量一个集合),固定 @Bean 声明并不适配。

总结

  • build() ≠ 初始化:Spring AI 向量库的 initializeSchema 只是开关,真正建集合/建索引的逻辑在 InitializingBean.afterPropertiesSet()
  • 回调只有容器调:手动 new 的对象不是 Spring Bean,生命周期回调永远不会执行。
  • 一句话排查套路:如果某个组件"starter 自动注入能用、手动 new 就失效",先怀疑它的初始化是不是挂在 afterPropertiesSet() / @PostConstruct 这类容器生命周期回调上。

延伸:这不是 Qdrant 独有

RedisVectorStore(1.0.x)是同一个设计:afterPropertiesSet() 里才是 FT.CREATE 建索引。依赖 starter 自动配置索引自动建好,手动 new RedisVectorStore 则完全没有——套路完全一致,解法也一样:手动补调 afterPropertiesSet(),或显式建索引。

posted @ 2026-09-11 10:06  PC2005-cloud  阅读(4)  评论(0)    收藏  举报