Skip to content

필사 모드: RAG:Retrieval-Augmented Generation 论文解析与生产架构

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

1. LLM 的 Hallucination 问题与 RAG 的诞生背景

Large Language Model(LLM)在海量文本数据上完成预训练,在自然语言理解和生成上展现出惊人的性能。然而 LLM 存在一个根本性的局限 — 那就是Hallucination(幻觉)问题。

Hallucination 指的是模型将并非事实的信息、当作事实一样自信地生成出来的现象。这个问题产生的根本原因如下。

  • 知识的静态特性:编码在 LLM 参数中的知识在训练时刻就已固定,无法反映训练之后发生的事件或更新过的信息。
  • 参数记忆的不完整性:即便有数十亿个参数,也不可能把世界上所有细节事实都准确地存储并复现出来。
  • 概率式生成方式:LLM 是以概率方式预测并生成下一个 token 的,因此可能生成统计上看似合理、但与事实不符的文本。
  • 无法追溯来源:生成的答案究竟源自哪份训练数据,是无法追溯的,这使得验证本身变得困难。

为克服这些局限而出现的方法,就是Retrieval-Augmented Generation(RAG)。RAG 的核心思路简单而强大 — 在 LLM 生成答案之前,先从外部知识库中检索(Retrieve)相关文档,再基于这些信息生成(Generate)答案。

由此可以获得以下好处。

  1. 基于事实的回答:以检索到的真实文档为依据生成答案,从而减少 Hallucination。
  2. 知识更新容易:只需更新外部数据库即可反映最新信息,无需重新训练模型。
  3. 可提供出处:可以把作为答案依据的文档一并呈现,提升透明度和可信度。
  4. 易于领域特化:只需对特定领域的文档建立索引,就能快速构建面向该领域的专用系统。

2. 原始 RAG 论文结构:Retriever + Generator

2020 年,Facebook AI Research(现 Meta AI)的 Patrick Lewis 等人发表的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(NeurIPS 2020),是首次将 RAG 这一概念正式化的核心研究。

2.1 论文的核心提案

Lewis 等人提出了一种通用的(可 fine-tuning 的)方法论,将预训练语言模型的Parametric Memory(参数内隐含的知识)与Non-Parametric Memory(外部文档索引中显式的知识)结合起来。具体而言,Parametric Memory 是预训练好的 seq2seq 模型(BART),Non-Parametric Memory 则是把整个 Wikipedia 构建成的 Dense Vector Index。

2.2 架构组成

RAG 模型的架构大体由两大组件构成。

Retriever(检索器)— p_eta(z|x)

给定输入查询 x 后,检索相关文档(passage)z 的组件。论文中使用 Dense Passage Retrieval(DPR)分别将查询和文档编码为 Dense Vector,再通过 Maximum Inner Product Search(MIPS)检索 top-k 相关文档。

Generator(生成器)— p_theta(y_i|x, z, y_{1:i-1})

把检索到的文档 z 和原始输入 x 一起作为上下文接收,生成最终输出 y 的组件。论文中使用 BART-large 作为 Generator。

2.3 两种 RAG 变体

论文根据检索文档的使用方式提出了两种变体。

RAG-Sequence

在生成整个输出序列时,一致地使用同一份检索到的文档。针对每份检索到的文档 z 生成完整序列后,再对各文档的概率做 marginalize。

p_RAG-Sequence(y|x) ≈ Σ_z p_eta(z|x) Π_i p_theta(y_i|x, z, y_{1:i-1})

RAG-Token

生成每个输出 token 时都可以参考不同的检索文档,在 token 层级对各文档的概率做 marginalize。

p_RAG-Token(y|x) ≈ Π_i Σ_z p_eta(z|x) p_theta(y_i|x, z, y_{1:i-1})

2.4 主要实验结果

RAG 模型在三个 Open-Domain QA 基准(Natural Questions、TriviaQA、WebQuestions)上,全面超越了既有的 parametric seq2seq 模型以及 task-specific 的 retrieve-and-extract 架构,取得了state-of-the-art性能。特别值得注意的是,相比纯 parametric 模型,RAG 模型生成的文本更加具体、多样、贴近事实


