§1.1.2

RAG 相对纯 LLM 的五个动机(时效性 / 私域 / 幻觉 / 成本 / 可溯源)?

核心概念

检索增强生成(Retrieval-Augmented Generation, RAG)是一种将大型语言模型(LLM)与外部知识库相结合的架构。其核心思想是,在生成回答之前,首先从一个庞大的文档集合(如公司的内部 wiki、最新的网络文章)中检索出与用户问题最相关的信息片段,然后将这些信息作为上下文(Context)连同原始问题(Query)一起提供给 LLM,引导模型生成基于这些可靠信息的回答。RAG 将 LLM 从一个单纯的“记忆者”转变为一个能够“开卷考试”的“思考者”,有效结合了检索系统的时效性和准确性与语言模型的生成能力。

原理与推得

RAG 的工作流程可以分解为两个核心阶段:检索(Retrieval)生成(Generation)

1. 阶段一:检索(Retrieval)

此阶段的目标是从知识库 DD 中找到与用户问题 xx 最相关的 kk 个文档片段 z1,z2,...,zkz_1, z_2, ..., z_k

  • 数学表示: 知识库 DD 首先被分割成多个文档片段(chunks){d1,d2,...,dN}\{d_1, d_2, ..., d_N\}。每个片段 did_i 通过一个编码器模型 Edoc()E_{doc}(\cdot) 映射到一个高维向量(embedding):vi=Edoc(di)v_i = E_{doc}(d_i)。这些向量构成了一个可被高效检索的索引库(Index)。 当用户输入一个问题 xx 时,同样使用一个编码器 Eq()E_{q}(\cdot)(通常与 EdocE_{doc} 相同或相似)将其转换为查询向量 q=Eq(x)q = E_q(x)。 接着,通过计算查询向量 qq 与所有文档向量 viv_i 的相似度来对文档进行排序。最常用的相似度度量是余弦相似度:

    similarity(q,vi)=qviqvi\text{similarity}(q, v_i) = \frac{q \cdot v_i}{\|q\| \|v_i\|}

    系统会选出相似度最高的 Top-k 个文档片段 {z1,...,zk}\{z_1, ..., z_k\} 作为后续生成的上下文。

  • 算法复杂度

    • 索引构建(离线):对于 NN 个文档片段,需要 NN 次编码器前向传播。时间复杂度为 O(NCenc)O(N \cdot C_{enc}),其中 CencC_{enc} 是单次编码的成本。
    • 检索(在线):如果使用暴力法(Flat Index),每次查询需要计算 NN 次相似度,时间复杂度为 O(Nd)O(N \cdot d),其中 dd 是向量维度。在工程实践中,通常使用近似最近邻搜索(ANN)算法,如 FAISS 或 HNSW,其查询复杂度可以降至对数级别,如 O(logN)O(\log N)O(poly(logN))O(\text{poly}(\log N)),大大提高检索效率。
  • 直观解释: 可以将其想象成一个图书馆管理员。用户提出问题后,管理员(Retriever)不会立刻凭记忆回答,而是先去书架(知识库)上,利用索引卡片(Vector Index)快速找到几本最相关的书籍或章节(文档片段)。

2. 阶段二:生成(Generation)

此阶段,LLM 基于原始问题和检索到的上下文生成最终答案。

  • 数学表示: 一个纯粹的 LLM 在回答问题 xx 时,其生成过程可以建模为预测词序列 y={y1,...,yL}y = \{y_1, ..., y_L\} 的概率:

    P(yx)=i=1LP(yiy<i,x)P(y|x) = \prod_{i=1}^{L} P(y_i | y_{<i}, x)

    而在 RAG 中,模型在生成时同时以问题 xx 和检索到的上下文 z={z1,...,zk}z = \{z_1, ..., z_k\} 为条件。概率模型变为:

    P(yx,z)=i=1LP(yiy<i,x,z)P(y|x, z) = \prod_{i=1}^{L} P(y_i | y_{<i}, x, z)

    这在形式上是通过构建一个精心设计的提示(Prompt)来实现的,该提示将 xxzz 组合在一起。例如: "根据以下信息回答问题。信息:{z}。问题:{x}。回答:"

  • 直观解释: 接上例,管理员把找到的书籍章节(上下文)和用户的问题便条(原始问题)一起交给一位博学的专家(LLM)。专家在阅读了这些指定材料后,结合自己的知识,给出一个有理有据、内容详实的回答。


