get_collection 与 get_or_create_collection

🎉摘要:详解 ChromaDB 中 get_collection 和 get_or_create_collection 的差异、使用场景与嵌入函数校验机制。结合源码分析 ValueError 异常原因,总结 DefaultEmbeddingFunction 与自定义 EF 混用的常见陷阱及最佳实践。

两者均是用来获取集合,但是又存在一些差异。

get_collection

用于获取已经存在的集合。如果集合不存在,直接抛异常。该方法适合你确定集合一定已经提前建好的场景。

方法签名如下:

col = client.get_collection(
    name: str,  # 集合名称
    embedding_function = None  # 嵌入函数
)

注意:

(1)1.5.9 会把 embedding function 的配置持久存入集合元信息。get_collection 不传 embedding_function,会自动加载持久化的那个,不会抛异常。如果你手动传了另一个 embedding_function,只是当前客户端会话覆盖持久化的 ef,不会报错。但不推荐这么做(向量维度不匹配会在 add/query 时报错)。

(2)构建期参数 space、ef_construction、max_neighbors 不可以修改,一旦创建后,永久固定,想改只能克隆 / 重建集合。

(3)调用 client.get_collection(name="xxx"),服务端会返回该集合持久保存的 configuration(含hnsw)、metadata、embedding 配置,不需要在 get_collection 里再次填写 hnsw 参数。  

示例:在 create_collection() 和 get_collection() 时使用不同的 embedding_function,并不会报错。如果在调用 add() 时 embedding_function 计算出的向量维度不一致会报错,如 768 和 1024。

import chromadb
# 默认嵌入函数
from chromadb.api import DefaultEmbeddingFunction
# 导入自定义的函数
from common.custom_embedding import AliQwenEmbeddingFunction

client = chromadb.EphemeralClient()
# 创建时使用自定义阿里云嵌入函数
col1 = client.create_collection(name="notes", embedding_function=AliQwenEmbeddingFunction())
col1.add(ids=["q1", "q2"], documents=[
    "订单付款后多久发货?一般 48 小时内出库",
    "如何申请退款?在订单详情页点击申请退款",
], metadatas=[
    {"category": "物流"},
    {"category": "售后"},
])

# 获取集合时使用其他嵌入函数
col2 = client.get_collection(name="notes", embedding_function=DefaultEmbeddingFunction())
col2.add(ids=["q3", "q4"], documents=[
    "发票怎么开?在个人中心-发票管理里申请电子发票",
    "快递丢了怎么办?联系客服补发或全额退款",
], metadatas=[
    {"category": "财务"},
    {"category": "售后"},
])

执行上面代码,会出现如下错误:

chromadb.errors.InvalidArgumentError: Collection expecting embedding with dimension of 1024, got 384

这是因为,DefaultEmbeddingFunction() 底层使用的是 all-MiniLM-L6-v2 模型,默认是 384 维。而自定义的 AliQwenEmbeddingFunction() 底层采用 qwen3.7-text-embedding-flash 模型,是 1024 维。两者维度不一致,所以报错了。即使维度一样也不建议这么做,不同模型计算的向量是存在差异的,会导致后续检索不准确。

如果我们先设置 DefaultEmbeddingFunction(),再在 get_collection() 设置 AliQwenEmbeddingFunction(),例如:

import chromadb
# 默认嵌入函数
from chromadb.api import DefaultEmbeddingFunction
# 导入自定义的函数
from common.custom_embedding import AliQwenEmbeddingFunction

client = chromadb.EphemeralClient()
# 创建时使用自定义阿里云嵌入函数
client.create_collection(name="notes", embedding_function=DefaultEmbeddingFunction())
# 获取集合时使用其他嵌入函数
client.get_collection(name="notes", embedding_function=AliQwenEmbeddingFunction())

运行代码,出错了:ValueError: An embedding function already exists in the collection configuration, and a new one is provided. If this is intentional, please embed documents separately. Embedding function conflict: new: NotImplemented vs persisted: default

为什么会报错呢?需要分析源码 validate_embedding_function_conflict_on_get() 函数:

