02 Chroma_集合(Collection)与文档(Document)初体验
💡 一句话核心概念
Collection 是向量数据库的"表",Document 是你要存的"记忆片段"------一个管结构,一个管内容。搞懂这俩,Chroma 的大门就算踹开了。
🧩 关键实操
1. 理解 Collection:不只是"表"
python
# 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())
arduino
uv run python 02_collection_basics.py
2. 添加文档:向量的前世今生
ini
# 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. 查询:语义搜索的"啊哈时刻"
python
# 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_collection | ValueError: Collection already exists |
用 get_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 有什么区别?
考点拆解: 向量数据库与关系型数据库的核心设计差异。
通关答案:
| 维度 | 传统数据库 Table | Chroma Collection |
|---|---|---|
| 主查询方式 | 精确匹配(WHERE name = 'xxx') |
语义相似度(ANN 近似最近邻) |
| 索引核心 | B-Tree / Hash | HNSW 图索引 |
| "列"的概念 | 固定 Schema,强类型 | 弱 Schema:documents + metadatas(JSON),向量自动生成 |
| 写入成本 | O(log n) | O(log n) + Embedding 计算(这是大头!) |
底层原理: Collection 内部维护了三套存储:
- 向量索引(HNSW 图):负责 ANN 搜索,是 Chroma 的核心竞争力
- 元数据存储 (sqlite3):负责
WHERE过滤和精确查询 - 文档存储(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 设计就是懒加载模式的数据库版。
实战最佳实践:
ini
# ❌ 不好的写法:脚本跑两次就炸
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是"敲门进"(有人就进,没人就开灯),业务代码永远用后者。