跳转至

RAG架构设计

1. RAG概述

检索增强生成(Retrieval-Augmented Generation, RAG)通过在生成之前检索相关文档,为LLM提供上下文信息,解决知识截断、幻觉和领域知识不足的问题。

1.1 为什么需要RAG

问题 纯LLM RAG
知识时效性 限于训练数据截止日期 可访问最新信息
幻觉 容易编造事实 基于检索到的事实回答
领域知识 泛化但不深入 可接入专业知识库
可追溯性 无法追溯信息来源 可引用具体文档
成本 微调成本高 无需训练,更新知识库即可

1.2 RAG vs 微调

  • RAG: 适合知识密集型任务、需要最新信息、知识频繁更新
  • 微调: 适合调整模型行为/风格、特定领域的专业表达、需要深度理解的任务
  • RAG + 微调: 最佳实践是结合两者

2. RAG演进

2.1 Naive RAG

最基础的RAG实现:

用户查询 → 向量检索 → 拼接上下文 → LLM生成

局限性:

  • 检索质量依赖查询质量
  • 简单的向量相似度可能不准确
  • 无法处理需要多跳推理的问题
  • 上下文窗口有限

2.2 Advanced RAG

在Naive RAG基础上添加了预处理和后处理:

用户查询 → 查询改写 → 向量检索 → 重排序 → 上下文压缩 → LLM生成

改进:

  • 查询优化(改写、扩展、分解)
  • 检索优化(混合搜索、重排序)
  • 生成优化(上下文压缩、自我反思)

2.3 Modular RAG

将RAG拆分为可组合的模块,灵活编排:

[路由模块] → 决定处理策略
[检索模块] → 多源检索 + 融合
[重排序模块] → 相关性排序
[压缩模块] → 上下文精炼
[生成模块] → 基于优化上下文生成
[验证模块] → 检查生成质量

3. RAG Pipeline详解

3.1 文档加载 (Load)

from langchain.document_loaders import (
    PyPDFLoader, TextLoader, UnstructuredHTMLLoader,
    CSVLoader, DirectoryLoader
)

# 加载不同格式的文档
pdf_loader = PyPDFLoader("document.pdf")
html_loader = UnstructuredHTMLLoader("page.html")
csv_loader = CSVLoader("data.csv")

# 批量加载目录
dir_loader = DirectoryLoader(
    "./docs/", 
    glob="**/*.md",
    show_progress=True
)
documents = dir_loader.load()

支持的数据源: PDF、HTML、Markdown、CSV、JSON、数据库、API、网页爬取

3.2 文档分块 (Chunk)

固定大小分块

from langchain.text_splitter import CharacterTextSplitter

splitter = CharacterTextSplitter(
    chunk_size=1000,      # 每块大小
    chunk_overlap=200,    # 重叠区域
    separator="\n\n"      # 分隔符
)
chunks = splitter.split_documents(documents)

语义分块

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", "。", ".", " ", ""]  # 递归分隔
)

分块策略对比

策略 优点 缺点 适用场景
固定大小 简单、快速 可能截断语义 通用场景
语义分块 保持语义完整性 计算开销较大 文档结构不规则
递归分块 灵活、效果好 需要调参 大多数场景(推荐)
文档结构分块 利用文档结构 依赖文档格式 结构化文档(HTML、Markdown)
句子级分块 语义最完整 块太小,检索效率低 精确匹配场景

分块大小的选择:

  • 太小: 丢失上下文信息
  • 太大: 检索精度降低,可能超出上下文窗口
  • 经验值: 256-1024 tokens
  • 建议: 通过实验确定最优大小

3.3 嵌入 (Embed)

from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
vectors = embeddings.embed_documents([chunk.page_content for chunk in chunks])

详见 向量数据库实战 中的嵌入模型对比。

3.4 索引 (Index)

from langchain.vectorstores import Chroma

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

3.5 检索 (Retrieve)

# 基本语义检索
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)

# MMR检索(平衡相关性和多样性)
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.5}
)

docs = retriever.get_relevant_documents("什么是Transformer?")

3.6 重排序 (Rerank)

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

# 对检索结果重排序
pairs = [(query, doc.page_content) for doc in retrieved_docs]
scores = reranker.predict(pairs)

# 按分数排序
reranked = sorted(zip(retrieved_docs, scores), key=lambda x: x[1], reverse=True)

