📚 什么是 RAG?

RAG(Retrieval Augmented Generation,检索增强生成)是一种将信息检索大语言模型生成相结合的技术,用于构建能够回答特定领域问题的 AI 应用。

💡 核心思想

RAG 通过在生成答案之前先从知识库中检索相关信息,让 LLM 能够基于最新、最准确的数据生成回答, 而不仅仅依赖训练时的知识。这解决了 LLM 的两大问题:

  • 知识过时:LLM 的知识截止于训练时间
  • 幻觉问题:LLM 可能生成不准确的信息

🏗️ RAG 系统架构

graph TB subgraph "离线阶段(索引)" A[原始文档] --> B[Document Loader] B --> C[Text Splitter] C --> D[文档块 Chunks] D --> E[Embeddings 模型] E --> F[Vector Store] end subgraph "在线阶段(查询)" G[用户问题] --> H[Embeddings 模型] H --> I[查询向量] I --> F F --> J[检索相关文档] J --> K[LLM + Prompt] G --> K K --> L[生成答案] end style A fill:#e1f5ff style G fill:#e1f5ff style L fill:#c8e6c9 style F fill:#fff9c4

RAG 系统分为两个主要阶段:

  • 离线阶段(索引):准备知识库,将文档向量化并存储
  • 在线阶段(查询):接收用户问题,检索相关信息,生成答案

⚖️ 两种实现方式对比

特性 Agentic RAG(推荐) Two-Step Chain
工作方式 Agent 自主决定何时检索 固定先检索后生成
灵活性 ✅ 高 - 可多次检索 ❌ 低 - 仅检索一次
查询优化 ✅ 可根据上下文优化查询 ❌ 直接使用用户问题
成本 ⚠️ 较高(每次检索2个LLM调用) ✅ 较低(1个LLM调用)
延迟 ⚠️ 较高 ✅ 较低
适用场景 复杂问题、多轮对话 简单问答、快速响应
✅ 推荐选择

对于大多数企业应用,推荐使用 Agentic RAG,因为:

  • 能够处理复杂的多步问题
  • 可以根据需要多次检索信息
  • 更智能地理解用户意图
  • 支持多轮对话和上下文理解

🚀 完整实现步骤

第一步:环境准备

Bash
# 安装必要的包
pip install -U langchain langchain-openai langchain-chroma
pip install langchain-community langchain-text-splitters
pip install bs4  # 用于网页加载

第二步:文档加载(Load)

使用 Document Loaders 加载各种格式的文档:

Python 🟢 基础
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)} 字符")
📝 常用 Document Loaders
加载器 用途 导入路径
WebBaseLoader 网页内容 langchain_community.document_loaders
PyPDFLoader PDF 文件 langchain_community.document_loaders
CSVLoader CSV 文件 langchain_community.document_loaders
UnstructuredMarkdownLoader Markdown 文件 langchain_community.document_loaders

第三步:文本分割(Split)

将长文档切分成适合向量化的小块:

Python 🟡 中级
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)

将文本块转换为向量并存储到向量数据库:

Python 🟡 中级
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 使用:

Python 🟡 中级
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:

Python 🔴 高级
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 系统:

Python 🔴 高级 - 完整项目
"""
完整的 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. 检索优化

Python 🟡 中级
# 使用 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. 缓存策略

对于频繁访问的文档,使用缓存减少重复计算:

Python 🔴 高级
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: 如何处理多语言文档?

Python
# 使用支持多语言的 embeddings 模型
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
    model="text-embedding-3-large"  # 支持多语言
)

Q3: 向量数据库太大怎么办?

  • 定期清理过时文档
  • 使用更小的 chunk_size
  • 考虑使用分布式向量数据库(如 Pinecone, Qdrant)
  • 实施文档过期策略

Q4: 如何评估 RAG 系统质量?

关键指标:

  • 检索准确率:检索到的文档是否相关
  • 答案质量:生成的答案是否准确
  • 响应时间:端到端延迟
  • 成本:API 调用费用

🎓 进阶话题

混合检索(Hybrid Search)

结合关键词搜索和语义搜索,提高检索质量:

Python 🔴 高级
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(重排序)

使用专门的模型对检索结果重新排序:

Python 🔴 高级
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("用户问题")

📖 参考资源