all-in-rag 实战:向量数据库选型与 FAISS 本地向量存储实现指南
【免费下载链接】all-in-rag🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/项目地址: https://gitcode.com/datawhalechina/all-in-rag
导读
本文是 Datawhale《大模型应用开发实战一:RAG 技术全栈指南》中「索引构建」章节的深入讲解,围绕向量数据库在 RAG 流水线中的核心作用展开。你将理解向量数据库与传统关系型数据库的本质差异、HNSW/IVF 等 ANN 索引的底层原理、主流向量数据库的选型思路,并通过仓库中可运行的源码示例,完整掌握基于 LangChain + FAISS 的「创建 → 保存 → 加载 → 查询」本地向量存储流程,以及 LlamaIndex 的 JSON 持久化方案,为后续构建生产级 RAG 系统打下坚实基础。
一、向量数据库的作用
在上一节向量嵌入中,我们已经学会了使用嵌入模型将文本、图像等非结构化数据转换为高维向量。这些向量是 RAG 系统进行语义理解的基础。然而,当向量数量从几百个增长到数百万甚至数十亿时,一个核心问题随之而来:如何快速、准确地从海量向量中找到与用户查询最相似的那几个?
1.1 向量数据库主要功能
向量数据库的核心价值在于其高效处理海量高维向量的能力。其主要功能可以概括为以下几点:
- 高效的相似性搜索:这是向量数据库最重要的功能。它利用专门的索引技术(如 HNSW、IVF),能够在数十亿级别的向量中实现毫秒级的近似最近邻(ANN)查询,快速找到与给定查询最相似的数据。
- 高维数据存储与管理:专门为存储高维向量(通常维度成百上千)而优化,支持对向量数据进行增、删、改、查等基本操作。
- 丰富的查询能力:除了基本的相似性搜索,还支持按标量字段过滤查询(例如,在搜索相似图片的同时,指定
年份 > 2023)、范围查询和聚类分析等,满足复杂业务需求。 - 可扩展与高可用:现代向量数据库通常采用分布式架构,具备良好的水平扩展能力和容错性,能够通过增加节点来应对数据量的增长,并确保服务的稳定可靠。
- 数据与模型生态集成:与主流的 AI 框架(如 LangChain、LlamaIndex)和机器学习工作流无缝集成,简化了从模型训练到向量检索的应用开发流程。
1.2 向量数据库 vs 传统数据库
传统的数据库(如 MySQL)擅长处理结构化数据的精确匹配查询(例如,WHERE age = 25),但它们并非为处理高维向量的相似性搜索而设计的。在庞大的向量集合中进行暴力、线性的相似度计算,其计算成本和时间延迟无法接受。向量数据库(Vector Database)很好地解决了这一问题,它是一种专门设计用于高效存储、管理和查询高维向量的数据库系统。在 RAG 流程中,它扮演着「知识库」的角色,是连接数据与大语言模型的关键桥梁。
向量数据库与传统数据库的主要差异如下:
| 维度 | 向量数据库 | 传统数据库 (RDBMS) |
|---|---|---|
| 核心数据类型 | 高维向量 (Embeddings) | 结构化数据 (文本、数字、日期) |
| 查询方式 | 相似性搜索(ANN) | 精确匹配 |
| 索引机制 | HNSW, IVF, LSH 等 ANN 索引 | B-Tree, Hash Index |
| 主要应用场景 | AI 应用、RAG、推荐系统、图像/语音识别 | 业务系统 (ERP, CRM)、金融交易、数据报表 |
| 数据规模 | 轻松应对千亿级向量 | 通常在千万到亿级行数据,更大规模需复杂分库分表 |
| 性能特点 | 高维数据检索性能极高,计算密集型 | 结构化数据查询快,高维数据查询性能呈指数级下降 |
| 一致性 | 通常为最终一致性 | 强一致性 (ACID 事务) |
向量数据库和传统数据库并非相互替代的关系,而是互补关系。在构建现代 AI 应用时,通常会将两者结合使用:利用传统数据库存储业务元数据和结构化信息,而向量数据库则专门负责处理和检索由 AI 模型产生的海量向量数据。
二、工作原理
向量数据库的核心是高效处理高维向量的相似性搜索。向量是一组有序的数值,可以表示文本、图像、音频等复杂数据的特征或属性。在 RAG 系统中,向量一般通过嵌入模型将原始数据转换为高维向量表示,比如上一节的图文示例。
向量数据库通常采用四层架构,通过存储层、索引层、查询层和服务层的协同工作来实现高效相似性搜索:
- 存储层:负责存储向量数据和元数据,优化存储效率并支持分布式存储;
- 索引层:维护索引算法(HNSW、LSH、PQ 等),负责索引的创建与优化,并支持索引调整;
- 查询层:处理查询请求,支持混合查询并实现查询优化;
- 服务层:管理客户端连接,提供监控和日志能力,并实现安全管理。
在上述架构中,索引层是决定检索性能的关键。主要的技术手段包括:
- 基于树的方法:如 Annoy 使用的随机投影树,通过树形结构实现对数复杂度的搜索;
- 基于哈希的方法:如 LSH(局部敏感哈希),通过哈希函数将相似向量映射到同一「桶」;
- 基于图的方法:如 HNSW(分层可导航小世界图),通过多层邻近图结构实现快速搜索;
- 基于量化的方法:如 Faiss 的 IVF 和 PQ,通过聚类和量化压缩向量。
关于不同索引类型(FLAT、IVF 系列、HNSW、DiskANN)的详细原理、优缺点对比与选型策略,可继续阅读本仓库的第四节 Milvus 介绍及多模态检索实践与第五节 索引优化。
三、主流向量数据库介绍
目前市面上的向量数据库产品丰富多样,可以从「开源/商业」与「专用向量数据库/支持向量搜索的通用数据库」两个维度进行归类。下面这张分类图清晰地展示了主流产品在整个生态中的位置:
当前主流的向量数据库产品包括:
- Pinecone:一款完全托管的向量数据库服务,采用 Serverless 架构设计。它提供存储计算分离、自动扩展和负载均衡等企业级特性,并保证 99.95% 的 SLA。Pinecone 支持多种语言 SDK,提供极高可用性和低延迟搜索(<100ms),特别适合企业级生产环境、高并发场景和大规模部署。
- Milvus:一款开源的分布式向量数据库,采用分布式架构设计,支持 GPU 加速和多种索引算法。它能够处理亿级向量检索,提供高性能 GPU 加速和完善的生态系统。Milvus 特别适合大规模部署、高性能要求的场景,以及需要自定义开发的开源项目。
- Qdrant:一款高性能的开源向量数据库,采用 Rust 开发,支持二进制量化技术。它提供多种索引策略和向量混合搜索功能,能够实现极高的性能(RPS>4000)和低延迟搜索。Qdrant 特别适合性能敏感应用、高并发场景以及中小规模部署。
- Weaviate:一款支持 GraphQL 的 AI 集成向量数据库,提供 20+ AI 模块和多模态支持。它采用 GraphQL API 设计,支持 RAG 优化,特别适合 AI 开发、多模态处理和快速开发场景。Weaviate 具有活跃的社区支持和易于集成的特点。
- Chroma:一款轻量级的开源向量数据库,采用本地优先设计,无依赖。它提供零配置安装、本地运行和低资源消耗等特性,特别适合原型开发、教育培训和小规模应用。Chroma 的部署简单,适合快速原型开发。
选择建议:
- 新手入门/小型项目:从
ChromaDB或FAISS开始是最佳选择。它们与 LangChain/LlamaIndex 紧密集成,几行代码就能运行,且能满足基本的存储和检索需求。 - 生产环境/大规模应用:当数据量超过百万级,或需要高并发、实时更新、复杂元数据过滤时,应考虑更专业的解决方案,如
Milvus、Weaviate或云服务Pinecone。
四、本地向量存储:以 FAISS 为例
FAISS(Facebook AI Similarity Search)是一个由 Facebook AI Research 开发的高性能库,专门用于高效的相似性搜索和密集向量聚类。当与 LangChain 结合使用时,它可以作为一个强大的本地向量存储方案,非常适合快速原型设计和中小型应用。
与 ChromaDB 等数据库不同,FAISS 本质上是一个算法库,它将索引直接保存为本地文件(一个.faiss索引文件和一个.pkl映射文件),而非运行一个数据库服务。这种方式轻量且高效。
4.1 环境准备
在开始之前,请确保已安装所有必需的库。本仓库根目录下的 code/requirements.txt 已经声明了全部依赖,其中向量存储相关的关键依赖包括:
langchain==0.3.26、langchain-community==0.3.27(LangChain 生态,提供FAISS向量存储封装);langchain-huggingface==0.3.1(提供HuggingFaceEmbeddings等本地嵌入模型接入);faiss-cpu>=1.7.0(FAISS 算法库本体);chromadb>=0.4.0(Chroma 向量数据库);pymilvus==2.5.11、pymilvus.model==0.3.2(Milvus 客户端,用于后续章节);sentence-transformers>=3.0.0(HuggingFace 嵌入模型底层依赖)。
pip install -r code/requirements.txt当前
requirements.txt安装的faiss-cpu是 CPU 版本。如果你的机器有 GPU,可以安装faiss-gpu以获得更好的性能。
4.2 基础示例(FAISS)
下面的代码演示了使用 LangChain 和 FAISS 完成一个完整的「创建 → 保存 → 加载 → 查询」流程。该示例与仓库中的 code/C3/02_langchain_faiss.py 完全一致,可直接运行验证。
from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_core.documents import Document # 1. 示例文本和嵌入模型 texts = [ "张三是法外狂徒", "FAISS是一个用于高效相似性搜索和密集向量聚类的库。", "LangChain是一个用于开发由语言模型驱动的应用程序的框架。" ] docs = [Document(page_content=t) for t in texts] embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5") # 2. 创建向量存储并保存到本地 vectorstore = FAISS.from_documents(docs, embeddings) local_faiss_path = "./faiss_index_store" vectorstore.save_local(local_faiss_path) print(f"FAISS index has been saved to {local_faiss_path}") # 3. 加载索引并执行查询 # 加载时需指定相同的嵌入模型,并允许反序列化 loaded_vectorstore = FAISS.load_local( local_faiss_path, embeddings, allow_dangerous_deserialization=True ) # 相似性搜索 query = "FAISS是做什么的?" results = loaded_vectorstore.similarity_search(query, k=1) print(f"\n查询: '{query}'") print("相似度最高的文档:") for doc in results: print(f"- {doc.page_content}")运行结果与解读:
当你运行上述脚本时,会看到类似以下的输出:
FAISS index has been saved to ./faiss_index_store 查询: 'FAISS是做什么的?' 相似度最高的文档: - FAISS是一个用于高效相似性搜索和密集向量聚类的库。可见,虽然查询语句「FAISS是做什么的?」与文档原文「FAISS是一个用于高效相似性搜索和密集向量聚类的库。」用词并不完全一致,但 FAISS 依然通过语义相似度准确召回了目标文档,这正是向量检索区别于关键词匹配的核心价值。
关键细节说明:
- 嵌入模型一致性:
load_local时必须传入与创建索引时完全相同的嵌入模型(BAAI/bge-small-zh-v1.5)。因为加载后的查询向量需要用同一模型编码,才能在相同的向量空间中进行相似度比较。 allow_dangerous_deserialization=True:FAISS 的本地文件包含序列化的docstore(文档原文与元数据),LangChain 出于安全考虑默认禁止反序列化,此处需要显式开启。务必仅加载可信来源的索引文件,不要加载来路不明的.pkl文件,以免触发反序列化漏洞。
4.3 索引创建实现细节:LangChain 源码调用链
通过深入 LangChain 源码,可以发现索引创建是一个分层、解耦的过程,主要涉及以下几个方法的嵌套调用:
from_documents(封装层):- 这是我们直接调用的方法。它的职责很简单:从输入的
Document对象列表中提取出纯文本内容(page_content)和元数据(metadata)。 - 然后,它将这些提取出的信息传递给核心的
from_texts方法。
- 这是我们直接调用的方法。它的职责很简单:从输入的
from_texts(向量化入口):- 这个方法是面向用户的入口。它接收文本列表,并执行关键的第一步:调用
embedding.embed_documents(texts),将所有文本批量转换为向量。 - 完成向量化后,它并不直接处理索引构建,而是将生成的向量和其他所有信息(文本、元数据等)传递给一个内部的辅助方法
__from。
- 这个方法是面向用户的入口。它接收文本列表,并执行关键的第一步:调用
__from(构建索引框架):- 一个内部方法,负责搭建 FAISS 向量存储的「空框架」。
- 它会根据指定的距离策略(默认为 L2 欧氏距离)初始化一个空的 FAISS 索引结构(如
faiss.IndexFlatL2)。 - 同时,它也准备好了用于存储文档原文的
docstore和用于连接 FAISS 索引与文档的index_to_docstore_id映射。 - 最后,它调用另一个内部方法
__add来完成数据的填充。
__add(填充数据):- 真正执行数据添加操作的核心。它接收到向量、文本和元数据后,执行以下关键操作:
- 添加向量:将向量列表转换为 FAISS 需要的
numpy数组,并调用self.index.add(vector)将其批量添加到 FAISS 索引中。 - 存储文档:将文本和元数据打包成
Document对象,存入docstore。 - 建立映射:更新
index_to_docstore_id字典,建立起 FAISS 内部的整数 ID(如 0, 1, 2...)到我们文档唯一 ID 的映射关系。
- 添加向量:将向量列表转换为 FAISS 需要的
- 真正执行数据添加操作的核心。它接收到向量、文本和元数据后,执行以下关键操作:
理解这条调用链的意义在于:当你需要定制索引构建流程时(例如更换距离度量、批量增量添加文档、或自定义文档 ID 映射),你就知道应该从哪一层切入。例如,生产环境中「先建索引框架、再分批add文档」的需求,就可以直接基于__from与__add的思路进行扩展。
4.4 生产环境中的 FAISS 封装实践
FAISS 的用法不止于演示脚本。在仓库第八章的生产级 RAG 项目实战中,code/C8/rag_modules/index_construction.py 就将 FAISS 封装成了可复用的VectorIndexBuilder类:通过build_vector_index(chunks)方法调用FAISS.from_documents完成索引构建,并通过load_local实现索引的持久化加载。这种「构建 → 保存 → 加载」的分层设计,与本文 4.2 节的示例一脉相承,但补充了日志记录、路径管理与异常处理等生产要素,是学习如何将 FAISS 从脚本升级到工程模块的绝佳范例。
五、LlamaIndex 的向量存储:JSON 持久化实践
除了 LangChain + FAISS,本教程的另一大框架是 LlamaIndex。LlamaIndex 的VectorStoreIndex默认使用内存存储,但其StorageContext支持将索引以透明可读的 JSON 格式持久化到本地磁盘,这一点与 FAISS 的二进制索引文件形成鲜明对比,也便于开发者直接检查索引内容。
仓库中的 code/C3/03_llamaindex_vector.py 演示了完整的创建与持久化流程:
from llama_index.core import VectorStoreIndex, Document, Settings from llama_index.embeddings.huggingface import HuggingFaceEmbedding # 1. 配置全局嵌入模型 Settings.embed_model = HuggingFaceEmbedding("BAAI/bge-small-zh-v1.5") # 2. 创建示例文档 texts = [ "张三是法外狂徒", "LlamaIndex是一个用于构建和查询私有或领域特定数据的框架。", "它提供了数据连接、索引和查询接口等工具。" ] docs = [Document(text=t) for t in texts] # 3. 创建索引并持久化到本地 index = VectorStoreIndex.from_documents(docs) persist_path = "./llamaindex_index_store" index.storage_context.persist(persist_dir=persist_path) print(f"LlamaIndex 索引已保存至: {persist_path}")运行后会生成llamaindex_index_store目录,其中以 JSON 文件形式存储了向量(default__vector_store.json)、文档节点(docstore.json)与索引元数据(index_store.json)。由于 JSON 是明文格式,你可以直接打开文件查看向量数值与文本的对应关系,这在调试阶段非常有用——这也是 LlamaIndex「默认透明可读」设计理念的体现。
5.1 加载与相似性搜索
对应地,加载持久化的索引并执行相似性搜索,只需要使用StorageContext.from_defaults(persist_dir=...)恢复存储上下文,再通过load_index_from_storage重建索引:
from llama_index.core import StorageContext, load_index_from_storage # 加载持久化的存储上下文并重建索引 storage_context = StorageContext.from_defaults(persist_dir="./llamaindex_index_store") index = load_index_from_storage(storage_context) # 转换为查询引擎并执行语义搜索 query_engine = index.as_query_engine(similarity_top_k=1) response = query_engine.query("LlamaIndex 是用来做什么的?") print(response)这里需要注意:重建索引时,Settings.embed_model必须与持久化时保持一致(同样是BAAI/bge-small-zh-v1.5),否则查询向量的向量空间与库内向量不一致,检索结果将失去意义。这与 4.2 节 FAISS 的「嵌入模型一致性」原则是同一个道理。
六、从本地走向生产:Milvus 分布式向量数据库
当业务数据量突破百万级、需要高并发与实时更新时,FAISS/Chroma 这类本地轻量方案会逐渐触达瓶颈。此时应转向真正的分布式向量数据库。在本书的第四节 Milvus 介绍及多模态检索实践中,你将看到使用 Docker Compose 一键部署 Milvus Standalone(含etcd元数据存储与MinIO对象存储),并基于pymilvus完成「Schema 设计 → 创建 Collection → 批量插入 → 构建 HNSW 索引 → 多模态检索」的完整流程,其完整代码位于 code/C3/04_multi_milvus.py。
值得注意的是,04_multi_milvus.py 中创建 HNSW 索引时使用了params={"M": 16, "efConstruction": 256},检索时设置params={"ef": 128}——这三个参数分别控制着图节点的最大连接数、索引构建时的搜索宽度与查询时的遍历深度,是决定「召回率 vs 延迟」权衡的核心旋钮。理解这些参数,是后续阅读第五节 索引优化中句子窗口检索、结构化索引等内容的前提。
练习与延伸
- LlamaIndex 持久化观察:运行 03_llamaindex_vector.py,查看生成的
llamaindex_index_store目录下 JSON 文件的内容,体会 LlamaIndex 默认「透明可读」的存储设计。 - LlamaIndex 加载与检索:新建一个代码文件,参考 5.1 节示例,实现对上述 JSON 数据的加载和相似性搜索,并尝试更换查询语句观察召回结果的变化。
- FAISS 查询深度调参:修改 02_langchain_faiss.py 中的
k值(如k=2、k=3),观察返回的相似度文档排序,理解 Top-K 召回的含义;进一步可尝试在FAISS.from_documents中通过distance_strategy指定COSINE或MAX_INNER_PRODUCT,对比不同度量方式对中文语义检索效果的影响。 - 进阶阅读:完成上述练习后,建议继续阅读第四节 Milvus 介绍及多模态检索实践,并运行 04_multi_milvus.py(需要先启动 Milvus 服务),体验从本地单机向量库到分布式向量库的完整升级路径。
【免费下载链接】all-in-rag🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/项目地址: https://gitcode.com/datawhalechina/all-in-rag
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考