ChromaDB + Python:5分钟搭建个人知识库

不想折腾 Docker,只想要 pip install 就能跑的向量数据库?ChromaDB 内置 embedding、自带持久化、支持元数据过滤,5 分钟从零搭建一个能理解语义的个人知识库。本文包含完整可运行代码与 FAISS/Qdrant 选型对比。

上一篇用 FAISS 搭语义搜索引擎,有人问我:有没有不用自己管索引文件的方案?有,ChromaDB 就是为这个场景生的。

先说结论

ChromaDB 是面向开发者的"零配置"向量数据库。pip install 就能用,不需要 Docker,不需要外部服务,自带持久化,支持元数据过滤,几行代码接入 embedding 模型。

一句话定位:向量数据库里的 SQLite

如果你只是想快速搭一个知识库、文档搜索、RAG 原型,ChromaDB 是门槛最低的选择。等数据量到百万级、需要高并发了,再考虑 Qdrant 或 Milvus。

向量数据库 60 秒科普

上一篇 FAISS 文章讲过原理,这里用图书馆的类比快速过一遍。

你去图书馆找书,有两种方式:

  • 索引卡方式(关键词搜索):翻目录卡片,按书名精确匹配。搜"Python 入门"只能找到标题里有这几个字的书
  • 问管理员方式(语义搜索):跟管理员说"我想学编程",他理解你的意思,给你推荐相关的书,不管书名叫什么

向量数据库干的就是"管理员"的活。它把每条文档编码成向量(一串数字),搜索时把你的查询也编码成向量,然后计算向量间的距离找最接近的。

ChromaDB 在此基础上多做了一件事:它把这些向量连同原始文本和元数据一起存好,不用你操心存储细节。FAISS 是搜索库,你得自己管存储;ChromaDB 是完整数据库,存储、索引、元数据一站式搞定。

5 分钟快速上手

第一步:安装

1
pip install chromadb

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

维度ChromaDBFAISSQdrant
部署方式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,一起学习,一起成长