02 Chroma_集合(Collection)与文档(Document)初体验

💡 一句话核心概念

Collection 是向量数据库的"表",Document 是你要存的"记忆片段"——一个管结构,一个管内容。搞懂这俩,Chroma 的大门就算踹开了。


🧩 关键实操

1. 理解 Collection:不只是"表"

# 02_collection_basics.py
from chromadb import Client
​
client = Client()
​
# ===== Collection 三大核心参数 =====
# name: 集合名(必填,相当于表名)
# metadata: 集合级别的元数据(比如 hnsw 索引参数)
# embedding_function: 指定嵌入模型(下一章细讲,先不管)# 创建一个"知识库"风格的集合
kb = client.create_collection(
    name="tech_kb",  # 技术知识库
    metadata={
        "description": "存放技术文档的向量知识库",
        "hnsw:space": "cosine",  # 指定距离算法,默认是 l2(欧几里得)
        "created_by": "chroma-tutorial",
    },
)
​
# 看看已经有哪些集合(刚创建的都会列出来)
print("📋 现有集合:", client.list_collections())
uv run python 02_collection_basics.py

2. 添加文档:向量的前世今生

# 02_add_docs.py
from chromadb import Client
​
client = Client()
kb = client.get_or_create_collection(name="tech_kb")
# ↑ get_or_create 是懒人福音:有就复用,没有就创建,不会像 create 那样重复创建报错# 添加文档的四种姿势
kb.add(
    documents=[
        "Python 的 GIL 是全局解释器锁,它让多线程变成伪并行。",
        "uv 是一个用 Rust 写的 Python 包管理器,比 pip 快 100 倍。",
        "向量数据库通过 Embedding 模型把文本变成高维向量,实现语义搜索。",
    ],
    metadatas=[
        {"topic": "Python", "difficulty": "进阶", "source": "官方文档"},
        {"topic": "工具链", "difficulty": "入门", "source": "astral.sh"},
        {"topic": "向量数据库", "difficulty": "中级", "source": "chroma docs"},
    ],
    ids=["py_gil", "uv_intro", "vectordb_intro"],
)
​
print(f"✅ 添加了 {kb.count()} 条文档到集合")

ids 不传会怎样? 直接抛异常。Chroma 在这一点上很固执——每条文档必须有唯一 ID,方便后续更新/删除。这跟 MongoDB 的 _id 是一个道理。

3. 查询:语义搜索的"啊哈时刻"

# 02_query_docs.py
from chromadb import Client
​
client = Client()
kb = client.get_or_create_collection(name="tech_kb")
​
# 用中文搜
results = kb.query(
    query_texts=["怎么管理 Python 依赖包比较好?"],
    n_results=2,
    # include 控制返回字段,不指定默认返回所有
    # include=["documents", "metadatas", "distances"],
)
​
print("🔍 查询结果:")
for i, (doc_id, doc, meta, dist) in enumerate(zip(
    results["ids"][0],
    results["documents"][0],
    results["metadatas"][0],
    results["distances"][0],
)):
    print(f"  #{i+1} | ID: {doc_id} | 距离: {dist:.4f}")
    print(f"      文档: {doc}")
    print(f"      元数据: {meta}")
    print()

运行后你会发现——即使查询词和原文没一个完全匹配的词,Chroma 也能把"uv 是包管理器"这条排到第一位。这就是向量搜索的魔法:搜的是"意思",不是"字面"。


🚧 避坑指南

现象解法
重复 create_collectionValueError: Collection already existsget_or_create_collection() 代替,或者 create_collection(name, get_or_create=True)
embedding 维度不一致添加文档时报 dimension mismatch同一集合必须用同一个 embedding 函数。要么全部用默认(all-MiniLM-L6-v2,384维),要么自定义但保持维度一致。第 3 章会深入讲
每次重启 Client() 数据没了list_collections() 返回空Client() 是内存模式,用 PersistentClient("./chroma_data") 代替(第 6 章详讲)

🎤 Chroma 面试题与通关答案

Q1:Chroma 的 Collection 和传统数据库的 Table 有什么区别?

考点拆解: 向量数据库与关系型数据库的核心设计差异。

通关答案:

维度传统数据库 TableChroma Collection
主查询方式精确匹配(WHERE name = 'xxx'语义相似度(ANN 近似最近邻)
索引核心B-Tree / HashHNSW 图索引
"列"的概念固定 Schema,强类型弱 Schema:documents + metadatas(JSON),向量自动生成
写入成本O(log n)O(log n) + Embedding 计算(这是大头!)

底层原理: Collection 内部维护了三套存储:

  1. 向量索引(HNSW 图):负责 ANN 搜索,是 Chroma 的核心竞争力
  2. 元数据存储(sqlite3):负责 WHERE 过滤和精确查询
  3. 文档存储(sqlite3):原始文本,查询结束后回表拼接

最佳实践: Collection 不适合存高频更新的数据(每次更新要重建向量+更新索引),更适合"写多读多但更新少"的知识库场景。

一句话总结: Table 是 Excel 表格,Collection 是"语义搜索引擎的索引库"——前者精确定位,后者理解含义。


Q2:query() 返回的 distances 字段代表什么?为什么越小越好?

考点拆解: 向量相似度度量,面试官最爱的算法基础题。

通关答案:

distances 是查询向量与结果向量之间的距离值,距离越小 = 语义越接近。但具体含义取决于集合的 hnsw:space 设置:

距离算法范围最优值通俗解释
l2(欧几里得,默认)[0, ∞)0空间中两点的直线距离
cosine(余弦)[0, 2]0只关心方向,不关心长度(归一化后等同于 l2)
ip(内积/点积)(-∞, ∞)越大越好向量投影,-ip 转成距离用

避坑点: Chroma 默认是 l2,但 NLP 领域大家习惯用 cosine。如果你没在 metadata={"hnsw:space": "cosine"} 指定,出来的 distances 可能大到离谱(比如 50+),因为 l2 没做归一化。建议一律显式指定 cosine,跟行业对齐。

一句话总结: 距离是你和正确答案之间的"语义差距"——l2 像直线距离,cosine 像夹角,默认用 cosine 就对了。


Q3:get_or_create_collection()create_collection() 有什么区别?什么时候用哪个?

考点拆解: API 设计哲学,幂等性(Idempotency)在数据库操作中的重要性。

通关答案:

  • create_collection(name) :严格创建,集合已存在直接抛 ValueError。适合初始化脚本,语义是"我确定这个集合不该已存在"。
  • get_or_create_collection(name) :幂等操作,集合存在就返回引用,不存在就创建。适合业务代码,语义是"给我一个能用的集合"。

源码视角: get_or_create 内部先调 get_collection,捕获 ValueError 后 fallback 到 create_collection。这个 try-except 设计就是懒加载模式的数据库版

实战最佳实践:

# ❌ 不好的写法:脚本跑两次就炸
collection = client.create_collection("my_collection")
​
# ✅ 生产代码的标准写法
collection = client.get_or_create_collection(
    name="my_collection",
    metadata={"hnsw:space": "cosine"},  # ⚠️ 注意:metadata 只在创建时生效!
)

⚠️ 大坑警告: get_or_create 传入的 metadata 只在"create 分支"生效!如果集合已存在,传的 metadata 会被静默忽略,不会更新已有集合的配置。要改 metadata 只能用 collection.modify()

一句话总结: create 是"破门而入"(已有人在就炸),get_or_create 是"敲门进"(有人就进,没人就开灯),业务代码永远用后者。