3. Dense Passage Retrieval(DPR)原理

在 RAG 的 Retriever 组件中扮演核心角色的,正是Dense Passage Retrieval(DPR)。这是 Karpukhin 等人在 2020 年 EMNLP 上发表的论文《Dense Passage Retrieval for Open-Domain Question Answering》中提出的方法。

3.1 传统 Sparse Retrieval 的局限

传统信息检索中,主要使用的是像BM25这样的 Sparse Retrieval 方法。BM25 基于 TF-IDF 进行关键词匹配,存在以下局限。

  • 词汇不一致(Lexical Mismatch):使用同义词或不同表达时无法找到相关文档。比如用「机器学习」提问,但文档里写的是「机器学习」的其他说法或英文缩写时,就会匹配失败。
  • 未反映语义相似性:只考虑词的出现频率,无法把握上下文含义。

3.2 DPR 的 Bi-Encoder 架构

DPR 采用Bi-Encoder(双编码器)架构,使用两个独立的 BERT-base 编码器分别将查询和文档转换为 Dense Vector。

- Query Encoder: E_Q(q) → d 维向量
- Passage Encoder: E_P(p) → d 维向量

相似度用两个向量的Inner Product(内积)来计算。

sim(q, p) = E_Q(q)^T · E_P(p)

这种架构的核心优势在于查询和文档的编码是相互独立的。文档编码可以离线预先完成,并索引进 FAISS 之类的 ANN(Approximate Nearest Neighbor)库中。检索时只需对查询做编码即可,因此即便面对数百万份文档,也能实现毫秒级检索。

3.3 训练方式

DPR 采用In-Batch Negatives策略训练——把同一批次内其他问题对应的正确 passage 用作 Negative Sample。此外还会把 BM25 检索到但并非正确答案的 passage 用作 Hard Negative,以提升训练效果。

3.4 性能

DPR 在 Top-20 Passage Retrieval Accuracy 上相较 BM25 取得了 9%~19% 的绝对性能提升。这说明只需有限数量的 query-passage 对,也能训练出高质量的 Dense Retriever。


4. Chunking 策略

在 RAG 系统中,把文档切分为大小合适的 Chunk,是直接影响检索质量的关键步骤。LangChain 提供了多种 Text Splitter,主要的 Chunking 策略如下。

4.1 Fixed-Size Chunking(固定大小分割)

最简单的方法,按指定的字符数(Character)或 token 数(Token)来分割文本。

from langchain_text_splitters import CharacterTextSplitter

text_splitter = CharacterTextSplitter(
    separator="\n\n",
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
)
docs = text_splitter.split_documents(documents)
  • 优点:实现简单,行为可预测。
  • 缺点:可能在句子中间或语义单元中间被切断。

chunk_overlap 参数在相邻 Chunk 之间保留重叠部分,以缓解上下文丢失的问题。

4.2 Recursive Character Splitting(递归式分割)

LangChain 最推荐的通用 Text Splitter,递归地依次应用多级分隔符,尽可能保留语义单元。

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
    length_function=len,
)
docs = text_splitter.split_documents(documents)

其运行原理如下。

  1. 首先尝试按 \n\n(段落分隔)分割。
  2. 若 Chunk 仍超过 chunk_size,则按 \n(换行)分割。
  3. 若仍然超出,则按 . (句子单位)分割。
  4. 最后手段是按空格或按字符逐个分割。

这种方式的核心在于优先尝试保留更大的语义单元,只有在必要时才降到更小的单元

4.3 Semantic Chunking(语义分割)

基于 Embedding 相似度检测语义发生变化的位置来分割,是最高级的策略。

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings

text_splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",
)
docs = text_splitter.split_documents(documents)

计算相邻句子之间的 Embedding Cosine Similarity,并将相似度骤降的位置设为 Chunk 边界。虽然可以生成语义上连贯一致的 Chunk,但会额外产生 Embedding 计算成本。

