CH3_02_langchain_faiss

1511 字
8 分钟
CH3_02_langchain_faiss

下面的代码演示了使用 LangChain 和 FAISS 完成一个完整的“创建 -> 保存 -> 加载 -> 查询”流程。

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document

这里我们引入了 LangChain 的强大生态系统:

  • langchain_community.vectorstores & FAISS :这是由社区维护的第三方集成包,FAISS 就是向量数据库的封装类。它将 FAISS 封装为了 LangChain 统一的向量存储接口,提供 from_documentssimilarity_searchsave_localload_local 等方法,方便与 LangChain 的其他组件(Chain、Retriever 等)无缝配合。
  • langchain_community.embeddings & HuggingFaceEmbeddings :这里是将 Hugging Face 上的 Embedding 模型(如我们接下来用的 BAAI/bge-small-zh-v1.5)封装成 LangChain 统一接口。只需指定 model_name,就能把文本转为向量,供 FAISS 存储和检索。
  • langchain_core.documents & Document :这是 LangChain 的核心基础包,其中 Document 是文档数据类。它是 LangChain 中最基本的数据结构,包含两个字段:page_content(文本内容)和 metadata(元数据字典)。整个 RAG 流程中的数据载体——文本加载、分块、向量化、检索,都以 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}")
  • from_documents :返回一个由 documentsembedding 初始化的 VectorStore 对象,接收三个参数:
    • documents :要添加到 VectorStore 的文档列表;
    • embedding :要使用的嵌入函数;
    • kwargs :其他关键字参数。
  • save_local :将 FAISS 索引、文档库和 index_to_docstore_id 保存到磁盘,接收两个参数:
    • folder_path :要保存的文件夹路径;
    • index_name :用于指定保存的索引文件名。
索引创建细节#

通过深入 LangChain 源码,可以发现索引创建是一个分层、解耦的过程,主要涉及以下几个方法的嵌套调用:

  1. from_documents (封装层):

    • 这是我们直接调用的方法。它的职责很简单:从输入的 Document 对象列表中提取出纯文本内容 (page_content) 和元数据 (metadata)。
    • 然后,它将这些提取出的信息传递给核心的 from_texts 方法。
  2. from_texts (向量化入口):

    • 这个方法是面向用户的入口。它接收文本列表,并执行关键的第一步:调用 embedding.embed_documents(texts),将所有文本批量转换为向量。
    • 完成向量化后,它并不直接处理索引构建,而是将生成的向量和其他所有信息(文本、元数据等)传递给一个内部的辅助方法 __from
  3. __from (构建索引框架):

    • 一个内部方法,负责搭建 FAISS 向量存储的“空框架”。
    • 它会根据指定的距离策略(默认为 L2 欧氏距离)初始化一个空的 FAISS 索引结构(如 faiss.IndexFlatL2)。
    • 同时,它也准备好了用于存储文档原文的 docstore 和用于连接 FAISS 索引与文档的 index_to_docstore_id 映射。
    • 最后,它调用另一个内部方法 __add 来完成数据的填充。
  4. __add (填充数据):

    • 真正执行数据添加操作的核心。它接收到向量、文本和元数据后,执行以下关键操作:
      • 添加向量: 将向量列表转换为 FAISS 需要的 numpy 数组,并调用 self.index.add(vector) 将其批量添加到 FAISS 索引中。
      • 存储文档: 将文本和元数据打包成 Document 对象,存入 docstore
      • 建立映射: 更新 index_to_docstore_id 字典,建立起 FAISS 内部的整数 ID(如 0, 1, 2…)到我们文档唯一 ID 的映射关系。
# 3. 加载索引并执行查询
# 加载时需指定相同的嵌入模型,并允许反序列化
loaded_vectorstore = FAISS.load_local(
local_faiss_path,
embeddings,
allow_dangerous_deserialization=True
)
  • load_local :从磁盘加载 FAISS 索引、文档库和 index_to_docstore_id,接收四个参数:
    • folder_path :要从中加载索引、文档库和 index_to_docstore_id 的文件夹路径;
    • embeddings :生成查询时使用的嵌入;
    • index_name :用于保存的特定索引文件名;
    • allow_dangerous_deserialization :是否允许反序列化数据,这涉及到加载 pickle 文件。恶意攻击者可以修改 pickle 文件,从而传递恶意载荷,导致在您的计算机上执行任意代码。
序列化与反序列化#

序列化(Serialization):把内存中的对象(比如 Python 的列表、字典、自定义类实例)转换成可以存储或传输的格式(比如二进制文件、JSON 字符串)。

反序列化(Deserialization):反过来,把存储的二进制数据恢复成内存中的对象。

当我们调用 FAISS.load_local() 时,如上所述,它需要从磁盘恢复三样东西:

  • index.faiss :内容为 FAISS 向量索引,二进制格式,安全;
  • index.pkl :文档库 + index_to_docstore_id,Pickle 格式,有风险

之所以说 index.pkl 是危险的,是因为 Pickle 反序列化可以执行任意代码。考虑一个恶意构造的 pickle 文件:

# 攻击者构造的恶意 pickle 数据
import pickle
import os
# 在反序列化时自动执行系统命令
class Malicious:
def __reduce__(self):
return (os.system, ("rm -rf /重要数据",))
with open("index.pkl", "wb") as f:
pickle.dump(Malicious(), f)

可以看到,当我们用 pickle.load() 加载它时,这段恶意代码就会在我们的电脑上执行。相当于你捡了一个 U 盘插到自己电脑上,如果 U 盘里有病毒,你就中招了。

这里我们传入 allow_dangerous_deserialization=True,是因为这里的 .pkl 文件是我们刚刚自己保存的,来源可信,所以安全。

# 执行相似性搜索
query = "FAISS是做什么的?"
results = loaded_vectorstore.similarity_search(query, k=1)
  • similarity_search :返回最符合 query 的 docs,接收四个参数:
    • query :要查找相似 doc 的文本;
    • k :要返回 docs 的数量,默认为 4;
    • filter :可选,按元数据过滤,默认为 None;
    • fetch_k :可选,过滤前要获取的 docs 数量,默认为 20.
print(f"\n查询: '{query}'")
print("相似度最高的文档:")
for doc in results:
print(f"- {doc.page_content}")

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

CH3_02_langchain_faiss
https://datawhalechina.github.io/all-in-rag/#/chapter1/03_get_start_rag
作者
HAC
发布于
2026-06-23
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录