RAG 相对纯 LLM 的五大动机详解

  1. 时效性 (Timeliness)

    • 纯 LLM:知识被“冻结”在其训练数据截止的日期。对于之后发生的新事件、新发现,它一无所知。
    • RAG:知识库可以随时、低成本地更新。只需将新文档加入知识库并更新索引,RAG 系统就能即时获取并利用最新信息,而无需重新训练昂贵的 LLM。
  2. 私域知识 (Private Domain)

    • 纯 LLM:公开训练的 LLM 无法访问企业内部的私有数据,如技术文档、财务报表、项目邮件等。
    • RAG:可以构建一个仅包含企业私域数据的知识库。这使得 LLM 能够为企业内部提供定制化服务(如智能客服、内部知识问答),同时保证了数据的隐私和安全,因为私有数据并未用于模型训练,仅在查询时被检索。
  3. 缓解幻觉 (Alleviate Hallucination)

    • 纯 LLM:在回答其知识范围外或记忆模糊的问题时,容易“一本正经地胡说八道”,即产生幻觉(Hallucination),捏造事实、日期、引用等。
    • RAG:通过将模型的回答“锚定”在检索到的具体文本上,极大地减少了幻觉。模型被引导去整合和复述所提供的信息,而不是凭空创造。这使得回答更加可靠和真实。
  4. 成本效益 (Cost-Effectiveness)

    • 纯 LLM:要让 LLM 掌握新知识,传统方法是进行全量或增量微调(Fine-tuning)。这个过程计算资源消耗巨大,成本高昂,且需要大量的标注数据。
    • RAG:更新知识的成本极低。主要是对新文档进行编码和索引的成本,远低于模型微调。对于大多数需要频繁更新知识的场景,RAG 是一个更经济、更敏捷的解决方案。
  5. 可溯源性与可解释性 (Traceability & Interpretability)

    • 纯 LLM:其回答是一个“黑箱”,很难解释为什么会这样说,其知识来源也无法追溯。
    • RAG:由于答案是基于检索到的特定文档片段生成的,系统可以同时展示这些“引用来源”。用户不仅能得到答案,还能看到答案依据了哪些原始材料,可以自行核实。这在金融、法律、医疗等需要高可信度的领域至关重要,大大增强了系统的透明度和用户的信任度。

代码实现

下面是一个使用 sentence-transformersnumpy 实现的极简 RAG 流程的演示代码。这里我们不调用真实的 LLM API,而是模拟最后一步的 prompt 构建,以清晰地展示 RAG 的核心机制。