4.4 Chunking 策略选择指南

策略适用场景成本
Fixed-Size结构均匀的文档、快速原型开发
Recursive大部分通用场景(推荐的默认选项)
Semantic语义边界重要的文档、高质量要求

实践中通常会把 chunk_size 设在 500~1500 之间,chunk_overlap 设为 chunk_size 的 10%~20% 左右。最优值需要根据数据特性和使用场景通过实验来确定。


5. Embedding 模型选择

将 Chunk 转换为向量的 Embedding 模型的选择,是左右检索质量的重要决定。可以参考 MTEB(Massive Text Embedding Benchmark)排行榜来比较主要模型。

5.1 OpenAI text-embedding-3 系列

from langchain_openai import OpenAIEmbeddings

# 高性能模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")  # 3072 维

# 成本高效模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")  # 1536 维
  • MTEB 分数:text-embedding-3-large 约 64.6
  • 优点:仅通过 API 调用即可简单使用,提供稳定的质量。支持 Matryoshka Representation,即使降低维度性能损失也很小。
  • 缺点:会产生 API 费用,且数据会被发送到外部服务器。

5.2 Sentence-Transformers

from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)
  • 优点:开源,可在本地运行。以英语为主的场景下,有大量轻量快速的模型可选。all-MiniLM-L6-v2 只有 384 维,轻量的同时也能提供不错的性能。
  • 缺点:多语言支持有限,性能相较大规模模型可能偏低。

5.3 BGE(BAAI General Embedding)系列

from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cuda"},
    encode_kwargs={"normalize_embeddings": True},
)
  • MTEB 分数:BGE-M3 约 63.0
  • 优点:开源模型中处于顶尖水准的性能,支持超过 100 种语言,尤其适合包含中文、韩文在内的多语言 RAG。作为 Hybrid 模型,同时支持 Dense、Sparse、Multi-Vector Retrieval。
  • 缺点:模型体积较大,需要 GPU。

5.4 选择标准

标准推荐模型
快速原型开发OpenAI text-embedding-3-small
生产环境(质量优先)OpenAI text-embedding-3-large 或 Cohere embed-v4
生产环境(成本优先,自建)BGE-M3
中文/多语言BGE-M3
轻量/边缘环境all-MiniLM-L6-v2

核心原则是务必用自己的实际数据做基准测试。MTEB 分数是通用基准,特定领域的表现可能有所不同。


6. Vector Database 比较

存储 Embedding 向量并执行相似度检索的 Vector Database,是 RAG 系统基础设施的核心。下面比较几款主要的 Vector Database。

6.1 Chroma

from langchain_chroma import Chroma

vectorstore = Chroma.from_documents(
    documents=docs,
    embedding=embeddings,
    persist_directory="./chroma_db",
)
  • 类型:开源,Embedded(进程内)
  • 适用场景:本地开发、原型开发、小规模项目
  • 优点:安装简单(pip install chromadb),无需单独的服务器,直接在 Python 进程内运行。与 LangChain 的集成非常完善。
  • 局限:在大规模数据(数百万向量以上)下性能可能下降,生产级的可用性和可扩展性有限。

6.2 Pinecone

  • 类型:Managed SaaS(完全托管)
  • 适用场景:生产环境、希望把运维负担降到最低的团队
  • 优点:Serverless 架构,无需管理基础设施,提供多区域支持、高可用性和自动扩缩容,可扩展到数十亿向量规模。
  • 局限:相对成本较高,存在 Vendor Lock-in(供应商锁定)的顾虑,并非开源。

6.3 Weaviate

  • 类型:开源 + 托管云
  • 适用场景:Hybrid Search(向量 + 关键词)很重要的场景、需要灵活 schema 的场景
  • 优点:对 Native Hybrid Search 提供强力支持,具备 GraphQL API、模块化架构和自带的向量化模块。既是开源又有托管云选项,比较灵活。
  • 局限:存在一定学习曲线,配置可能相对复杂。

