上一篇用 FAISS 搭语义搜索引擎,有人问我:有没有不用自己管索引文件的方案?有,ChromaDB 就是为这个场景生的。
先说结论
ChromaDB 是面向开发者的"零配置"向量数据库。pip install 就能用,不需要 Docker,不需要外部服务,自带持久化,支持元数据过滤,几行代码接入 embedding 模型。
一句话定位:向量数据库里的 SQLite。
如果你只是想快速搭一个知识库、文档搜索、RAG 原型,ChromaDB 是门槛最低的选择。等数据量到百万级、需要高并发了,再考虑 Qdrant 或 Milvus。
向量数据库 60 秒科普
上一篇 FAISS 文章讲过原理,这里用图书馆的类比快速过一遍。
你去图书馆找书,有两种方式:
- 索引卡方式(关键词搜索):翻目录卡片,按书名精确匹配。搜"Python 入门"只能找到标题里有这几个字的书
- 问管理员方式(语义搜索):跟管理员说"我想学编程",他理解你的意思,给你推荐相关的书,不管书名叫什么
向量数据库干的就是"管理员"的活。它把每条文档编码成向量(一串数字),搜索时把你的查询也编码成向量,然后计算向量间的距离找最接近的。
ChromaDB 在此基础上多做了一件事:它把这些向量连同原始文本和元数据一起存好,不用你操心存储细节。FAISS 是搜索库,你得自己管存储;ChromaDB 是完整数据库,存储、索引、元数据一站式搞定。
5 分钟快速上手
第一步:安装
ChromaDB 自带一个内置 embedding 模型(all-MiniLM-L6-v2),英文场景开箱即用。但如果你处理中文,建议额外装一个中文模型:
1
| pip install chromadb sentence-transformers
|
sentence-transformers 提供了丰富的中文 embedding 模型,下面的代码我会用它接入 BAAI/bge-small-zh-v1.5(约 100MB,首次自动下载)。
第二步:创建客户端和集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import chromadb
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction
# 用中文 embedding 模型(默认模型偏英文,中文效果差)
ef = SentenceTransformerEmbeddingFunction(model_name="BAAI/bge-small-zh-v1.5")
# 创建持久化客户端,数据会保存到本地目录
client = chromadb.PersistentClient(path="./my_knowledge_base")
# 创建集合时指定 embedding 函数
collection = client.get_or_create_collection(
name="my_docs",
embedding_function=ef
)
|
PersistentClient 和 FAISS 的最大区别:数据自动落盘。程序关了再开,数据还在。不用手动 write_index / read_index,不用维护"索引 ID → 原始文本"的映射表。
底层存储用的是 SQLite 数据库(chroma.sqlite3),向量索引用二进制文件存储,全部放在你指定的 path 目录下。这意味着你甚至可以用 sqlite3 直接查看元数据,备份只需要拷贝整个目录。
如果你只想做内存测试,用 chromadb.Client() 即可,退出即丢。
第三步:添加文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 添加文档,ChromaDB 自动帮你做 embedding
collection.add(
documents=[
"Python 是一门解释型编程语言,语法简洁易读",
"Java 是面向对象的编程语言,跨平台运行",
"今天天气真不错,适合出门散步",
"如何申请退款?请在订单页面点击退款按钮",
"我想把钱退回来,应该怎么操作",
],
metadatas=[
{"category": "编程", "source": "wiki"},
{"category": "编程", "source": "wiki"},
{"category": "生活", "source": "diary"},
{"category": "客服", "source": "faq"},
{"category": "客服", "source": "faq"},
],
ids=["doc1", "doc2", "doc3", "doc4", "doc5"]
)
|
注意:这里指定了中文 embedding 模型,add() 的时候 ChromaDB 自动把文本转成向量存进去。ChromaDB 内置的默认模型(all-MiniLM-L6-v2)偏英文,对中文语义理解较差,所以处理中文数据时强烈建议换成中文模型。
第四步:查询
1
2
3
4
5
6
7
8
| # 语义搜索
results = collection.query(
query_texts=["编程语言有哪些"],
n_results=3
)
for doc, dist in zip(results["documents"][0], results["distances"][0]):
print(f"[距离: {dist:.4f}] {doc}")
|
输出:
1
2
3
| [距离: 0.3133] Java 是面向对象的编程语言,跨平台运行
[距离: 0.3375] Python 是一门解释型编程语言,语法简洁易读
[距离: 0.7588] 今天天气真不错,适合出门散步
|
搜"编程语言有哪些",前两条命中 Python 和 Java,第三条天气明显不相关。距离越小越相似。
补充一点:distances 返回的是向量距离,不是相似度分数。ChromaDB 默认用余弦距离,值域 [0, 2],0 表示完全一致,2 表示完全相反。实际使用中,距离小于 1.0 的结果通常比较相关,大于 1.2 就可以当噪音忽略了。
第五步:元数据过滤
这是 ChromaDB 相比 FAISS 的杀手级功能——先筛选再搜索。
1
2
3
4
5
6
7
8
| # 只在"客服"分类里搜索
results = collection.query(
query_texts=["退款"],
n_results=5,
where={"category": "客服"}
)
print(results["documents"][0])
|
输出:
1
| ['如何申请退款?请在订单页面点击退款按钮', '我想把钱退回来,应该怎么操作']
|
where 条件支持 $eq(等于)、$ne(不等于)、$in(包含)等操作符:
1
2
3
4
5
6
7
8
| # 多值过滤
where={"category": {"$in": ["编程", "客服"]}}
# 复合条件
where={"$and": [
{"source": "faq"},
{"category": "客服"}
]}
|
想象一个场景:你有 10000 篇文档,10 个分类。用户只想搜"技术"分类下的内容。FAISS 需要你自己实现过滤逻辑,ChromaDB 一个 where 参数搞定。
完整实战:搭建个人知识库
把上面的模块组合起来,做一个能增删查的迷你知识库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| import chromadb
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction
import time
class KnowledgeBase:
def __init__(self, db_path="./my_kb"):
self.client = chromadb.PersistentClient(path=db_path)
ef = SentenceTransformerEmbeddingFunction(model_name="BAAI/bge-small-zh-v1.5")
self.collection = self.client.get_or_create_collection(
name="notes", embedding_function=ef
)
def add(self, text, category="未分类", doc_id=None):
"""添加一条笔记"""
if doc_id is None:
doc_id = f"note_{int(time.time()*1000)}"
self.collection.add(
documents=[text],
metadatas=[{"category": category}],
ids=[doc_id]
)
return doc_id
def search(self, query, n=5, category=None):
"""语义搜索,可选按分类过滤"""
where = {"category": category} if category else None
results = self.collection.query(
query_texts=[query],
n_results=n,
where=where
)
hits = []
for doc, dist, meta in zip(
results["documents"][0],
results["distances"][0],
results["metadatas"][0]
):
hits.append({"text": doc, "distance": dist, "meta": meta})
return hits
def count(self):
return self.collection.count()
# ========== 使用示例 ==========
kb = KnowledgeBase()
# 批量添加笔记
notes = [
("Python 列表推导式比 for 循环快,因为底层用 C 实现", "Python"),
("Git rebase 会重写提交历史,谨慎在公共分支使用", "Git"),
("Docker 容器隔离靠 namespace 和 cgroups", "Docker"),
("Python 装饰器本质是接受函数返回函数的高阶函数", "Python"),
("Git stash 暂存当前工作区改动,不提交到分支", "Git"),
]
for text, cat in notes:
kb.add(text, category=cat)
print(f"知识库共 {kb.count()} 条笔记\n")
# 语义搜索:搜"循环优化"
print("=== 搜索: 循环优化 ===")
for hit in kb.search("循环优化", n=3):
print(f" [{hit['meta']['category']}] {hit['text']}")
# 搜索:只在 Git 分类下搜
print("\n=== 搜索: 分支管理 (仅 Git) ===")
for hit in kb.search("分支管理", n=3, category="Git"):
print(f" [{hit['meta']['category']}] {hit['text']}")
|
运行后重启程序,数据还在——因为 PersistentClient 自动落盘了。这就是 ChromaDB 和 FAISS 最大的体验差异:你不用管索引文件,数据自己活着。
补充两个实用技巧:
批量添加比逐条添加快得多。上面的示例为了清晰逐条 add(),实际使用时建议一次传入列表:
1
2
3
4
5
6
| # 批量添加,比循环 add() 快 10 倍以上
collection.add(
documents=["文档1", "文档2", "文档3"],
metadatas=[{"cat": "a"}, {"cat": "b"}, {"cat": "c"}],
ids=["d1", "d2", "d3"]
)
|
换更强的中文模型。如果你的数据量更大、精度要求更高,可以把 bge-small-zh-v1.5 换成 BAAI/bge-large-zh-v1.5(1024 维,约 1.3GB)。注意:同一个集合内不能混用不同模型,换模型必须新建集合。
选型对比:ChromaDB vs FAISS vs Qdrant
| 维度 | ChromaDB | FAISS | Qdrant |
|---|
| 部署方式 | pip install,零配置 | pip install,纯库 | 需 Docker 或独立服务 |
| 内置 embedding | 有(all-MiniLM-L6-v2) | 无,需自己编码 | 无 |
| 持久化 | 自动落盘 | 手动 save/load | 自动 |
| 元数据过滤 | where 条件,开箱即用 | 无,需自己实现 | 有,filter API |
| 适用规模 | <100万 | 任意(看索引类型) | 千万级 |
| 典型场景 | 知识库、RAG 原型 | 高性能向量搜索 | 生产级语义搜索 |
我的选型建议:
- 个人知识库、文档搜索、RAG 原型 → ChromaDB。零配置,内置 embedding,5 分钟跑通
- 纯追求搜索速度、数据量可控 → FAISS。最轻最快,但要自己管存储和元数据
- 生产环境、高并发、需要精确过滤 → Qdrant。功能全性能好,代价是要部署一个服务
一句话总结:个人和原型用 ChromaDB,生产高并发用 Qdrant,纯速度用 FAISS。
结语
ChromaDB 的定位很清晰:向量数据库里的 SQLite。不需要服务端,不需要 Docker,pip install 之后几行代码就能跑,数据自动持久化,接入 embedding 模型就能开箱即用。
它不追求极限性能,不追求分布式高可用,但它把"存储 + 索引 + embedding + 元数据过滤"打包成一个极简的包。对于个人知识库、文档语义搜索、RAG 原型验证这些场景,ChromaDB 是目前最省心的选择。Python 开发者十分钟就能搭出一个能用的知识库,这个门槛在两年前是不可想象的。
上一篇 FAISS 文章解决了"怎么把向量搜得快",这一篇解决了"怎么用得省心"。两者互补:FAISS 适合想完全掌控底层细节的极客,ChromaDB 适合想快速出结果的开发者。如果你刚接触向量检索,建议从 ChromaDB 开始——先跑通流程,理解语义搜索的价值,再根据实际需求决定是否需要换到 FAISS 或 Qdrant。
试试用自己的文档笔记跑一遍上面的代码,把日常积累的技术笔记、读书摘录、工作文档丢进去,你会发现语义搜索比关键词搜索好用太多。搜"性能优化"能找到"降低延迟"的笔记,搜"部署"能找到"Docker 配置"的记录——这在传统的搜索方式下几乎不可能。
关注 varkm,一起学习,一起成长