前情回顾
在 上一篇文章 中,我们化身效率大师,使用FAISS成功地为海量向量构建了高速索引,解决了RAG系统中的"检索慢"问题。那一刻,我们仿佛掌控了从百万数据中大海捞针的神力。
然而,兴奋劲还没过,几个头疼的工程问题就冒了出来:
- 持久化之痛:我们用FAISS辛辛苦苦构建的索引,它只存在于内存里。程序一重启,一切归零,我们又得重新加载全部数据、重新训练、重新构建索引。这谁受得了?
- 元数据之殇 :FAISS只关心向量本身。它返回给我们的是向量的ID和距离。但那个ID对应的原始文本块是什么?它来自哪个文档的第几页?这些信息(我们称之为元数据 Metadata)我们得自己另找地方存(比如另一个SQL数据库),然后小心翼翼地做关联。太繁琐了!
- 管理之烦:如果我想删除或更新某一个文本块和它对应的向量呢?在FAISS这类底层库里操作起来非常复杂。
总而言之,我们造出了一台性能强劲的"发动机"(FAISS),但我们还得自己手动焊车架、装轮子、连电路。是时候让专业的"汽车制造商"出场了。
这个制造商,就是向量数据库 (Vector Database)。
什么是向量数据库?
向量数据库是一种专门为了高效存储、索引、管理和查询海量向量及其关联元数据而设计的数据库系统。
它就像一个全能管家,把我们昨天头疼的所有问题都打包解决了:
- 向量存储与索引:内置了类似FAISS的高效索引算法。
- 元数据存储:允许你将向量和其对应的文本、来源、日期等元数据绑定存储。
- 数据持久化:自动将数据和索引安全地存储在磁盘上。
- 简单的API:提供增(Add)、查(Query)、改(Update)、删(Delete)等简单易用的接口。
- 可扩展性:为分布式和大规模部署提供了解决方案。
今天,我们来认识一位向量数据库领域的"小而美"的明星选手:ChromaDB。它非常轻量,对新手极其友好,堪称向量数据库界的"SQLite"。
上手实战:ChromaDB 四步走
我们将用ChromaDB重新实现一遍存储和检索的流程,感受一下它到底有多方便。
首先,安装必要的库:
bash
pip install chromadb sentence-transformers
第1步:初始化客户端,创建集合
ChromaDB可以纯内存运行,也可以持久化到本地。我们当然选择后者。 一个"集合(Collection)"就类似于SQL数据库中的一张"表"。
python
import chromadb
# 初始化一个持久化的客户端,数据将存储在'my_chroma_db'目录下
client = chromadb.PersistentClient(path="my_chroma_db")
# 创建一个名为"rag_series_demo"的集合
# 如果该集合已存在,get_or_create_collection会直接获取它
collection = client.get_or_create_collection(name="rag_series_demo")
就这么简单,我们的"数据库"和"表"就建好了。
第2步:添加数据(见证奇迹的时刻)
这是ChromaDB最能体现其便利性的地方。我们不再需要自己去手动做Embedding,再把向量和ID分开添加。我们可以把所有信息一股脑地扔给它!
python
# 准备要添加的数据,注意,这次我们有丰富的元数据
documents_to_add = [
"RAG的核心思想是检索增强生成。",
"FAISS是Facebook开源的向量检索库。",
"ChromaDB是一个对开发者友好的向量数据库。",
"今天天气真不错,适合出去玩。",
]
metadatas_to_add = [
{"source": "doc1", "type": "tech"},
{"source": "doc2", "type": "tech"},
{"source": "doc3", "type": "tech"},
{"source": "doc4", "type": "daily"},
]
ids_to_add = ["id1", "id2", "id3", "id4"]
# 只需一个add命令,ChromaDB会自动处理:
# 1. 调用默认的Embedding模型将documents转换为向量 (我们也可以指定自己的模型)
# 2. 存储向量、文档原文、元数据和ID
collection.add(
documents=documents_to_add,
metadatas=metadatas_to_add,
ids=ids_to_add
)
看到了吗?文本、元数据、ID,一步到位! 这背后繁琐的Embedding、存储、索引过程,ChromaDB已经默默为我们全部搞定。
第3步:查询集合
查询同样简单到令人发指。
python
# 定义查询
query_texts = ["什么是向量数据库?"]
# 执行查询
results = collection.query(
query_texts=query_texts,
n_results=2 # 我们想找最相关的2个结果
)
# 打印结果
import json
print(json.dumps(results, indent=2, ensure_ascii=False))
输出结果:
json
{
"ids": [
[
"id3",
"id2"
]
],
"distances": [
[
0.4851231575012207,
1.0456184148788452
]
],
"metadatas": [
[
{
"source": "doc3",
"type": "tech"
},
{
"source": "doc2",
"type": "tech"
}
]
],
"embeddings": null,
"documents": [
[
"ChromaDB是一个对开发者友好的向量数据库。",
"FAISS是Facebook开源的向量检索库。"
]
],
"uris": null,
"data": null
}
太完美了!返回的结果是一个结构清晰的对象,包含了ID、距离、元数据 和原始文档!我们不再需要拿着ID去另一个地方查找原文了。
第4. 杀手级特性:带元数据过滤的查询
如果我只想在"科技类(tech)"文档中进行搜索怎么办?ChromaDB的 where
子句可以轻松实现。
python
results_filtered = collection.query(
query_texts=["什么是向量数据库?"],
n_results=2,
where={"type": "tech"} # 只在type为'tech'的文档中搜索
)
# (你可以自行打印看看结果,它不会包含type为'daily'的文档)
这个功能在构建复杂的检索策略时极其有用!
总结与预告
今日小结:
- 手动管理FAISS索引和元数据非常繁琐,难以维护和扩展。
- 向量数据库(如ChromaDB)提供了一站式的解决方案,集成了存储、索引、元数据管理和持久化功能。
- 使用ChromaDB,我们可以通过简单的
add
和query
命令,轻松地管理和检索"文档-元数据-向量"三位一体的数据。- 元数据过滤是向量数据库的强大功能,能实现更精准、更灵活的检索。
到此,我们已经打通了RAG系统中"R"(Retrieval,检索)部分的基础全链路:文本分块 -> 向量嵌入 -> 存储与检索。
但,检索出来的结果就完美了吗?我们取了Top 3的结果,是不是这3个都同等重要?有没有可能第3个结果其实比第1个更贴近用户的真实意图?
仅仅依靠向量相似度做的第一轮"海选",有时候并不足够精确。我们需要一个"复试"环节,来对海选出来的结果进行精修和重排。
明天预告:RAG 每日一技(七):只靠检索还不够?用Re-ranking给你的结果精修一下
明天,我们将学习如何引入一个"精排模型(Re-ranker)",对初步检索到的文档进行二次排序,把最最相关的答案推到最前面,为最终的生成环节提供最高质量的"弹药"!