6.4 pgvector

CREATE EXTENSION vector;

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(1536)
);

-- 相似度检索
SELECT content, embedding <=> '[0.1, 0.2, ...]'::vector AS distance
FROM documents
ORDER BY distance
LIMIT 5;
  • 类型:PostgreSQL 扩展
  • 适用场景:已经在使用 PostgreSQL 的组织、不想额外增加基础设施的场景
  • 优点:可以直接沿用现有的 PostgreSQL 基础设施,SQL 与向量检索可在同一个数据库中完成,架构因此更简单。支持 HNSW、IVFFlat 索引。
  • 局限:在 1 亿向量以上规模时性能可能下降,功能相较专用 Vector Database 有限。

6.5 Milvus

  • 类型:开源,分布式架构
  • 适用场景:数十亿向量规模的大型系统、具备数据工程能力的团队
  • 优点:在工业级大规模向量检索中拥有经过验证的性能,支持多种索引类型(IVF、HNSW、DiskANN 等),并可使用 GPU 加速。也可通过 Zilliz Cloud 获得托管服务。
  • 局限:运维复杂度较高,集群搭建和管理需要专门知识。

6.6 比较汇总

DB类型最大规模Hybrid Search推荐场景
ChromaEmbedded~100 万有限原型开发、开发环境
PineconeManaged SaaS数十亿支持生产环境、运维最小化
Weaviate开源/云数亿强力支持以 Hybrid Search 为中心
pgvectorPostgreSQL 扩展~1 亿SQL 组合已有 PostgreSQL 基础设施
Milvus开源分布式数十亿支持大规模系统

7. Advanced RAG 模式

基础 RAG(Naive RAG)只是一个简单的「先检索再生成」流水线。在生产环境中,需要各种 Advanced RAG 模式来提升检索质量和生成质量。

7.1 Re-ranking

基础 RAG 中的初期检索(Retrieval)使用的是 Bi-Encoder 的向量相似度,速度快但精度可能不足。Re-ranking 是用Cross-Encoder对初期检索结果重新评估、以提升精度的模式。

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

# 加载 Cross-Encoder 模型
model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3")
compressor = CrossEncoderReranker(model=model, top_n=3)

# 配置 Re-ranking Retriever
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=vectorstore.as_retriever(search_kwargs={"k": 20}),
)

其运行流程如下。

  1. Bi-Encoder 快速检索出范围较广的候选文档(top-20)。
  2. Cross-Encoder 把查询和每个候选文档成对输入,直接打出相关性分数。
  3. 只把重新排序后的靠前文档(top-3)传给 Generator。

Cross-Encoder 比 Bi-Encoder 更精确,但需要对每个候选逐一推理,因此不适合大规模检索。所以标准做法是先用 Bi-Encoder 缩小候选范围,再用 Cross-Encoder 重新排序的两阶段流水线。

7.2 HyDE(Hypothetical Document Embeddings)

Gao 等人(2022)提出的 HyDE,是用来弥合查询与文档之间语义鸿沟(Semantic Gap)的一种模式。用户的问题通常简短而抽象,而包含答案的文档则冗长而具体。这种差异可能使得直接的向量相似度检索并非最优。

HyDE 的核心思路如下。

  1. 收到用户查询后,请 LLM「写一份能回答这个问题的假想文档」。
  2. 对生成的假想文档(Hypothetical Document)做 Embedding。这份假想文档内容可能与事实不符,但其格式和用词与真实相关文档相似。
  3. 用这个 Embedding 去检索真实文档。
from langchain.chains import HypotheticalDocumentEmbedder
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

llm = ChatOpenAI(model="gpt-4o-mini")
base_embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
    llm=llm,
    base_embeddings=base_embeddings,
    prompt_key="web_search",
)

# 用 HyDE Embedding 检索
results = vectorstore.similarity_search_by_vector(
    hyde_embeddings.embed_query("如何评估 RAG 系统的性能?")
)