python
1import numpy as np
2from sentence_transformers import SentenceTransformer
3from numpy.linalg import norm
4
5# 1. 知识库:模拟一个包含私域和时效性知识的文档集合
6knowledge_base = [
7 "2023年,AlphaTech公司发布了其旗舰AI芯片'Neuro-X',算力达到每秒1000万亿次浮点运算。",
8 "RAG(Retrieval-Augmented Generation)是一种结合检索和生成的AI技术,能有效缓解模型幻觉。",
9 "公司的内部安全政策规定,所有员工必须每季度更换一次密码。",
10 "最新的市场报告显示,2024年第一季度全球智能手机出货量同比增长了5%。"
11]
12
13# 2. 编码器:加载一个预训练的句向量模型
14# 使用一个轻量级的中文模型,第一次运行时会自动下载
15encoder = SentenceTransformer('DMetaSoul/sbert-chinese-general-v2')
16
17# 3. 索引构建:将知识库中的文档编码为向量
18# 在真实项目中,这里会使用FAISS等向量数据库进行存储和高效索引
19print("正在构建知识库索引...")
20indexed_vectors = encoder.encode(knowledge_base, show_progress_bar=True)
21print("索引构建完成!")
22
23def cosine_similarity(v1, v2):
24 """计算两个向量的余弦相似度"""
25 return np.dot(v1, v2) / (norm(v1) * norm(v2))
26
27def retrieve(query: str, top_k: int = 1):
28 """
29 检索阶段:输入查询,返回最相关的top_k个文档片段
30 """
31 # 为什么这样做:将用户问题同样编码为向量,才能在同一向量空间中进行比较
32 query_vector = encoder.encode(query)
33
34 # 为什么这样做:计算查询向量与知识库中所有文档向量的相似度,以找到最相关的内容
35 similarities = [cosine_similarity(query_vector, doc_vector) for doc_vector in indexed_vectors]
36
37 # 为什么这样做:使用argsort获取排序后的索引,-top_k: 表示取最后k个(即最大的k个),[::-1]将其反转为降序
38 # 这是从相似度得分中高效找到top-k文档的常用方法
39 top_k_indices = np.argsort(similarities)[-top_k:][::-1]
40
41 # 返回最相关的文档原文
42 return [knowledge_base[i] for i in top_k_indices]
43
44def generate_prompt(query: str, context: list):
45 """
46 生成阶段(模拟):将问题和检索到的上下文组合成一个完整的提示
47 """
48 # 为什么这样做:这是RAG的核心,将检索到的证据(context)和原始问题(query)清晰地呈现给LLM。
49 # 这种结构化的Prompt引导LLM基于提供的上下文进行回答,而不是依赖其内部的、可能过时的知识。
50 context_str = "\n".join(context)
51 prompt = f"""
52---
53[上下文信息]:
54{context_str}
55
56---
57[任务]:
58请根据以上提供的上下文信息,简洁地回答以下问题。如果信息不足,请回答“根据现有信息无法回答”。
59
60[问题]:
61{query}
62---
63[回答]:
64"""
65 return prompt
66
67# --- 模拟用户交互 ---
68
69# 案例1:查询时效性信息
70query1 = "AlphaTech公司在2023年发布了什么产品?"
71retrieved_context1 = retrieve(query1, top_k=1)
72final_prompt1 = generate_prompt(query1, retrieved_context1)
73
74print("\n--- 案例1:时效性问题 ---")
75print(f"用户问题: {query1}")
76print(f"检索到的上下文: {retrieved_context1}")
77print("--- 生成的最终Prompt ---")
78print(final_prompt1)
79
80# 案例2:查询私域知识
81query2 = "公司的密码政策是什么?"
82retrieved_context2 = retrieve(query2, top_k=1)
83final_prompt2 = generate_prompt(query2, retrieved_context2)
84
85print("\n--- 案例2:私域知识问题 ---")
86print(f"用户问题: {query2}")
87print(f"检索到的上下文: {retrieved_context2}")
88print("--- 生成的最终Prompt ---")
89print(final_prompt2)
90
91# 案例3:查询可能导致幻觉的问题(但RAG可以回答)
92query3 = "RAG技术有什么用?"
93retrieved_context3 = retrieve(query3, top_k=1)
94final_prompt3 = generate_prompt(query3, retrieved_context3)
95
96print("\n--- 案例3:缓解幻觉 ---")
97print(f"用户问题: {query3}")
98print(f"检索到的上下文: {retrieved_context3}")
99print("--- 生成的最终Prompt ---")
100print(final_prompt3)

工程实践

  1. 使用场景

    • 企业知识库:搭建面向员工的智能问答机器人,回答 HR 政策、IT 支持、产品文档等问题。
    • 智能客服:快速从产品手册、FAQ 中找到答案,提供给客服人员或直接回答客户,提升响应速度和准确率。
    • 个人知识助理:集成到笔记软件(如 Notion, Obsidian)中,对个人积累的笔记和资料进行智能问答。
    • 金融/法律分析:快速从海量研报、财报、法律条文中提取关键信息,辅助分析师和律师决策。
  2. 超参数选择经验

    • Chunk Size:文档切块的大小。一般在 256-512 个 token 之间。太小则上下文不完整,太大则引入过多噪声,且可能超出 LLM 的上下文窗口限制。需要根据文档类型和查询复杂度进行实验。
    • Chunk Overlap:块之间的重叠大小,通常是 Chunk Size 的 10%-20%。设置重叠可以防止语义完整的句子在切分时被割裂,保证检索的连续性。
    • Top-k:检索文档的数量。通常选择 3-5。k 太小可能错过关键信息,k 太大则会引入噪声,增加 LLM 处理的负担和成本,并可能触发“大海捞针”(Lost in the Middle)问题。
    • Embedding Model:选择合适的编码模型至关重要。需要考虑其在特定领域和语言上的表现、向量维度(影响存储和计算成本)以及推理速度。
  3. 性能/显存/吞吐的权衡

    • 检索器:使用 Flat Index(暴力搜索)精度最高但速度最慢。使用 HNSW (FAISS) 等 ANN 索引,可以在牺牲极小的召回率下,将检索速度提升几个数量级,是生产环境的首选。
    • 编码器:大型编码模型效果好但推理慢、显存占用大。小型模型反之。可以采用模型量化、蒸馏或使用专门优化的推理引擎(如 ONNX Runtime, TensorRT)来平衡。
    • 生成器 (LLM):更大的 LLM 理解能力和遵循指令能力更强,但推理更慢、成本更高。需要根据任务复杂度和预算选择合适的模型尺寸。流式输出(Streaming)可以显著改善用户体验。
  4. 常见坑和调试技巧

    • “Garbage in, Garbage out”:检索质量是 RAG 系统性能的上限。如果检索出的文档不相关,LLM 也无能为力。需要重点评估和优化检索模块。
    • 评估困难:RAG 的评估是端到端的,需要同时评估检索(如 Recall, MRR)和生成(如 Faithfulness - 忠实度, Answer Relevance - 相关性)两部分。可以利用 Ragas 等框架进行评估。
    • 调试:当回答不佳时,首先检查检索模块返回的上下文是什么。是没找到相关文档?还是找到了但内容有误或不完整?通过分析中间步骤来定位问题。