def validate_embedding_function_conflict_on_get(
    embedding_function: Optional[EmbeddingFunction],  # type: ignore
    persisted_ef_config: Optional[Dict[str, Any]],
) -> None:
    """
    校验传入的 embedding_function 参数和集合持久化保存的嵌入函数配置 之间不存在冲突。
    调用 get_collection / get_or_create_collection 获取已有集合时执行。

    规则:
    1. 如果集合元数据里已经持久保存了EF配置(persisted_ef_config不为空)
    2. 并且用户在get接口显式传入了新的 embedding_function 实例
    3. 并且两个EF的name不相等,则判定冲突,抛出ValueError
    """
    # 集合持久化存在EF配置,同时用户get时显式传入了 embedding_function
    if persisted_ef_config is not None and embedding_function is not None:
        if (
            # 当前传入EF的name不是 default
            embedding_function.name() != "default"
            # 持久化配置中存在name字段
            and persisted_ef_config.get("name") is not None
            # 持久化EF名称 和 当前传入EF名称不一致
            and persisted_ef_config.get("name") != embedding_function.name()
        ):
            raise ValueError(
                f"An embedding function already exists in the collection configuration, and a new one is provided. If this is intentional, please embed documents separately. Embedding function conflict: new: {embedding_function.name()} vs persisted: {persisted_ef_config.get('name')}"
            )
    # 校验通过,无冲突直接返回
    return None

代码报错是因为,create_collection(..,embedding_function=DefaultEmbeddingFunction()) 时 persisted_ef_config.name 为 default。自定义 AliQwenEmbeddingFunction 的 persisted_ef_config.name 为 NotImplemented:

  • persisted_ef_config:{"name": "default"} → persisted_ef_config.get("name") = "default"(不为 None)

  • get 传入 ef:AliQwenEmbeddingFunction → embedding_function.name() = "NotImplemented"

判断内部三条件:

  1. "NotImplemented" != "default" → True

  2. persisted name 不为 None → True

  3. "default" != "NotImplemented" → True

全部满足 → raise 报错,也就是你看到的异常。

client.get_collection(name="notes", embedding_function=DefaultEmbeddingFunction()) 不报错,因为第一个条件就不满足,embedding_function.name() != "default",if 语句跳过执行。

结论: 当传入的 embedding_function 是 DefaultEmbeddingFunction(.name() 返回 "default"),内层冲突校验直接跳过,不再做名字比对。  

get_or_create_collection

业务开发推荐,幂等。

如果集合已经存在,直接返回。如果集合不存在,就新建。幂等接口,重复执行不会报错。适合启动脚本、业务代码,不知道集合是否存在,一键保证集合可用。

方法签名如下:

col = client.get_or_create_collection(
    name: str,  # 集合名称
    metadata: dict | None = None,  # 元数据
    configuration: dict | None = None,  # 配置信息
    embedding_function = None  # 嵌入函数
)

注意,如果集合已经存在,后续传入的 configuration、metadata 不会覆盖原有集合配置。只有集合不存在的时候,才会使用你传入的 configuration / metadata 创建新集合。

import chromadb

client = chromadb.EphemeralClient()
try:
    # 取不到就报错
    col = client.get_collection("notes")

    # 取不到就建,取到就返回(最常用)
    col = client.get_or_create_collection("notes", embedding_function=None)
except Exception as e:
    print("集合不存在时 get_collection 就是会报错:", type(e).__name__, e)

# 输出结果:
# 集合不存在时 get_collection 就是会报错: NotFoundError Collection [notes] does not exist

关于嵌入函数的一个大坑:

1.1.13 之前的版本,嵌入函数不会存在服务端。get_collection() 拿到的集合会用默认嵌入函数,跟你建库时用的不是同一个,然后你 query 的时候就会得到一堆莫名其妙的结果 —— 因为查询向量和库里的向量来自两个模型,根本不在一个空间里。

老版本的写法必须显式传。

新版本(1.1.13+)会把嵌入函数配置存进 configuration,客户端自动解析,不用再传了。但为了保险,我还是习惯显式传一遍,尤其是自定义嵌入函数的时候。

import chromadb
from common.custom_embedding import AliQwenEmbeddingFunction

client = chromadb.EphemeralClient()
col = client.get_or_create_collection("notes", embedding_function=AliQwenEmbeddingFunction(),
                                      metadata={"embedding_function_name":"AliQwenEmbeddingFunction"})
print(col.configuration)
# {'hnsw': {...}, 'spann': None, 'embedding_function': None}
print(col._embedding_function)
# <common.custom_embedding.AliQwenEmbeddingFunction object at 0x0000020DD6202BA0>
print(col.metadata)
# {'embedding_function_name': 'AliQwenEmbeddingFunction'}

注意,col.configuration 打印的 embedding_function 总是为 None,实际 embedding_function 是存放在私有变量 _embedding_function 上的。建议单独将当前使用的 embedding_function 信息存放在 metadata 上,即使私有字段 _embedding_function 不可用,也能正常查看集合的 embedding_function 信息。

说说我的看法
全部评论()
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号