Encoder 的 Dense Bottleneck 能起到过滤假想文档中 Hallucination 的作用,因此即便假想文档内容不准确,也能有效检索到相关的真实文档。

7.3 Self-RAG

Asai 等人(2023)提出的Self-RAG,是一种让 LLM 自行判断是否需要检索、并对生成结果进行自我批判性评估的模式。

Self-RAG 的核心机制是Reflection Token(反思 token)。

  • [Retrieve]:判断当前时刻是否需要外部检索。
  • [IsRel]:评估检索到的文档是否与问题相关。
  • [IsSup]:评估生成的回答是否得到了检索文档的支持。
  • [IsUse]:评估生成的回答整体上是否有用。

这些 Reflection Token 被加入模型词表中,像普通 token 一样参与训练,推理时由模型自主生成。在 7B、13B 参数规模下,Self-RAG 在 Open-Domain QA、Reasoning、Fact Verification 等任务上超越了 ChatGPT 和 retrieval-augmented Llama2-chat。

7.4 Corrective RAG(CRAG)

Yan 等人(2024)提出的CRAG,是一种主动评估并修正检索文档质量的模式。

CRAG 的核心组件如下。

  1. Retrieval Evaluator:由一个轻量模型将检索文档的相关性判断为 Correct、Incorrect、Ambiguous 三种。
  2. Knowledge Refinement:从检索文档中只提取核心信息、去除不必要的部分,采用 Decompose-then-Recompose 算法。
  3. Web Search Fallback:如果 Retrieval Evaluator 判定为 Incorrect,则不使用静态语料库,改用网络搜索来寻找更好的信息。
[Query][Retriever][Retrieval Evaluator]
              CorrectKnowledge RefinementGenerator
              IncorrectWeb SearchKnowledge RefinementGenerator
              Ambiguous → 结合两条路径 → Generator

这个模式的优势在于,即便在检索质量不佳的情况下,也能自动启用备用路径,生成稳健的响应。


8. Evaluation 指标:RAGAS 框架

为了系统性地评估 RAG 系统的性能,RAGAS(Retrieval Augmented Generation Assessment)框架被广泛使用。Es 等人(2023)提出的 RAGAS,提供了即便没有 Ground Truth 也能评估 RAG 流水线的自动化指标。

8.1 Faithfulness(忠实度)

衡量生成的答案在多大程度上忠实于检索到的 Context,是检测 Hallucination 的核心指标。

Faithfulness = (Context 支持的 Claim) / (全部 Claim)

其运行方式如下。

  1. LLM 从生成的答案中提取出各个 Claim(主张)。
  2. 判断每个 Claim 是否得到了所提供 Context 的支持(Support)。
  3. 计算被支持的 Claim 所占的比例。

数值介于 0~1 之间,越接近 1 表示答案越忠实于 Context。

8.2 Answer Relevance(答案相关性)

衡量生成的答案与原始问题之间的相关程度。

其运行方式如下。

  1. 从生成的答案反向生成问题(Reverse Engineering)。
  2. 计算生成的问题与原始问题之间的 Embedding 相似度。
  3. 平均相似度即为 Answer Relevance 分数。

这种方式间接衡量了答案是否切中问题的核心,以及是否包含了不必要的信息。

8.3 Context Recall(上下文召回率)

衡量检索到的 Context 中,包含了多少生成 Ground Truth 答案所需的信息。

Context Recall = (Context 中得到支持的 GT 句子数) / (GT 句子总数)

这是唯一需要 Ground Truth 的指标,直接评估 Retriever 的性能。

8.4 Context Precision(上下文精确度)

衡量检索到的 Context 中,真正相关的项目是否排在靠前的位置。相关文档在检索结果中越靠前,分数越高。

8.5 RAGAS 使用示例

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision,
)
from datasets import Dataset

