本文旨在理解向量檢索的實(shí)現(xiàn)機(jī)制、VectorStore 與 Retriever 在 LangChain 中的職責(zé)劃分,掌握 FAISS 與 Chroma 的實(shí)踐用法,以及如何把向量庫“作為 retriever” 接入檢索增強(qiáng)生成(RAG)流水線。
一、先把概念理清楚(最重要的點(diǎn))
- 向量數(shù)據(jù)庫 / VectorStore:把文本或片段映射為向量并存儲、索引,用于快速近鄰搜索(ANN)。在 LangChain 中常見實(shí)現(xiàn)包括 FAISS、Chroma、Milvus、Pinecone 等。
- Embedding:把文本變?yōu)橄蛄康哪P停∣penAIEmbeddings / HuggingFaceEmbeddings / 本地 xinference 等)。Embedding 的語義質(zhì)量直接決定檢索效果。
- Retriever(檢索器):一個抽象層(BaseRetriever),負(fù)責(zé)“給我最相關(guān)的 documents”。VectorStore 通常提供 .as_retriever() 把自己包裝成 Retriever。LangChain 的 Retriever 遵循 Runnable 接口(支持 .invoke() / .ainvoke() 等)。
關(guān)鍵區(qū)別:VectorStore 負(fù)責(zé)存與查;Retriever 負(fù)責(zé)把“查詢”映射成文檔列表并做業(yè)務(wù)側(cè)包裝(例如加過濾、返回 score、metadata 過濾等)。
二、FAISS 與 Chroma 的快速比較(實(shí)踐視角)
FAISS(Facebook AI Similarity Search)
優(yōu)點(diǎn):極高的性能與靈活的索引(IVF、PQ、HNSW 等),適合大規(guī)模離線索引與高吞吐檢索。實(shí)現(xiàn)上常用于內(nèi)存/本地服務(wù)化場景。
典型場景:批量構(gòu)建索引后離線部署、需要自定義索引參數(shù)與性能調(diào)優(yōu)時。
Chroma
優(yōu)點(diǎn):開源、易用、開發(fā)者友好,提供嵌入 + 向量存儲的一體化體驗(yàn),適合中小規(guī)??焖俚angChain 提供了 Chroma 的一鍵集成。
典型場景:快速原型、POC、輕量級應(yīng)用或與 metadata 結(jié)合做過濾。
選擇建議:如果你要生產(chǎn)級、海量檢索(億級向量),優(yōu)先 FAISS / Milvus / ANN 專有云;若只是開發(fā)迭代或中小數(shù)據(jù),Chroma 更省心。
三、as_retriever() 的作用與行為
as_retriever() 是 VectorStore 提供的便捷方法,把 VectorStore 包裝成 BaseRetriever(或可直接當(dāng)作 Runnable 使用)。調(diào)用后你得到的 retriever 常帶有 search_kwargs(比如 k),并可被 LangChain 的 create_retrieval_chain / RetrievalQA 直接消費(fèi)。官方 How-to 有示例。
要點(diǎn):
- vectorstore.as_retriever(search_kwargs={"k":4}) → 返回 top-k 文檔(含 metadata、score)。
- Retriever 遵循 Runnable 接口,可直接 .invoke({"input": "..."}) 或在 Chain 中被自動調(diào)用。
四、實(shí)戰(zhàn)示例(兩版:FAISS 與 Chroma,基于最新 create_retrieval_chain)
說明:下面示例基于 LangChain 最新 API(create_retrieval_chain(retriever, combine_docs_chain)),并展示如何用 as_retriever()。你可以把 embedding 換成本地模型(如 Xinference / HuggingFace)。
依賴(示例環(huán)境)
pip install -U langchain langchain_community faiss-cpu chromadb sentence-transformers
示例 A:FAISS + Xinference Embeddings(小數(shù)據(jù)集演示)
import os
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.embeddings import XinferenceEmbeddings
from langchain_community.vectorstores.faiss import FAISS
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_retrieval_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 1. 加載文本并切片
loader = DirectoryLoader("docs", glob="**/*.txt") # 你的文檔目錄
docs = loader.load()
splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=30)
chunks = splitter.split_documents(docs)
# 2. Embedding
embedding = XinferenceEmbeddings(
server_url="http://127.0.0.1:9997",
model_uid="bge-large-zh-v1.5" # 這里填你在 Xinference 加載的 embedding 模型的 uid
)
# 3. 建索引(FAISS)
vectorstore = FAISS.from_documents(chunks, embedding)
vectorstore.save_local('GPT5')
# 4. 構(gòu)造 retriever(as_retriever)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 5. LLM + combine chain(stuff)
llm = ChatOpenAI(
temperature=0,
model="glm-4.5",
openai_api_key=os.getenv("ZAI_API_KEY"),
openai_api_base="https://open.bigmodel.cn/api/paas/v4/"
) # 或本地 LLM 封裝
prompt_template = "根據(jù)下面文檔回答問題:\n\n{context}\n\n問題:{input}\n"
prompt = ChatPromptTemplate.from_template(prompt_template)
combine_chain = create_stuff_documents_chain(llm, prompt)
# 6. 組合成 retrieval chain
retrieval_chain = create_retrieval_chain(retriever=retriever,
combine_docs_chain=combine_chain)
# 7. 調(diào)用
resp = retrieval_chain.invoke({"input": "GPT-5有什么特點(diǎn)?"})
print(resp["answer"])
說明/要點(diǎn):
- FAISS.from_documents 會調(diào)用 Embeddings,把 chunks 向量化并建立索引(可保存/load)。
-
retriever = vectorstore.as_retriever(...) 把向量存儲包裝成 Retriever,可直接給 create_retrieval_chain 使用。
image.png
示例 B:Chroma 快速原型
import os
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.embeddings import XinferenceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_retrieval_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
loader = DirectoryLoader("docs", glob="**/*.txt")
docs = loader.load()
splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=40)
chunks = splitter.split_documents(docs)
emb = XinferenceEmbeddings(
server_url="http://127.0.0.1:9997",
model_uid="bge-large-zh-v1.5" # 這里填你在 Xinference 加載的 embedding 模型的 uid
)
db = Chroma.from_documents(chunks, embedding=emb)
retriever = db.as_retriever(search_kwargs={"k": 1})
llm = ChatOpenAI(
temperature=0,
model="glm-4.5",
openai_api_key=os.getenv("ZAI_API_KEY"),
openai_api_base="https://open.bigmodel.cn/api/paas/v4/"
)
prompt = ChatPromptTemplate.from_template(
"請基于下面上下文回答問題:\n\n{context}\n\n問題:{input}\n")
combine_chain = create_stuff_documents_chain(llm, prompt)
chain = create_retrieval_chain(retriever=retriever, combine_docs_chain=combine_chain)
print(chain.invoke({"input": "一句話總結(jié)GPT5的特點(diǎn)!"})["answer"])
說明:
-
Chroma 做為一體化 DB,使用體驗(yàn)更簡單,適合迭代。
image.png
五、實(shí)現(xiàn)細(xì)節(jié)與調(diào)優(yōu)要點(diǎn)(工程級)
文本切分(chunking)
切分策略影響召回 vs 上下文連貫性。常見做法:chunk_size=300800,overlap=50200。
先把長文檔分段并摘要,再存入向量庫,這樣能減少 Token 消耗。向量歸一化(normalize)
當(dāng)使用余弦相似度時,常將向量 L2 歸一化,檢索時只需計(jì)算 dot product。某些 VectorStore 提供 normalize_L2 參數(shù)(FAISS 支持)。距離度量
FAISS 支持歐氏(L2)/內(nèi)積(dot)等;Chroma 默認(rèn)用余弦/內(nèi)積。根據(jù) embedding 決定(embedding 是否已歸一化)。索引類型與性能(FAISS)
常見索引:IndexFlatL2(精確);IVF + PQ(大規(guī)模壓縮);HNSW(快速近鄰)。選擇取決于吞吐、內(nèi)存、精度需求??稍趧?chuàng)建時通過 FAISS API 參數(shù)配置。Metadata 過濾
如果需要基于 metadata 做篩選(例如按 source、date),vectorstores / retrievers 常支持 filter 參數(shù);在調(diào)用 retriever.get_relevant_documents 時可傳 filters。檢查具體 VectorStore API(Chroma/FAISS wrapper 支持程度不同)。持久化 / 重建索引
FAISS 索引與 docstore 需分開保存(FAISS.save_local / FAISS.load_local);Chroma 提供內(nèi)置持久化方式。務(wù)必做好 embedding 版本與索引版本的同步策略。語義質(zhì)量監(jiān)測
使用小集的查詢做 A/B(不同 embedding / chunking / index)評測。監(jiān)控召回率、平均相似度分布、用戶反饋。
六、如何自定義 Retriever(高級用法)
如果內(nèi)置 retriever 不滿足你的需求(例如要做多階段檢索、語境壓縮、檢索融合多個 index),可以繼承 BaseRetriever:
from langchain_core.retrievers import BaseRetriever
from langchain_core.documents import Document
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.vectorstores import VectorStore
class MyCustomRetriever(BaseRetriever):
vector_store: VectorStore
k: int
def _get_relevant_documents(
self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
) -> list[Document]:
# 可以先做 query expansion / rerank / metadata filter
docs = self.vector_store.similarity_search(query, k=self.k)
# 做二次排序或裁剪
docs = docs[:2]
return docs
retriever = MyCustomRetriever(vector_store=db, k=4)

