本文将拆解检索增强生成(Retrieval-Augmented Generation)的核心组成(Retriever、Embedding、ChainType),理解 LangChain 是如何把"检索 + 生成"这两部分安全、可扩展地拼接起来的。
一、核心概念快速回顾(为什么要 RAG?)
RAG 把检索(retrieval)和生成(generation)结合起来:先从知识库检索出相关文档片段,再把这些片段连同用户问题一并送给 LLM 做"基于上下文"的生成。这样既能显著提高生成回答的事实性,又能保持模型生成的自然语言能力。
LangChain 把这个流程封装为一套可组合的 Chain(链)与 Retriever/VectorStore 抽象,使得你能用不同的向量库、Embedding 模型、合并策略(chain_type)来搭建自己的 RAG 系统。关于 Retriever 的接口与职责,官方文档有明确说明:实现 _get_relevant_documents(query: str)
即可。([LangChain][1], [api.python.langchain.com][2])
二、关键组件与职责(源码角度)
1. Retriever(检索器)
- 职责 :接收自然语言查询,返回
Document
列表(通常按相关度排序)。 - 接口/约束 :LangChain 要求自定义 retriever 实现
_get_relevant_documents(query: str)
(同步)或_aget_relevant_documents
(异步)。这使得 Retriever 可以基于任意后端实现(向量库、数据库、网络 API)。([api.python.langchain.com][2])
工程意义:Retriever 将后端检索逻辑与上层 RAG 流程解耦;上层只关心"给我最相关的文档",不需关心索引实现细节。
2. Embedding(向量化)
- 职责:把文本(文档段、query)转换为向量,供向量检索使用。
- 注意点 :Embedding 的语义空间、维度和文本切分策略直接影响检索质量;要保证对 query 与文档使用相同的 embedding 模型/参数。常见实现:OpenAIEmbeddings、local embedding 模型、sentence-transformers 等。向量库(FAISS / Pinecone / Milvus / Weaviate 等)通常提供
from_documents
或add_documents
的便捷接口。([LangChain][3])
3. ChainType(合并策略)
LangChain 提供多种把检索到的文档送入 LLM 的策略(chain_type
),主要有:
- stuff:把所有检索到的片段直接拼接到 prompt 中(简单但易触发 token 限制)。
- map_reduce:先对每片段分别生成小答案,再把小答案汇总(降低单次上下文大小,适合长文本)。
- refine:先生成初始答案,再迭代用更多文档精炼答案(适合需要逐步打磨的场景)。
选择哪种策略取决于文档长度、token 预算和任务复杂度。关于迁移建议,LangChain 官方已经把老的 RetrievalQA
迁移到 create_retrieval_chain
/ 更现代的 LCEL 风格链上。([LangChain][4])
三、整体流程(代码级别的执行顺序)
典型 RAG 调用(高层伪流程):
- 用户发起
query
。 Retriever.get_relevant_documents(query)
→ 返回 top-kDocument
(每个 Document 含.page_content
与.metadata
)。- 按
chain_type
把这些文档拼成适合 LLM 的输入(如直接拼接、分块 map/reduce、或 refine 流程)。 - 把拼好的 prompt 交给 LLM(LLMChain / ChatModelChain 等)执行生成。
- 将 LLM 的回答和(可选的)source documents 一并返回。
在 LangChain 中,这条路径通常可通过 create_retrieval_chain(retriever, llm, chain_type=...)
构造并直接调用(或使用更细粒度的组合方式)。([LangChain][5])
四、可运行的 RAG 示例(需要 Embedding / Vectorstore / OpenAI 等)
说明:下面示例演示真实项目常用流程(使用 FAISS、OpenAIEmbeddings、ChatOpenAI)。如果你有 API Key 与依赖环境(faiss、openai),可直接运行。
- 准备文档
docs

- 代码
python
import os
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains.retrieval import create_retrieval_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
# 1. 加载文档并切分成片段
loader = DirectoryLoader("docs", glob="**/*.txt") # 你的文档目录
raw_docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
texts = splitter.split_documents(raw_docs)
# 2. 嵌入并构建向量索引(FAISS)
embedding_model_path = "BAAI/bge-small-zh-v1.5"
embeddings = HuggingFaceEmbeddings(
model_name=embedding_model_path,
model_kwargs={'device': 'cpu'}, # 如果有 GPU 可改为 'cuda'
encode_kwargs={'normalize_embeddings': True}
)
vectorstore = FAISS.from_documents(texts, embeddings)
# 3. 构建 Retriever
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 2})
# 4. LLM
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/"
)
# 5. Prompt + Combine Docs Chain
prompt = ChatPromptTemplate.from_template("""
你是一个问答助手,请基于以下上下文简要地使用纯文本回答用户问题:
<context>
{context}
</context>
问题: {input}
""")
combine_docs_chain = create_stuff_documents_chain(llm, prompt)
# 6. 创建 Retrieval Chain
chain = create_retrieval_chain(
retriever=retriever,
combine_docs_chain=combine_docs_chain
)
# 7. 调用
out = chain.invoke({"input": "GPT-5的多模态能力怎么样?"})
print("context:", out["context"])
print("answer:", out["answer"])
- 输出:

