LangChain 設計原理分析1? | 向量數據庫與 Retriever 機制

本文旨在理解向量檢索的實現(xiàn)機制、VectorStore 與 Retriever 在 LangChain 中的職責劃分,掌握 FAISS 與 Chroma 的實踐用法,以及如何把向量庫“作為 retriever” 接入檢索增強生成(RAG)流水線。

一、先把概念理清楚(最重要的點)

  • 向量數據庫 / VectorStore:把文本或片段映射為向量并存儲、索引,用于快速近鄰搜索(ANN)。在 LangChain 中常見實現(xiàn)包括 FAISS、Chroma、Milvus、Pinecone 等。
  • Embedding:把文本變?yōu)橄蛄康哪P停∣penAIEmbeddings / HuggingFaceEmbeddings / 本地 xinference 等)。Embedding 的語義質量直接決定檢索效果。
  • Retriever(檢索器):一個抽象層(BaseRetriever),負責“給我最相關的 documents”。VectorStore 通常提供 .as_retriever() 把自己包裝成 Retriever。LangChain 的 Retriever 遵循 Runnable 接口(支持 .invoke() / .ainvoke() 等)。

關鍵區(qū)別:VectorStore 負責存與查;Retriever 負責把“查詢”映射成文檔列表并做業(yè)務側包裝(例如加過濾、返回 score、metadata 過濾等)。

二、FAISS 與 Chroma 的快速比較(實踐視角)

FAISS(Facebook AI Similarity Search)

優(yōu)點:極高的性能與靈活的索引(IVF、PQ、HNSW 等),適合大規(guī)模離線索引與高吞吐檢索。實現(xiàn)上常用于內存/本地服務化場景。
典型場景:批量構建索引后離線部署、需要自定義索引參數與性能調優(yōu)時。

Chroma

優(yōu)點:開源、易用、開發(fā)者友好,提供嵌入 + 向量存儲的一體化體驗,適合中小規(guī)模快速迭代。LangChain 提供了 Chroma 的一鍵集成。
典型場景:快速原型、POC、輕量級應用或與 metadata 結合做過濾。

選擇建議:如果你要生產級、海量檢索(億級向量),優(yōu)先 FAISS / Milvus / ANN 專有云;若只是開發(fā)迭代或中小數據,Chroma 更省心。

三、as_retriever() 的作用與行為

as_retriever() 是 VectorStore 提供的便捷方法,把 VectorStore 包裝成 BaseRetriever(或可直接當作 Runnable 使用)。調用后你得到的 retriever 常帶有 search_kwargs(比如 k),并可被 LangChain 的 create_retrieval_chain / RetrievalQA 直接消費。官方 How-to 有示例。

要點:

  • vectorstore.as_retriever(search_kwargs={"k":4}) → 返回 top-k 文檔(含 metadata、score)。
  • Retriever 遵循 Runnable 接口,可直接 .invoke({"input": "..."}) 或在 Chain 中被自動調用。

四、實戰(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(小數據集演示)
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. 構造 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 = "根據下面文檔回答問題:\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. 調用
resp = retrieval_chain.invoke({"input": "GPT-5有什么特點?"})
print(resp["answer"])

說明/要點:

  • FAISS.from_documents 會調用 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": "一句話總結GPT5的特點!"})["answer"])

說明:

  • Chroma 做為一體化 DB,使用體驗更簡單,適合迭代。


    image.png

五、實現(xiàn)細節(jié)與調優(yōu)要點(工程級)

  1. 文本切分(chunking)
    切分策略影響召回 vs 上下文連貫性。常見做法:chunk_size=300800,overlap=50200。
    先把長文檔分段并摘要,再存入向量庫,這樣能減少 Token 消耗。

  2. 向量歸一化(normalize)
    當使用余弦相似度時,常將向量 L2 歸一化,檢索時只需計算 dot product。某些 VectorStore 提供 normalize_L2 參數(FAISS 支持)。

  3. 距離度量
    FAISS 支持歐氏(L2)/內積(dot)等;Chroma 默認用余弦/內積。根據 embedding 決定(embedding 是否已歸一化)。

  4. 索引類型與性能(FAISS)
    常見索引:IndexFlatL2(精確);IVF + PQ(大規(guī)模壓縮);HNSW(快速近鄰)。選擇取決于吞吐、內存、精度需求??稍趧?chuàng)建時通過 FAISS API 參數配置。

  5. Metadata 過濾
    如果需要基于 metadata 做篩選(例如按 source、date),vectorstores / retrievers 常支持 filter 參數;在調用 retriever.get_relevant_documents 時可傳 filters。檢查具體 VectorStore API(Chroma/FAISS wrapper 支持程度不同)。

  6. 持久化 / 重建索引
    FAISS 索引與 docstore 需分開保存(FAISS.save_local / FAISS.load_local);Chroma 提供內置持久化方式。務必做好 embedding 版本與索引版本的同步策略。

  7. 語義質量監(jiān)測
    使用小集的查詢做 A/B(不同 embedding / chunking / index)評測。監(jiān)控召回率、平均相似度分布、用戶反饋。

六、如何自定義 Retriever(高級用法)

如果內置 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)
image.png

自定義 retriever 的好處:可以把 檢索、排序和壓縮 一步完成,直接把處理干凈的文檔交給 Chain。官方有自定義檢索器的教程。

七、實操常見問題與排錯

  • 檢索到的是無關片段:檢查 embedding model 是否適配你的語言域、chunking 是否過碎或過大。
  • 向量大小不匹配 / 報錯:確認索引創(chuàng)建時的 embedding 維度與檢索時一致(避免模型升級而沒重建索引)。
  • metadata 過濾不起作用:不同 VectorStore 對 filter 支持度不同(Chroma 支持較好),務必查看具體實現(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')

九、小結

  • 向量數據庫是 RAG 的底座,選擇 FAISS / Chroma 應基于規(guī)模、性能與開發(fā)效率權衡。
  • as_retriever() 是把 VectorStore 變?yōu)?Retriever 的快捷方式,便于在 LangChain Chain 中直接消費。
  • 生產系統(tǒng)需要關注切分、embedding 一致性、索引類型選擇、metadata 過濾與索引持久化等要點。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容