# 构建评估数据集
eval_data = {
    "question": ["什么是 RAG?"],
    "answer": ["RAG 是检索增强生成,是 LLM 通过检索外部文档来生成答案的方法。"],
    "contexts": [["RAG(Retrieval-Augmented Generation)是一种检索外部知识、用于 LLM 生成的技术。"]],
    "ground_truth": ["RAG 是 Retrieval-Augmented Generation 的缩写,是一种通过从外部知识源检索相关信息来增强 LLM 响应生成的方法论。"],
}
dataset = Dataset.from_dict(eval_data)

# 执行评估
result = evaluate(
    dataset=dataset,
    metrics=[faithfulness, answer_relevancy, context_recall, context_precision],
)
print(result)

在生产环境中,建议把这些指标集成进 CI/CD 流水线,自动监控 RAG 系统的变更(Chunking 策略调整、模型更换等)对质量造成的影响。


9. LangChain + ChromaDB 实战实现示例

综合此前讨论的所有概念,下面完整实现一个基于 LangChain 和 ChromaDB 的实战 RAG 流水线。

9.1 环境配置与包安装

pip install langchain langchain-openai langchain-chroma langchain-community
pip install chromadb pypdf sentence-transformers

9.2 Document Loading 与 Chunking

import os
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载 PDF 文档
loader = DirectoryLoader(
    "./documents",
    glob="**/*.pdf",
    loader_cls=PyPDFLoader,
)
documents = loader.load()
print(f"已加载文档数: {len(documents)}")

# Recursive Character Splitting
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
    length_function=len,
)
splits = text_splitter.split_documents(documents)
print(f"生成的 Chunk 数: {len(splits)}")

9.3 Embedding 与 Vector Store 构建

from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

# Embedding 模型设置
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    openai_api_key=os.getenv("OPENAI_API_KEY"),
)

# 构建并持久化 ChromaDB Vector Store
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory="./chroma_db",
    collection_name="rag_collection",
)
print(f"Vector Store 中存储的文档数: {vectorstore._collection.count()}")

9.4 Retriever 配置

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

# MMR(Maximal Marginal Relevance)Retriever — 确保多样性
retriever_mmr = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 5,
        "fetch_k": 20,      # 初始检索候选数
        "lambda_mult": 0.7,  # 相关性(1.0)与多样性(0.0)之间的平衡
    },
)

9.5 RAG Chain 构建与执行

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# LLM 设置
llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    openai_api_key=os.getenv("OPENAI_API_KEY"),
)

# Prompt Template
prompt = ChatPromptTemplate.from_template("""
请根据以下 Context 回答问题。
如果 Context 中没有回答所需的信息,请回答"提供的文档中未找到相关信息。"

Context:
{context}

问题:{question}

回答:
""")

# Context 格式化函数
def format_docs(docs):
    return "\n\n---\n\n".join(
        f"[出处: {doc.metadata.get('source', 'unknown')}, "
        f"页码: {doc.metadata.get('page', 'N/A')}]\n{doc.page_content}"
        for doc in docs
    )

# 用 LCEL(LangChain Expression Language)构建 RAG Chain
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 执行
question = "RAG 系统中的 Chunking 策略有哪些种类,各自的优缺点是什么?"
answer = rag_chain.invoke(question)
print(answer)

9.6 包含出处信息的响应

from langchain_core.runnables import RunnableParallel

# 同时返回出处信息和答案的 Chain
rag_chain_with_sources = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
).assign(
    answer=lambda x: (
        prompt.invoke(
            {"context": format_docs(x["context"]), "question": x["question"]}
        )
        | llm
        | StrOutputParser()
    ).invoke(x["question"])
)

# 更简洁的方式
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

# Stuff Documents Chain(把检索到的文档合并成一个 Context)
combine_docs_chain = create_stuff_documents_chain(llm, prompt)

# Retrieval Chain
retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain)

# 执行 — 同时返回 context 和 answer
response = retrieval_chain.invoke({"input": question})
print("答案:", response["answer"])
print("\n参考文档:")
for i, doc in enumerate(response["context"], 1):
    print(f"  [{i}] {doc.metadata.get('source', 'unknown')} "
          f"(p.{doc.metadata.get('page', 'N/A')})")