自定義 retriever 的好處:可以把 檢索、排序和壓縮 一步完成,直接把處理干凈的文檔交給 Chain。官方有自定義檢索器的教程。
七、實(shí)操常見問題與排錯
- 檢索到的是無關(guān)片段:檢查 embedding model 是否適配你的語言域、chunking 是否過碎或過大。
- 向量大小不匹配 / 報(bào)錯:確認(rèn)索引創(chuàng)建時的 embedding 維度與檢索時一致(避免模型升級而沒重建索引)。
- metadata 過濾不起作用:不同 VectorStore 對 filter 支持度不同(Chroma 支持較好),務(wù)必查看具體實(shí)現(xiàn)文檔。
八、快速參考代碼片段(保存/加載 FAISS)
from langchain_community.vectorstores.faiss import FAISS
# 3. 建索引(FAISS)
if os.path.exists("GPT5"):
vectorstore = FAISS.load_local("GPT5", embeddings=embedding,
allow_dangerous_deserialization=True)
else:
vectorstore = FAISS.from_documents(chunks, embedding)
vectorstore.save_local('GPT5')
九、小結(jié)
- 向量數(shù)據(jù)庫是 RAG 的底座,選擇 FAISS / Chroma 應(yīng)基于規(guī)模、性能與開發(fā)效率權(quán)衡。
- as_retriever() 是把 VectorStore 變?yōu)?Retriever 的快捷方式,便于在 LangChain Chain 中直接消費(fèi)。
- 生產(chǎn)系統(tǒng)需要關(guān)注切分、embedding 一致性、索引類型選擇、metadata 過濾與索引持久化等要點(diǎn)。