常见误区与边界情况

  1. 误区:RAG 能完全消除幻觉。

    • 真相:RAG 只能显著缓解幻觉,而非根除。LLM 仍可能:a) 忽略提供的上下文,顽固地使用其内部知识;b) 错误地组合或理解上下文中的信息;c) 在上下文信息不足时进行猜测。
  2. 误区:RAG 和微调(Fine-tuning)是互斥的。

    • 真相:它们是互补的。微调更擅长教会模型一种新的行为、风格或技能(如学会以特定格式输出,或理解特定领域的术语),而 RAG 擅长提供具体的、动态的事实知识。一个常见的强大模式是:对 LLM 进行微调,使其更擅长“利用上下文信息进行回答”,然后再将其用于 RAG 系统。
  3. 边界情况:检索不到任何相关信息。

    • 处理:系统应该有一个“后备策略”(Fallback)。当检索到的文档相似度得分低于某个阈值时,可以判断为知识库内无相关信息。此时,可以:a) 让 LLM 直接回答“我不知道”或“根据现有信息无法回答”;b) 直接调用纯 LLM 的能力进行开放式回答,并告知用户该回答未经外部知识验证。
  4. 边界情况:检索到的信息相互矛盾。

    • 处理:这是 RAG 的一个挑战。LLM 需要具备一定的辨别和综合能力。在 Prompt 中可以加入指令,如“如果信息有冲突,请指出冲突点,并给出最可能的回答”。更高级的系统会引入事实核查或多源信息比对的机制。
  5. 常见面试追问

    • Q: 如何评估一个 RAG 系统的好坏?
      • A: 需要分两部分评估。检索部分:使用标准信息检索指标,如召回率 (Recall@k)、平均倒数排名 (MRR)。生成部分:评估答案的忠实度 (Faithfulness,答案是否完全基于上下文)、相关性 (Answer Relevance,答案是否切中问题)、准确性 (Correctness)。可以借助 Ragas 等自动化评估框架,或通过人工评估。
    • Q: 如果你的 RAG 系统回答慢,你会从哪些方面进行优化?
      • A: 分三步排查:1. 检索阶段:是否使用了 ANN 索引?索引参数是否合理?编码模型是否过大?考虑换用更快的编码模型或进行模型优化。2. 生成阶段:LLM 模型尺寸是否过大?是否可以换用更小但性能足够好的模型?是否开启了流式输出以改善前端体验?3. 端到端链路:减少网络延迟,优化数据传输格式,考虑将检索和生成服务部署在同一集群内。
    • Q: “Lost in the Middle”问题是什么?如何缓解?
      • A: 指的是 LLM 在处理长上下文时,倾向于关注开头和结尾的信息,而忽略中间部分。这会导致即使检索到了正确文档,如果关键信息在中间,也可能被忽略。缓解方法:a) 优化 Top-k,不要传入过多文档;b) 对检索到的文档按相关性重排序(Re-ranking),将最相关的放在开头或结尾;c) 采用更长的上下文窗口模型,并对其进行针对性微调以提升长文本处理能力。
相关题目