- 1. LLM 的 Hallucination 问题与 RAG 的诞生背景
- 2. 原始 RAG 论文结构:Retriever + Generator
- 3. Dense Passage Retrieval(DPR)原理
- 4. Chunking 策略
- 5. Embedding 模型选择
- 6. Vector Database 比较
- 7. Advanced RAG 模式
- 8. Evaluation 指标:RAGAS 框架
- 9. LangChain + ChromaDB 实战实现示例
- 10. 总结
- 参考资料
- 测验
1. LLM 的 Hallucination 问题与 RAG 的诞生背景
Large Language Model(LLM)在海量文本数据上完成预训练,在自然语言理解和生成上展现出惊人的性能。然而 LLM 存在一个根本性的局限 — 那就是Hallucination(幻觉)问题。
Hallucination 指的是模型将并非事实的信息、当作事实一样自信地生成出来的现象。这个问题产生的根本原因如下。
- 知识的静态特性:编码在 LLM 参数中的知识在训练时刻就已固定,无法反映训练之后发生的事件或更新过的信息。
- 参数记忆的不完整性:即便有数十亿个参数,也不可能把世界上所有细节事实都准确地存储并复现出来。
- 概率式生成方式:LLM 是以概率方式预测并生成下一个 token 的,因此可能生成统计上看似合理、但与事实不符的文本。
- 无法追溯来源:生成的答案究竟源自哪份训练数据,是无法追溯的,这使得验证本身变得困难。
为克服这些局限而出现的方法,就是Retrieval-Augmented Generation(RAG)。RAG 的核心思路简单而强大 — 在 LLM 生成答案之前,先从外部知识库中检索(Retrieve)相关文档,再基于这些信息生成(Generate)答案。
由此可以获得以下好处。
- 基于事实的回答:以检索到的真实文档为依据生成答案,从而减少 Hallucination。
- 知识更新容易:只需更新外部数据库即可反映最新信息,无需重新训练模型。
- 可提供出处:可以把作为答案依据的文档一并呈现,提升透明度和可信度。
- 易于领域特化:只需对特定领域的文档建立索引,就能快速构建面向该领域的专用系统。
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)
其运行原理如下。
- 首先尝试按
\n\n(段落分隔)分割。 - 若 Chunk 仍超过
chunk_size,则按\n(换行)分割。 - 若仍然超出,则按
.(句子单位)分割。 - 最后手段是按空格或按字符逐个分割。
这种方式的核心在于优先尝试保留更大的语义单元,只有在必要时才降到更小的单元。
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 | 推荐场景 |
|---|---|---|---|---|
| Chroma | Embedded | ~100 万 | 有限 | 原型开发、开发环境 |
| Pinecone | Managed SaaS | 数十亿 | 支持 | 生产环境、运维最小化 |
| Weaviate | 开源/云 | 数亿 | 强力支持 | 以 Hybrid Search 为中心 |
| pgvector | PostgreSQL 扩展 | ~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}),
)
其运行流程如下。
- Bi-Encoder 快速检索出范围较广的候选文档(top-20)。
- Cross-Encoder 把查询和每个候选文档成对输入,直接打出相关性分数。
- 只把重新排序后的靠前文档(top-3)传给 Generator。
Cross-Encoder 比 Bi-Encoder 更精确,但需要对每个候选逐一推理,因此不适合大规模检索。所以标准做法是先用 Bi-Encoder 缩小候选范围,再用 Cross-Encoder 重新排序的两阶段流水线。
7.2 HyDE(Hypothetical Document Embeddings)
Gao 等人(2022)提出的 HyDE,是用来弥合查询与文档之间语义鸿沟(Semantic Gap)的一种模式。用户的问题通常简短而抽象,而包含答案的文档则冗长而具体。这种差异可能使得直接的向量相似度检索并非最优。
HyDE 的核心思路如下。
- 收到用户查询后,请 LLM「写一份能回答这个问题的假想文档」。
- 对生成的假想文档(Hypothetical Document)做 Embedding。这份假想文档内容可能与事实不符,但其格式和用词与真实相关文档相似。
- 用这个 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 的核心组件如下。
- Retrieval Evaluator:由一个轻量模型将检索文档的相关性判断为 Correct、Incorrect、Ambiguous 三种。
- Knowledge Refinement:从检索文档中只提取核心信息、去除不必要的部分,采用 Decompose-then-Recompose 算法。
- Web Search Fallback:如果 Retrieval Evaluator 判定为 Incorrect,则不使用静态语料库,改用网络搜索来寻找更好的信息。
[Query] → [Retriever] → [Retrieval Evaluator]
↓
Correct → Knowledge Refinement → Generator
Incorrect → Web Search → Knowledge Refinement → Generator
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 数)
其运行方式如下。
- LLM 从生成的答案中提取出各个 Claim(主张)。
- 判断每个 Claim 是否得到了所提供 Context 的支持(Support)。
- 计算被支持的 Claim 所占的比例。
数值介于 0~1 之间,越接近 1 表示答案越忠实于 Context。
8.2 Answer Relevance(答案相关性)
衡量生成的答案与原始问题之间的相关程度。
其运行方式如下。
- 从生成的答案反向生成问题(Reverse Engineering)。
- 计算生成的问题与原始问题之间的 Embedding 相似度。
- 平均相似度即为 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 系统的关键决策可总结如下。
- Chunking 策略:以 RecursiveCharacterTextSplitter 作为默认起点,再根据数据特性考虑 Semantic Chunking。
- Embedding 模型:需要多语言时选 BGE-M3,以英语为主时 OpenAI text-embedding-3 系列比较稳定。
- Vector Database:原型开发用 Chroma,生产环境则根据负载选择合适的 DB。
- Advanced 模式:Re-ranking 在几乎所有场景下都值得应用,检索质量不足时可以考虑 HyDE 和 CRAG。
- 评估:将 RAGAS 指标集成进 CI/CD,持续监控质量。
参考资料
- Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., ... & Kiela, D. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020. https://arxiv.org/abs/2005.11401
- Karpukhin, V., Oguz, B., Min, S., Lewis, P., Wu, L., Edunov, S., Chen, D., & Yih, W. (2020). Dense Passage Retrieval for Open-Domain Question Answering. EMNLP 2020. https://arxiv.org/abs/2004.04906
- Gao, L., Ma, X., Lin, J., & Callan, J. (2022). Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE). ACL 2023. https://arxiv.org/abs/2212.10496
- Asai, A., Wu, Z., Wang, Y., Sil, A., & Hajishirzi, H. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. ICLR 2024. https://arxiv.org/abs/2310.11511
- Yan, S., Gu, J., Zhu, Y., & Ling, Z. (2024). Corrective Retrieval Augmented Generation. https://arxiv.org/abs/2401.15884
- Es, S., James, J., Espinosa-Anke, L., & Schockaert, S. (2023). RAGAS: Automated Evaluation of Retrieval Augmented Generation. https://arxiv.org/abs/2309.15217
- LangChain Text Splitters Documentation. https://docs.langchain.com/oss/python/integrations/splitters
- RAGAS Documentation - Available Metrics. https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/
- Pinecone - Chunking Strategies. https://www.pinecone.io/learn/chunking-strategies/
- MTEB: Massive Text Embedding Benchmark. https://huggingface.co/blog/mteb
测验
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**(...