RAG 检索增强生成

面试官:「你们的 RAG 怎么做的?」

你:「先把文档切分,embedding 后存向量库,检索时找最相关的几段,喂给 LLM...」

面试官:「Embedding 用的什么模型?向量库用的哪个?」

你:「...」

面试官:「文档怎么切分的?检索效果怎么评估?」

🤔 这些细节才是项目深度的体现!这一章把 RAG 的每个环节都讲透,让你的项目不再浮于表面。

5.1 为什么需要 RAG

RAG(Retrieval-Augmented Generation,检索增强生成)解决了大语言模型的几个核心问题:

❌ LLM 的局限性

  • 知识截止:模型知识有截止日期,无法获取最新信息
  • 幻觉问题:可能生成看似合理但错误的内容
  • 私有知识:无法访问企业内部文档、私人数据
  • 资源限制:无法将大量知识塞进有限的上下文

✅ RAG 的优势

  • 最新信息:可接入实时数据源
  • 减少幻觉:答案基于检索到的真实文档
  • 私有知识:支持企业文档、个人资料
  • 可追溯:答案可追溯到具体来源
图 5-1:RAG 解决的核心问题
LLM 的痛点 ❌ 问:公司的年假政策是什么? 答:抱歉,我不知道,这是内部信息 ❌ 问:iPhone 16 什么时候发布? 答:据我所知是2024年9月...(幻觉) ❌ 问:你们公司的核心价值观? 答:我猜是创新、开放...(瞎编) + RAG RAG 解决方案 ✅ 检索:员工手册第15页 "年假:入职满1年享10天..." 答:根据员工手册,年假... ✅ 检索:苹果官网 2024年9月 "iPhone 16 于2024年9月9日发布" 答:iPhone 16 于2024年9月...

5.2 RAG 工作流程

RAG 系统分为两个阶段:索引阶段(离线)和查询阶段(在线)。

图 5-2:RAG 完整工作流程
索引阶段(离线) 📄 文档加载 ✂️ 文本分割 🔢 Embedding 🗄️ 向量数据库 文档 → 加载 → 分割 → Embedding → 存入向量数据库 这一步是离线完成的,只需做一次 查询阶段(在线) ❓ 用户问题 🔢 Embedding 🔍 检索 📝 上下文 🤖 LLM 从向量数据库检索相关文档 示例: "LangChain的Agent是什么?" → 向量化 → 检索相关文档 → 拼入Prompt → LLM生成回答 每次用户提问都会执行这个流程

5.3 文档加载

LangChain 提供了丰富的文档加载器,支持各种格式:

类型 加载器 说明
PDF 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
Google 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 链的执行过程
❓ "什么是RAG?" 🔍 Retriever 检索 📝 Context: "RAG是一种结合检索和生成的技术..." 🤖 LLM 生成 ✅ "RAG(检索增强生成)通过检索外部知识来增强 LLM 的回答..."

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
🚀 接下来学什么?

在下一章中,我们将学习 对话系统实战,通过完整的项目案例将所学知识串联起来。

关注我们