5.1 为什么需要 RAG
RAG(Retrieval-Augmented Generation,检索增强生成)解决了大语言模型的几个核心问题:
❌ LLM 的局限性
- 知识截止:模型知识有截止日期,无法获取最新信息
- 幻觉问题:可能生成看似合理但错误的内容
- 私有知识:无法访问企业内部文档、私人数据
- 资源限制:无法将大量知识塞进有限的上下文
✅ RAG 的优势
- 最新信息:可接入实时数据源
- 减少幻觉:答案基于检索到的真实文档
- 私有知识:支持企业文档、个人资料
- 可追溯:答案可追溯到具体来源
图 5-1:RAG 解决的核心问题
5.2 RAG 工作流程
RAG 系统分为两个阶段:索引阶段(离线)和查询阶段(在线)。
图 5-2:RAG 完整工作流程
5.3 文档加载
LangChain 提供了丰富的文档加载器,支持各种格式:
| 类型 | 加载器 | 说明 |
|---|---|---|
PyPDFLoader |
PDF 文件 | |
| Word | Docx2txtLoader |
Word 文档 |
| Markdown | UnstructuredMarkdownLoader |
Markdown 文件 |
| CSV | CSVLoader |
CSV 文件 |
| 网页 | WebBaseLoader |
网页内容 |
| 目录 | DirectoryLoader |
批量加载文件夹 |
5.3.1 基础加载示例
python
document_loaders.py
# 安装依赖
# pip install langchain langchain-community pypdf python-docx
# PDF 加载
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("document.pdf")
pages = loader.load()
# pages 是 Document 对象列表
for page in pages:
print(page.page_content) # 页面文本
print(page.metadata) # 元数据(页码等)
# Word 文档
from langchain_community.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("document.docx")
docs = loader.load()
# 网页内容
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://python.langchain.com/docs/")
docs = loader.load()
# 批量加载目录
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader(
"./documents/",
glob="**/*.pdf", # 只加载 PDF
loader_cls=PyPDFLoader
)
docs = loader.load()
5.4 文本分割
将长文档分割成小的文本块(chunks),这是 RAG 的关键步骤。
5.4.1 基础分割器
python
text_splitting.py
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 创建分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块的最大字符数
chunk_overlap=50, # 块之间的重叠字符数
length_function=len, # 计算长度的函数
separators=["\n\n", "\n", "。", "!", "?", " ", ""], # 分隔符优先级
)
# 分割文档
chunks = text_splitter.split_documents(docs)
print(f"生成了 {len(chunks)} 个文本块")
for i, chunk in enumerate(chunks[:3]):
print(f"块 {i+1}: {chunk.page_content[:100]}...")
5.4.2 Token 感知的分割
根据 token 数量分割(更准确):
python
token_splitting.py
from langchain.text_splitter import TokenTextSplitter
# 按 token 分割
text_splitter = TokenTextSplitter(
chunk_size=500, # 最大 token 数
chunk_overlap=50, # 重叠 token 数
)
chunks = text_splitter.split_documents(docs)
💡 chunk_size 选择建议
- 100-300 tokens:适合简单问答
- 500-1000 tokens:适合大多数场景(推荐)
- 1000+ tokens:适合需要更多上下文的复杂任务
5.4.3 Markdown 分割
保持 Markdown 结构:
python
markdown_splitting.py
from langchain.text_splitter import MarkdownTextSplitter
splitter = MarkdownTextSplitter(
chunk_size=500,
chunk_overlap=50
)
# 分割 Markdown
chunks = splitter.split_text(markdown_content)
# 或者直接分割文档
splitter = MarkdownTextSplitter()
chunks = splitter.split_documents(markdown_docs)
5.5 向量化与嵌入
将文本转换为向量(embeddings),用于语义搜索。
5.5.1 使用 OpenAI Embeddings
python
embeddings.py
from langchain_openai import OpenAIEmbeddings
# 创建嵌入模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small" # 或 "text-embedding-3-large"
)
# 单个文本嵌入
vector = embeddings.embed_query("你好,世界")
print(f"向量维度: {len(vector)}")
# 批量文本嵌入
texts = ["第一个文本", "第二个文本", "第三个文本"]
vectors = embeddings.embed_documents(texts)
print(f"生成了 {len(vectors)} 个向量")
5.5.2 其他嵌入提供商
| 提供商 | 集成 | 模型 |
|---|---|---|
| OpenAI | langchain-openai |
text-embedding-3-small/large |
langchain-google-vertexai |
text-embedding-004 | |
| Cohere | langchain-cohere |
embed-multilingual-v3.0 |
| HuggingFace | langchain-huggingface |
sentence-transformers |
| 本地 | langchain-community |
nomic-embed-text |
python
local_embeddings.py
# 使用本地嵌入模型
from langchain_community.embeddings import OllamaEmbeddings
embeddings = OllamaEmbeddings(
model="nomic-embed-text" # 或 "mxbai-embed-large"
)
# 使用 Cohere
from langchain_cohere import CohereEmbeddings
embeddings = CohereEmbeddings(
model="embed-multilingual-v3.0",
cohere_api_key="your-api-key"
)
5.6 向量存储
向量数据库用于存储和检索嵌入向量。
| 向量数据库 | 特点 | 适用场景 |
|---|---|---|
| FAISS | 快速、轻量、本地 | 小规模原型 |
| Chroma | 简单易用、嵌入式 | 开发测试 |
| Pinecone | 云原生、可扩展 | 生产环境 |
| Weaviate | 开源、混合搜索 | 生产环境 |
| Milvus | 高性能、大规模 | 大规模部署 |
5.6.1 FAISS 本地存储
python
faiss.py
# pip install faiss-cpu (或 faiss-gpu)
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# 创建向量存储
vectorstore = FAISS.from_documents(
documents=chunks, # 文本块
embedding=OpenAIEmbeddings() # 嵌入模型
)
# 保存到本地
vectorstore.save_local("faiss_index")
# 加载
loaded_vectorstore = FAISS.load_local(
"faiss_index",
OpenAIEmbeddings(),
allow_dangerous_deserialization=True # 仅在信任的来源时使用
)
5.6.2 Chroma
python
chroma.py
# pip install chromadb
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=OpenAIEmbeddings(),
persist_directory="./chroma_db" # 持久化目录
)
# 保存
vectorstore.persist()
# 加载
client = chromadb.PersistentClient(path="./chroma_db")
vectorstore = Chroma(
client=client,
embedding_function=OpenAIEmbeddings()
)
5.7 检索与问答
5.7.1 基础相似性检索
python
retrieval.py
# 相似性搜索
results = vectorstore.similarity_search(
query="LangChain的Agent是什么?",
k=3 # 返回最相似的 3 个文档块
)
for doc in results:
print(doc.page_content)
print("---")
# 带分数的搜索
results_with_scores = vectorstore.similarity_search_with_score(
query="什么是LangChain?",
k=3
)
for doc, score in results_with_scores:
print(f"分数: {score:.4f}")
print(f"内容: {doc.page_content[:200]}")
print("---")
5.7.2 构建 RAG 问答链
python
rag_chain.py
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import ChatPromptTemplate
# 初始化模型
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 创建检索器
retriever = vectorstore.as_retriever(
search_kwargs={"k": 3} # 检索 3 个文档块
)
# 创建 QA 链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 将检索结果拼接到 prompt
retriever=retriever,
return_source_documents=True # 返回源文档
)
# 问答
result = qa_chain.invoke({"query": "LangChain的Agent是什么?"})
print(result["result"])
print("\n来源文档:")
for doc in result["source_documents"]:
print(f"- {doc.page_content[:100]}...")
5.7.3 LCEL 方式构建 RAG
python
rag_lcel.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 创建检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# RAG 提示模板
RAG_PROMPT = ChatPromptTemplate.from_template("""根据以下上下文回答问题。如果上下文中没有相关信息,请说明不知道。
上下文:
{context}
问题:{question}
回答:""")
# 构建 RAG 链
rag_chain = (
{"context": retriever | (lambda docs: "\n\n".join([d.page_content for d in docs])), "question": RunnablePassthrough()}
| RAG_PROMPT
| llm
| StrOutputParser()
)
# 调用
result = rag_chain.invoke("LangChain的Agent是什么?")
print(result)
图 5-3:RAG 链的执行过程
5.8 进阶优化
5.8.1 查询压缩(Query Compression)
将对话历史压缩成独立的查询:
python
query_compression.py
from langchain.retrievers.multi_query import MultiQueryRetriever
# 多查询检索器 - 生成多个查询角度
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm,
include_original=True
)
# 会生成多个查询,如:
# - 原始查询: "Agent是什么?"
# - 衍生查询1: "LangChain中Agent的作用"
# - 衍生查询2: "如何使用Agent"
# 最终综合多个查询的结果
5.8.2 上下文压缩(Contextual Compression)
压缩检索结果,只保留相关内容:
python
contextual_compression.py
from langchain.retrievers import ContextualCompressionRetriever
from langchain_community.document_transformers import EmbeddingsFilter
# 嵌入过滤器 - 基于相似度过滤
embeddings_filter = EmbeddingsFilter(
embeddings=embeddings,
similarity_threshold=0.8 # 只保留相似度 > 0.8 的内容
)
# 上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_retriever=vectorstore.as_retriever(),
document_compressor=embeddings_filter
)
# 使用压缩检索器
results = compression_retriever.invoke("LangChain的Agent")
print(results)
5.8.3 混合搜索
结合向量搜索和关键词搜索:
python
hybrid_search.py
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
# 关键词搜索
bm25_retriever = BM25Retriever.from_texts(
texts=[doc.page_content for doc in chunks]
)
bm25_retriever.k = 2
# 向量搜索
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 组合检索器
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.5, 0.5] # 各占一半
)
# 使用组合检索器
results = ensemble_retriever.invoke("LangChain Agent")
print(results)
5.8.4 重排序(Re-ranking)
对检索结果进行重排序,提高相关性:
python
reranking.py
# 使用 Cohere 进行重排序
from langchain_community.document_compressors import CohereRerank
compressor = CohereRerank(
cohere_api_key="your-key",
top_n=5 # 返回前 5 个
)
from langchain.retrievers import ContextualCompressionRetriever
compression_retriever = ContextualCompressionRetriever(
base_retriever=vectorstore.as_retriever(),
document_compressor=compressor
)
5.9 本章小结
📝 RAG 完整流程
索引阶段:文档 → 加载 → 分割 → 嵌入 → 存储
查询阶段:问题 → 嵌入 → 检索 → 构建上下文 → LLM 生成
🔧 关键组件
| 组件 | 作用 | 常用选项 |
|---|---|---|
| DocumentLoader | 加载各种格式文档 | PyPDFLoader, WebBaseLoader |
| TextSplitter | 文本分割 | RecursiveCharacterTextSplitter |
| Embeddings | 文本向量化 | OpenAI, Cohere, HuggingFace |
| VectorStore | 向量存储 | FAISS, Chroma, Pinecone |
| Retriever | 检索相关文档 | similarity_search, BM25 |
🚀 接下来学什么?
在下一章中,我们将学习 对话系统实战,通过完整的项目案例将所学知识串联起来。