9.7 Conversational RAG(对话式 RAG)

from langchain_core.prompts import MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# 考虑对话历史的 Prompt
contextualize_prompt = ChatPromptTemplate.from_messages([
    ("system", "请结合之前的对话上下文,把用户最新的问题重新表述为可以独立理解的形式。"),
    MessagesPlaceholder("chat_history"),
    ("human", "{input}"),
])

# 按会话管理历史
store = {}

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 构建 Conversational RAG Chain
conversational_rag = RunnableWithMessageHistory(
    retrieval_chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history",
    output_messages_key="answer",
)

# 执行对话
config = {"configurable": {"session_id": "user_001"}}

response1 = conversational_rag.invoke(
    {"input": "什么是 RAG?"},
    config=config,
)
print(response1["answer"])

response2 = conversational_rag.invoke(
    {"input": "它的主要优点是什么?"},  # "它" = 上一轮对话中的 RAG
    config=config,
)
print(response2["answer"])

这个实现只是一个基础的 RAG 流水线。要走向生产环境,还需要应用前面讨论过的 Re-ranking、HyDE 等 Advanced 模式,通过 RAGAS 进行系统性评估,并搭建监控和日志基础设施。


10. 总结

RAG 是解决 LLM Hallucination 问题最实用、最有效的方法。Lewis 等人(2020)在原始论文中提出的 Retriever + Generator 结构,此后演化出各种 Advanced 模式,已成为构建生产级 AI 系统的核心架构。

构建高效 RAG 系统的关键决策可总结如下。

  1. Chunking 策略:以 RecursiveCharacterTextSplitter 作为默认起点,再根据数据特性考虑 Semantic Chunking。
  2. Embedding 模型:需要多语言时选 BGE-M3,以英语为主时 OpenAI text-embedding-3 系列比较稳定。
  3. Vector Database:原型开发用 Chroma,生产环境则根据负载选择合适的 DB。
  4. Advanced 模式:Re-ranking 在几乎所有场景下都值得应用,检索质量不足时可以考虑 HyDE 和 CRAG。
  5. 评估:将 RAGAS 指标集成进 CI/CD,持续监控质量。

参考资料

测验

Q1:「RAG:Retrieval-Augmented Generation 论文解析与生产架构」这篇文章主要涵盖哪些内容?

解析 RAG 论文的核心概念,梳理 Chunking 策略、Vector DB 选型、Advanced RAG 模式等生产级 RAG 系统设计。

Q2: LLM 的 Hallucination 问题与 RAG 的诞生背景是什么? Large Language Model(LLM)在海量文本数据上完成预训练,在自然语言理解和生成上展现出惊人的性能。然而 LLM 存在一个根本性的局限,那就是 Hallucination 问题。

Q3: 请说明原始 RAG 论文结构:Retriever + Generator。 2020 年,Facebook AI Research(现 Meta AI)的 Patrick Lewis 等人发表的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(NeurIPS 2020),是首次将 RAG 这一概念正式化的核心研究。

Q4: Dense Passage Retrieval(DPR)原理的要点是什么? 在 RAG 的 Retriever 组件中扮演核心角色的,正是 Dense Passage Retrieval(DPR)。这是 Karpukhin 等人在 2020 年 EMNLP 上发表的论文《Dense Passage Retrieval for Open-Domain Question Answering》中提出的方法。

Q5: Chunking 策略是如何运作的? 在 RAG 系统中,把文档切分为大小合适的 Chunk,是直接影响检索质量的关键步骤。LangChain 提供了多种 Text Splitter,主要的 Chunking 策略如下。

현재 단락 (1/406)

Large Language Model(LLM)在海量文本数据上完成预训练,在自然语言理解和生成上展现出惊人的性能。然而 LLM 存在一个根本性的局限 — 那就是**Hallucination**(...

작성 글자: 0원문 글자: 19,091작성 단락: 0/406