- 说明:上面流程中
FAISS.from_documents
会调用 embeddings,将文本转为向量并建立索引;retriever
负责返回 top k 文档;create_retrieval_chain
把这些文档合并传给 LLM。
五、最小本地复现实验(零外部依赖)
为了便于阅读源码思想与调试,我们给出一个纯 Python、本地可运行的最小实现,包含:
- 一个非常简单的
Embedding
(基于字符 hash); - 一个内存向量 store + 简单余弦相似度检索(实现为
BaseRetriever
子类); - 一个非常简单的 LLM 模拟器(
MockLLM
),把检索到的文档拼接并返回"基于文档的回答"。
这个版本不依赖 OpenAI / faiss 等,可以离线演示 RAG 的端到端流程。
python
from typing import List
import math
# LangChain 的 Document 类
from langchain.schema import Document
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.retrievers import BaseRetriever
# ---- 1) 最简单的 embedding(可替换为更复杂模型) ----
def simple_embedding(text: str, dim: int = 32) -> List[float]:
# 非语义,仅做示例:字符码累加后散列到向量
v = [0.0] * dim
for i, ch in enumerate(text):
v[i % dim] += ord(ch)
# 归一化
norm = math.sqrt(sum(x * x for x in v)) or 1.0
return [x / norm for x in v]
# ---- 2) 内存向量索引 ----
class InMemoryVectorStore:
def __init__(self):
self.docs: List[Document] = []
self.vectors: List[List[float]] = []
def add_documents(self, docs: List[Document]):
for d in docs:
self.docs.append(d)
self.vectors.append(simple_embedding(d.page_content))
def similarity_search(self, query: str, k: int = 3) -> List[Document]:
qv = simple_embedding(query)
scores = []
for idx, v in enumerate(self.vectors):
# cosine similarity
dot = sum(a * b for a, b in zip(qv, v))
scores.append((dot, idx))
scores.sort(reverse=True)
return [self.docs[i] for _, i in scores[:k]]
# ---- 3) 自定义 Retriever(继承 BaseRetriever) ----
class InMemoryRetriever(BaseRetriever):
store: InMemoryVectorStore
k: int = 3
def _get_relevant_documents(
self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
) -> list[Document]:
return self.store.similarity_search(query, k=self.k)
# ---- 4) Mock LLM:把检索到的文档拼出来并返回"回答" ----
class MockLLM:
def __init__(self):
pass
def answer(self, query: str, docs: List[Document]) -> str:
# 一个极其简单的回答函数:拼接 doc 摘要并回显
snippets = "\n---\n".join(d.page_content[:300] for d in docs)
return f"基于检索到的文档,我的回答是:\n{snippets}\n\n(以上为参考片段)"
# ---- 5) 演示数据与流程 ----
if __name__ == "__main__":
docs = [
Document(page_content="RAG 是检索增强生成,先检索相关片段,再由 LLM 生成答案。"),
Document(page_content="向量检索把文本映射为向量,用相似度来检索相关文档。"),
Document(
page_content="ChainType 决定如何把检索到的文档送入 LLM,例如 stuff/map_reduce/refine。"),
Document(page_content="FAISS 是常用的向量索引库,适合大规模相似度检索。"),
]
store = InMemoryVectorStore()
store.add_documents(docs)
retriever = InMemoryRetriever(store=store, k=2)
llm = MockLLM()
query = "什么是 RAG?"
retrieved = retriever._get_relevant_documents(query)
print("检索到文档:")
for i, d in enumerate(retrieved, 1):
print(i, "-", d.page_content)
answer = llm.answer(query, retrieved)
print("\nLLM 返回:")
print(answer)
输出:

说明 :上面示例把真实的 embedding/向量索引换成本地简化实现,但保留了 RAG 的关键构件:embedding → retriever → combine → LLM。这个实现便于理解检索与生成如何耦合。
六、实现细节与源码阅读要点(你在 LangChain 源码中该看的地方)
要深入理解 LangChain 的 RetrievalQA 实现,建议关注以下模块/函数:
langchain_core.retrievers.BaseRetriever
:检索器基类,查看_get_relevant_documents
的接口定义与文档。([api.python.langchain.com][6])langchain.vectorstores.*
(FAISS / Pinecone wrapper) :这些模块实现把向量数据库封装为可直用的 VectorStore,并提供as_retriever()
方法。([LangChain][3])langchain.chains.retrieval.create_retrieval_chain
:新的推荐工厂函数,了解combine_docs_chain
如何被传入与执行(stuff/map_reduce/refine)。- 迁移说明 / 文档页 :
RetrievalQA
被标记为 deprecated,官方建议迁移到create_retrieval_chain
。读迁移指南能帮你理解新/旧实现的差异。
七、经验总结
- 向量化策略:切分(chunking)+ 合理 overlap 能提高检索召回率,但会增加索引量与成本;先做小规模 A/B 测试。
- 选择 ChainType :短文本、token 预算充足 →
stuff
;长文档或分布式计算 →map_reduce
或refine
。 - 返回来源 :若需要可追溯性(traceability),设置
return_source_documents=True
并把Document.metadata
一并返回给用户。 - 缓存与批处理:Embedding 调用可批量化,向量索引构建可离线化;检索热问可以做缓存。
- 检索器健康监控:关注向量距离分布、召回与命中率,定期重训练/重嵌入文档。
八、如何把检索结果安全地注入 Prompt(提示工程注意点)
- 不要把完整原文直接注入(若含敏感或 PII),先做去敏感化处理或只注入摘要片段。
- 控制 prompt 长度 :在 chain_type 为
stuff
时,优先按相关性截断或采用滑窗切分。 - 提供来源给用户 :在答案末尾附上
source
列表以便核查与归因。
九、结语
本文从概念、源码接口、实现细节到运行示例,完整拆解了 RetrievalQA(RAG)在 LangChain 中的实现要点。你现在应该能够:
- 理解 Retriever / Embedding / ChainType 各自职责;
- 在本地或云端构建简易 RAG 流程;
- 选取适合的合并策略并调优 token 使用与检索策略。
接下来我们将深入讲解 FAISS / Pinecone / Milvus 等向量数据库的工作原理、索引构建策略、向量压缩/近邻查找算法(IVF / HNSW / PQ)与在 LangChain 中的集成方案。