向量数据库正在从"概念热词"变成工程日常。ChromaDB 作为轻量级、Python-first 的向量存储方案,适合本地开发和小规模 RAG(检索增强生成)场景。但真正上手时,很多人会卡在:嵌入怎么选?余弦相似度到底在算什么?collection 查询的参数怎么配?这篇文章把这些问题拆开,配上可以直接跑的代码。
嵌入:文本变成数字的入口
嵌入(embedding)是把一段文本映射成高维浮点向量。维度通常在 384 到 1536 之间,取决于模型。两个向量越"近",语义越相似——这就是向量检索的基础。
ChromaDB 默认使用 all-MiniLM-L6-v2(Sentence Transformers 系列,384 维),无需额外配置即可开箱运行。你也可以换成 OpenAI 的 text-embedding-3-small 或其他模型,只需在创建 collection 时指定 embedding function。
关键认知:嵌入模型的选择直接决定检索质量。同一个句子,不同模型产出的向量不可混用——它们的空间结构完全不同。一旦切换模型,已有数据必须重新嵌入。
余弦相似度:ChromaDB 默认的距离度量
ChromaDB 默认使用余弦相似度(cosine similarity)作为距离度量。公式很简单:
cosine_similarity(A, B) = (A · B) / (||A|| × ||B||)
结果范围 [-1, 1],1 表示方向完全一致,0 表示无关,-1 表示相反。ChromaDB 内部存储时用的是余弦距离(1 - similarity),所以返回的 distance 越小越相似。
你可以在创建 collection 时切换度量方式:
import chromadb
client = chromadb.PersistentClient(path="./chroma_db")
# 默认 cosine,也可以选 l2(欧氏距离)或 ip(内积)
collection = client.create_collection(
name="tech_docs",
metadata={"hnsw:space": "cosine"} # 可选 "l2" 或 "ip"
)
实际选择建议: - cosine:最通用,对向量长度不敏感,适合语义检索。 - l2:如果你关心绝对距离(比如地理位置向量),用欧氏距离。 - ip:内积,适合已经归一化的向量,计算更快。
Collection 查询:从添加到检索的完整流程
下面是一个可以直接跑的完整示例,覆盖添加文档、嵌入、查询三个步骤:
# pip install chromadb sentence-transformers
import chromadb
# 使用内存客户端,跑完即消失;换成 PersistentClient 可持久化
client = chromadb.Client()
collection = client.get_or_create_collection(
name="python_tips",
metadata={"hnsw:space": "cosine"}
)
# 添加文档——ChromaDB 会自动调用默认嵌入模型生成向量
docs = [
"Python 的 list comprehension 比手动 for 循环更快更简洁",
"用 functools.lru_cache 可以给纯函数加缓存,避免重复计算",
"asyncio.gather 能并发运行多个协程,适合 IO 密集场景",
"dataclass 替代手写 __init__,减少样板代码",
"pathlib.Path 比 os.path 更面向对象,路径操作更直观",
]
ids = [f"tip_{i}" for i in range(len(docs))]
collection.add(
documents=docs,
ids=ids
)
# 查询:找和"缓存性能优化"最相关的两条文档
results = collection.query(
query_texts=["如何优化函数的缓存和性能"],
n_results=2
)
print("匹配文档:", results["documents"][0])
print("距离值:", results["distances"][0])
print("ID:", results["ids"][0])
运行后你会看到距离值(越小越相似)和对应的文档内容。query_texts 会被自动嵌入成向量,再和 collection 中已有向量做余弦距离计算。
几个实用参数:
| 参数 | 作用 | 典型值 |
|---|---|---|
n_results |
返回结果数量 | 5-10 |
where |
元数据过滤 | {"category": "backend"} |
where_document |
文档内容过滤 | {"$contains": "async"} |
include |
返回哪些字段 | ["documents", "distances", "metadatas"] |
元数据过滤是 ChromaDB 的一个亮点——它把向量检索和结构化过滤组合在一起,避免"只能靠向量猜"的局限:
# 先添加带元数据的文档
collection.add(
documents=["FastAPI 的依赖注入系统非常灵活"],
ids=["tip_5"],
metadatas=[{"category": "web", "level": "advanced"}]
)
# 查询时同时过滤元数据
results = collection.query(
query_texts=["Web 框架的高级用法"],
n_results=3,
where={"level": "advanced"} # 只返回高级内容
)
RAG 实战:ChromaDB + LLM 的最小闭环
RAG 的核心流程:用户提问 → 从向量库检索相关片段 → 把片段塞进 prompt → LLM 生成回答。ChromaDB 负责中间的检索环节。
下面用 OpenAI API 拼一个最小 RAG:
# pip install openai chromadb
import chromadb
from openai import OpenAI
# 初始化
openai_client = OpenAI() # 确保已设置 OPENAI_API_KEY 环境变量
chroma_client = chromadb.Client()
collection = chroma_client.get_or_create_collection("rag_demo")
# 知识库内容
knowledge = [
"ChromaDB 默认使用 all-MiniLM-L6-v2 嵌入模型,输出 384 维向量",
"ChromaDB 的 query 方法支持 where 和 where_document 两种过滤",
"余弦距离 = 1 - 余弦相似度,值越小表示越相似",
"PersistentClient 把数据存到本地磁盘,Client 只存内存",
]
collection.add(
documents=knowledge,
ids=[f"k_{i}" for i in range(len(knowledge))]
)
def rag_answer(question: str, n_contexts: int = 2) -> str:
# 第一步:检索
retrieved = collection.query(
query_texts=[question],
n_results=n_contexts
)
contexts = "\n".join(retrieved["documents"][0])
# 第二步:构建 prompt
prompt = f"""根据以下参考资料回答问题。如果资料中没有相关信息,明确说明。
参考资料:
{contexts}
问题:{question}
回答:"""
# 第三步:调用 LLM
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.3
)
return response.choices[0].message.content
# 测试
answer = rag_answer("ChromaDB 用什么距离度量?")
print(answer)
这个闭环的关键决策点:
- n_contexts 选多少:2-5 条是常见范围。太少可能遗漏关键信息,太多会稀释 prompt、增加成本和延迟。
- temperature 设多少:RAG 场景建议 0.1-0.3,让模型紧贴检索到的内容,减少"自由发挥"。
- prompt 中的约束:加上"如果资料中没有相关信息,明确说明"能有效减少幻觉。
上手清单与边界提醒
在把 ChromaDB 用到真实项目前,确认这几项:
- 嵌入一致性:同一个 collection 只用一种嵌入模型。切换模型 = 重新嵌入全部数据。
- 数据规模:ChromaDB 适合万级到十万级文档。百万级以上考虑 Milvus 或 Pinecone。
- 持久化选择:开发阶段用
Client()(内存),生产用PersistentClient或接入 Chroma 的 HTTP server 模式。 - 元数据设计:提前规划
where过滤要用到的字段,添加时就带上 metadatas,事后补很麻烦。 - 距离度量:语义检索默认 cosine 就够用,不要无理由切换。
ChromaDB 的优势是轻和快——pip install 即用,Python API 直觉友好,适合从零到可演示的 RAG 原型。它的边界是规模和分布式能力。当你需要多节点部署、百万级向量、或严格的生产级 SLA时,就该评估更重的方案了。但在"先跑起来再说"的阶段,ChromaDB 是最短路径。