FAISS 与 LangChain 在文档检索中的分工
一句话结论
FAISS 只管"向量 → 编号",LangChain 负责其余全部:编码、存原文、编号转原文、打包返回、存盘。
1 整体流程
python
db = FAISS.from_documents(chunks, embeddings)
retriever = db.as_retriever(search_kwargs={"k": 2})
docs = retriever.invoke(question)
store.save_local(save_path)
内部真正发生的事:
text
chunks ──embedding──→ 向量 ──FAISS──→ index.faiss
│
└──────────────────────────────→ index.pkl(编号→原文)
question ──embedding──→ 查询向量
│
▼
FAISS 检索 → 编号 [0, 2]
│
▼
查 index.pkl → 原文
│
▼
打包成 Document 返回
2 两个文件,两个归属(重点:别被同一路径骗了)
save_local(save_path) 会把两个文件存进同一个目录,很容易误以为都是 FAISS 的,其实归属不同:
| 文件 | 存什么 | 属于谁 | FAISS 认不认 |
|---|---|---|---|
index.faiss |
向量 | FAISS | ✅ 这是它的全部世界 |
index.pkl |
编号 → 原文 | LangChain | ❌ 完全不知道它存在 |
为什么放一起? 因为 save_local 是 LangChain 封装的"一键保存",它顺手把两者放同一目录方便管理。但同目录 ≠ 同一家。
两边靠插入顺序保证编号一一对应:
text
index.faiss[0] ←→ index.pkl["0"]
index.faiss[1] ←→ index.pkl["1"]
index.faiss[2] ←→ index.pkl["2"]
3 逐行拆解职责
第 1 行:FAISS.from_documents(chunks, embeddings)
| 动作 | 谁做的 |
|---|---|
| chunk → 向量 | Embedding 模型 |
| 向量存进索引 | FAISS |
| 原文按编号存成字典 | LangChain |
结果 db 里挂着两样:向量索引(FAISS) + 编号→原文字典(LangChain)。
第 2 行:db.as_retriever(...)
纯 LangChain,只是把 db 包装成统一接口,不涉及 FAISS。
第 3 行:retriever.invoke(question)
| 步骤 | 做什么 | 谁做的 |
|---|---|---|
| ① | question → 查询向量 | Embedding 模型 |
| ② | 向量 → 编号 [0, 2] |
FAISS |
| ③ | 编号 → 原文 | LangChain |
| ④ | 打包成 Document 返回 |
LangChain |
第 4 行:store.save_local(save_path)
| 动作 | 谁做的 |
|---|---|
存 index.faiss |
FAISS (faiss.write_index) |
存 index.pkl |
LangChain(pickle 那个字典) |
| 把两者打包成一个调用、放同一目录 | LangChain |
4 核心结论
- FAISS 的戏份只有两处 :存向量、返回编号。存盘时也只负责
index.faiss。 - LangChain 的戏份 :调 embedding、维护"编号→原文"映射、把编号还原成原文、打包返回、统一接口、存
index.pkl。 - FAISS 是纯向量检索库 ,它不知道"文字"的存在 ;
index.pkl是 LangChain 为了补回原文而额外维护的,与 FAISS 无关。 save_local把两个文件放同一目录,纯属 LangChain 的封装便利,不代表它们同源。
FAISS 提供向量检索能力,LangChain 提供"检索 + 原文回填 + 一键存盘"的完整链路。