🔍 RAG(检索增强生成)详解
深入理解 RAG 技术原理,掌握文档加载、文本分割、向量存储和检索策略, 构建高质量的问答系统。
📚 什么是 RAG?
RAG(Retrieval Augmented Generation,检索增强生成)是一种将信息检索 与大语言模型生成相结合的技术,用于构建能够回答特定领域问题的 AI 应用。
RAG 通过在生成答案之前先从知识库中检索相关信息,让 LLM 能够基于最新、最准确的数据生成回答, 而不仅仅依赖训练时的知识。这解决了 LLM 的两大问题:
- 知识过时:LLM 的知识截止于训练时间
- 幻觉问题:LLM 可能生成不准确的信息
🏗️ RAG 系统架构
RAG 系统分为两个主要阶段:
- 离线阶段(索引):准备知识库,将文档向量化并存储
- 在线阶段(查询):接收用户问题,检索相关信息,生成答案
⚖️ 两种实现方式对比
| 特性 | Agentic RAG(推荐) | Two-Step Chain |
|---|---|---|
| 工作方式 | Agent 自主决定何时检索 | 固定先检索后生成 |
| 灵活性 | ✅ 高 - 可多次检索 | ❌ 低 - 仅检索一次 |
| 查询优化 | ✅ 可根据上下文优化查询 | ❌ 直接使用用户问题 |
| 成本 | ⚠️ 较高(每次检索2个LLM调用) | ✅ 较低(1个LLM调用) |
| 延迟 | ⚠️ 较高 | ✅ 较低 |
| 适用场景 | 复杂问题、多轮对话 | 简单问答、快速响应 |
对于大多数企业应用,推荐使用 Agentic RAG,因为:
- 能够处理复杂的多步问题
- 可以根据需要多次检索信息
- 更智能地理解用户意图
- 支持多轮对话和上下文理解
🚀 完整实现步骤
第一步:环境准备
# 安装必要的包
pip install -U langchain langchain-openai langchain-chroma
pip install langchain-community langchain-text-splitters
pip install bs4 # 用于网页加载
第二步:文档加载(Load)
使用 Document Loaders 加载各种格式的文档:
import bs4
from langchain_community.document_loaders import WebBaseLoader, PyPDFLoader, TextLoader
# 方式1:加载网页
web_loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content", "post-title", "post-header")
)
),
)
web_docs = web_loader.load()
# 方式2:加载 PDF
pdf_loader = PyPDFLoader("document.pdf")
pdf_docs = pdf_loader.load()
# 方式3:加载文本文件
text_loader = TextLoader("document.txt", encoding="utf-8")
text_docs = text_loader.load()
print(f"加载了 {len(web_docs)} 个文档")
print(f"第一个文档内容长度: {len(web_docs[0].page_content)} 字符")
| 加载器 | 用途 | 导入路径 |
|---|---|---|
WebBaseLoader |
网页内容 | langchain_community.document_loaders |
PyPDFLoader |
PDF 文件 | langchain_community.document_loaders |
CSVLoader |
CSV 文件 | langchain_community.document_loaders |
UnstructuredMarkdownLoader |
Markdown 文件 | langchain_community.document_loaders |
第三步:文本分割(Split)
将长文档切分成适合向量化的小块:
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块的字符数
chunk_overlap=200, # 块之间的重叠字符数
length_function=len, # 计算长度的函数
is_separator_regex=False,
)
# 分割文档
all_splits = text_splitter.split_documents(web_docs)
print(f"文档被分割成 {len(all_splits)} 个块")
print(f"第一个块长度: {len(all_splits[0].page_content)} 字符")
print(f"第一个块内容预览: {all_splits[0].page_content[:200]}...")
- RecursiveCharacterTextSplitter(推荐):递归地尝试多种分隔符
- CharacterTextSplitter:基于固定字符数分割
- TokenTextSplitter:基于 Token 数量分割
- MarkdownHeaderTextSplitter:基于 Markdown 标题分割
chunk_size 建议值:500-1500 字符
chunk_overlap 建议值:chunk_size 的 10-20%
第四步:向量化和存储(Store)
将文本块转换为向量并存储到向量数据库:
import os
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
# 设置 API Key
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# 初始化 Embeddings 模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 创建向量存储并添加文档
vector_store = Chroma.from_documents(
documents=all_splits,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化存储路径
)
print(f"向量存储创建完成,包含 {len(all_splits)} 个文档块")
Vector Stores 对比
| Vector Store | 特点 | 适用场景 |
|---|---|---|
| Chroma | 轻量级、易用、支持持久化 | 本地开发、原型验证、中小型应用 |
| FAISS | 高性能、内存存储、GPU 加速 | 大规模数据、需要极致性能 |
| Pinecone | 云托管、高可用、易扩展 | 生产环境、需要云服务 |
| Qdrant | 开源、功能强大、可自托管 | 企业级应用、需要自主控制 |
第五步:创建检索工具
使用 @tool 装饰器创建检索工具,供 Agent 使用:
from langchain.tools import tool
@tool(response_format="content_and_artifact")
def retrieve_context(query: str):
"""从知识库检索相关信息来回答用户问题。
Args:
query: 用户的查询问题
Returns:
检索到的相关文档内容和原始文档对象
"""
# 使用相似度搜索检索相关文档(返回最相关的 k=2 个文档)
retrieved_docs = vector_store.similarity_search(query, k=2)
# 格式化检索结果
serialized = "\n\n".join(
f"来源: {doc.metadata}\n内容: {doc.page_content}"
for doc in retrieved_docs
)
return serialized, retrieved_docs
# 测试检索工具
test_query = "什么是 Agent?"
result, docs = retrieve_context.invoke({"query": test_query})
print(f"检索结果:\n{result}")
第六步:构建 RAG Agent
使用 LangChain 1.0 的 create_agent() 函数创建 Agentic RAG:
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
# 初始化模型
model = init_chat_model("gpt-4", model_provider="openai")
# 定义系统提示词
system_prompt = """你是一个智能问答助手,可以访问知识库来回答问题。
使用检索工具 retrieve_context 来查找相关信息。当用户提问时:
1. 先使用检索工具查找相关信息
2. 基于检索到的信息生成准确的答案
3. 如果检索不到相关信息,诚实地告知用户
4. 在回答中引用信息来源
始终保持专业、准确和有帮助。"""
# 创建 Agent
agent = create_agent(
model=model,
tools=[retrieve_context],
system_prompt=system_prompt
)
# 使用 Agent 回答问题
response = agent.invoke({
"messages": [{"role": "user", "content": "请介绍一下 LangChain 中的 Agent"}]
})
print(response["messages"][-1].content)
💻 完整可运行示例
将以上步骤整合成一个完整的 RAG 系统:
"""
完整的 RAG 系统实现示例
适用于 LangChain 1.0
"""
import os
import bs4
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
# ==================== 配置 ====================
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# ==================== 第1步:加载文档 ====================
print("正在加载文档...")
loader = WebBaseLoader(
web_paths=("https://python.langchain.com/docs/tutorials/agents/",),
bs_kwargs=dict(parse_only=bs4.SoupStrainer(class_=("main",)))
)
docs = loader.load()
print(f"✓ 加载了 {len(docs)} 个文档")
# ==================== 第2步:分割文本 ====================
print("正在分割文本...")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
all_splits = text_splitter.split_documents(docs)
print(f"✓ 分割成 {len(all_splits)} 个文本块")
# ==================== 第3步:创建向量存储 ====================
print("正在创建向量存储...")
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=all_splits,
embedding=embeddings,
persist_directory="./chroma_langchain_docs"
)
print(f"✓ 向量存储创建完成")
# ==================== 第4步:创建检索工具 ====================
@tool(response_format="content_and_artifact")
def retrieve_context(query: str):
"""从 LangChain 文档中检索相关信息。"""
retrieved_docs = vector_store.similarity_search(query, k=3)
serialized = "\n\n".join(
f"【文档 {i+1}】\n{doc.page_content}"
for i, doc in enumerate(retrieved_docs)
)
return serialized, retrieved_docs
# ==================== 第5步:创建 RAG Agent ====================
print("正在创建 RAG Agent...")
model = init_chat_model("gpt-4o-mini", model_provider="openai")
system_prompt = """你是 LangChain 文档助手。使用 retrieve_context 工具查找相关信息。
回答要求:
- 先检索相关信息再回答
- 基于检索内容生成准确答案
- 如果信息不足,说明需要更多上下文
- 用中文回答,保持专业和友好"""
agent = create_agent(
model=model,
tools=[retrieve_context],
system_prompt=system_prompt
)
print("✓ RAG Agent 创建完成\n")
# ==================== 第6步:交互式问答 ====================
def ask_question(question: str):
"""向 RAG Agent 提问"""
print(f"\n{'='*60}")
print(f"问题: {question}")
print('='*60)
response = agent.invoke({
"messages": [{"role": "user", "content": question}]
})
answer = response["messages"][-1].content
print(f"\n回答:\n{answer}\n")
return answer
# 示例问答
if __name__ == "__main__":
questions = [
"什么是 LangChain Agent?",
"如何创建一个 Agent?",
"LangChain 支持哪些模型提供商?"
]
for q in questions:
ask_question(q)
完整的 RAG 示例项目代码位于:
projects/rag-example/main.py
包含 requirements.txt 和详细的 README 说明。
⚡ 性能优化技巧
1. Chunk Size 优化
根据应用场景选择合适的分块大小:
- 小块(300-500):精确匹配,适合问答
- 中块(800-1200):平衡性能,通用场景(推荐)
- 大块(1500-2000):保留上下文,适合摘要
2. 检索优化
# 使用 MMR(最大边际相关性)检索,避免结果重复
retrieved_docs = vector_store.max_marginal_relevance_search(
query="用户问题",
k=4, # 返回4个结果
fetch_k=20, # 先获取20个候选
lambda_mult=0.5 # 平衡相关性和多样性
)
# 使用相似度阈值过滤低质量结果
retrieved_docs = vector_store.similarity_search_with_score(
query="用户问题",
k=5
)
filtered_docs = [doc for doc, score in retrieved_docs if score > 0.7]
3. Embeddings 模型选择
| 模型 | 维度 | 性能 | 成本 | 推荐场景 |
|---|---|---|---|---|
| text-embedding-3-small | 1536 | 快速 | 低 | 通用应用(推荐) |
| text-embedding-3-large | 3072 | 最佳 | 高 | 高精度要求 |
| HuggingFace 模型 | 768+ | 中等 | 免费 | 预算有限 |
4. 缓存策略
对于频繁访问的文档,使用缓存减少重复计算:
from functools import lru_cache
@lru_cache(maxsize=100)
def cached_retrieve(query: str, k: int = 3):
"""缓存检索结果"""
return vector_store.similarity_search(query, k=k)
# 使用缓存的检索
results = cached_retrieve("什么是 Agent?")
❓ 常见问题与解决方案
Q1: 检索结果不准确怎么办?
- 优化 chunk_size 和 chunk_overlap
- 使用更好的 embeddings 模型
- 增加 k 值(检索更多文档)
- 改进查询重写(让 Agent 优化用户问题)
Q2: 如何处理多语言文档?
# 使用支持多语言的 embeddings 模型
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large" # 支持多语言
)
Q3: 向量数据库太大怎么办?
- 定期清理过时文档
- 使用更小的 chunk_size
- 考虑使用分布式向量数据库(如 Pinecone, Qdrant)
- 实施文档过期策略
Q4: 如何评估 RAG 系统质量?
关键指标:
- 检索准确率:检索到的文档是否相关
- 答案质量:生成的答案是否准确
- 响应时间:端到端延迟
- 成本:API 调用费用
🎓 进阶话题
混合检索(Hybrid Search)
结合关键词搜索和语义搜索,提高检索质量:
from langchain.retrievers import EnsembleRetriever
from langchain.retrievers import BM25Retriever
# 创建多个检索器
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 3})
bm25_retriever = BM25Retriever.from_documents(all_splits)
bm25_retriever.k = 3
# 组合检索器
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.6, 0.4] # 语义搜索60%,关键词搜索40%
)
# 使用混合检索
results = ensemble_retriever.get_relevant_documents("用户问题")
Re-ranking(重排序)
使用专门的模型对检索结果重新排序:
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
# 使用 Cohere 重排序模型
compressor = CohereRerank(model="rerank-english-v2.0")
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vector_retriever
)
# 获取重排序后的结果
compressed_docs = compression_retriever.get_relevant_documents("用户问题")