3.7 生成 (Generate)

from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(
    llm=ChatOpenAI(model="gpt-4"),
    chain_type="stuff",  # stuff / map_reduce / refine
    retriever=retriever,
    return_source_documents=True
)

result = qa_chain({"query": "什么是Transformer的自注意力机制?"})

3.8 RAG Pipeline全景

graph TD
    A[文档加载] --> B[文档分块]
    B --> C[向量嵌入]
    C --> D[向量索引]

    E[用户查询] --> F[查询改写]
    F --> G[向量检索]
    D --> G
    G --> H[重排序]
    H --> I[上下文构建]
    I --> J[LLM生成]
    J --> K[输出]

    F --> |HyDE| F1[生成假设文档]
    F1 --> G

    F --> |Multi-Query| F2[生成多个查询]
    F2 --> G

    style A fill:#e1f5fe
    style B fill:#e1f5fe
    style C fill:#e1f5fe
    style D fill:#e1f5fe
    style E fill:#fff3e0
    style J fill:#e8f5e9

4. 查询转换 (Query Transformation)

4.1 HyDE (Hypothetical Document Embeddings)

先让LLM生成一个假设性的答案文档,再用该文档的嵌入进行检索:

def hyde_retrieval(query, llm, embeddings, vectorstore):
    # 1. 生成假设文档
    hypothesis_prompt = f"请写一段可能回答以下问题的文字:\n{query}"
    hypothesis_doc = llm.predict(hypothesis_prompt)

    # 2. 用假设文档的嵌入检索
    hypothesis_embedding = embeddings.embed_query(hypothesis_doc)
    docs = vectorstore.similarity_search_by_vector(hypothesis_embedding, k=5)

    return docs

原理: 假设文档与真实答案的嵌入空间更接近,比直接用问题检索效果更好。

4.2 Multi-Query

将原始查询改写为多个不同角度的查询,合并检索结果:

def multi_query_retrieval(query, llm, retriever):
    # 生成多个查询变体
    prompt = f"""请从不同角度改写以下问题,生成3个相关但不同的查询:
    原始问题:{query}

    查询1:
    查询2:
    查询3:"""

    queries = llm.predict(prompt).split("\n")

    # 合并所有查询的检索结果
    all_docs = set()
    for q in queries:
        docs = retriever.get_relevant_documents(q)
        all_docs.update(docs)

    return list(all_docs)

4.3 查询分解

将复杂问题分解为子问题,分别检索后合并:

原始问题:Transformer和RNN在处理长序列时有什么区别?

子问题1:Transformer如何处理长序列?
子问题2:RNN如何处理长序列?
子问题3:Transformer和RNN的主要区别是什么?

5. 高级RAG模式

5.1 Self-RAG

在生成过程中动态决定是否需要检索:

1. 接收查询
2. 判断:是否需要检索?
   - 是 → 检索 → 评估检索结果相关性 → 生成 → 验证
   - 否 → 直接生成
3. 对生成结果进行自我验证

5.2 CRAG (Corrective RAG)

对检索结果进行评估和修正:

1. 检索文档
2. 评估文档相关性
   - 相关 → 使用
   - 不相关 → 丢弃 + 网络搜索补充
   - 部分相关 → 提取相关部分
3. 基于修正后的上下文生成

5.3 Graph RAG

结合知识图谱增强检索:

1. 构建知识图谱(实体+关系)
2. 向量检索 + 图谱遍历
3. 获取多跳关系信息
4. 合并向量检索和图谱结果
5. LLM生成

6. 总结

RAG架构的选择取决于具体需求:

  • 简单问答: Naive RAG即可
  • 企业级应用: Advanced RAG + 重排序 + 混合搜索
  • 复杂推理: Modular RAG + Self-RAG / CRAG
  • 知识图谱场景: Graph RAG

关键原则:

  1. 评估驱动: 建立评估基线再优化
  2. 渐进增强: 从简单开始,按需增加复杂度
  3. 端到端优化: 各环节联合优化而非孤立调整

参考资料

  • Lewis et al., "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks", 2020
  • Gao et al., "Retrieval-Augmented Generation for Large Language Models: A Survey", 2024
  • RAG增强记忆 — Agent中的RAG应用
  • 向量数据库实战 — 嵌入模型和向量数据库选择
  • RAG评估与优化 — RAG系统评估方法

评论 #