RAG(Retrieval-Augmented Generation,检索增强生成)是连接短期与长期记忆的桥梁——它把「从档案柜里捞相关记忆,拼到工作台上让 LLM 用」这件事系统化、工程化。RAG 是 Agent 记忆系统最实用的形态,但「会写 RAG」与「写好 RAG」之间隔着一整代技术的距离。
第 1.1 节讲过 LLM 的「知识截止」与「幻觉」两大短板。RAG 正是为这两条短板而生:
| LLM 短板 | RAG 的补齐方式 |
|---|---|
| 知识截止 | 实时检索外部知识库,把最新信息送进 Prompt |
| 幻觉 | 让 LLM 基于「检索到的事实」生成,而非凭记忆编造 |
| 领域知识不足 | 接入企业内部文档库,补齐专业领域 |
| 私有数据不可见 | 接入用户私有数据,个性化回答 |
💡 RAG 的本质:把「记忆」与「生成」分离——记忆存在外部库,生成时按需检索拼进 Prompt。LLM 不再需要「记住一切」,只需「会用检索」。
最朴素的 RAG 流水线分三步:检索 → 拼接 → 生成。
[系统] 你是一个问答助手。根据下面的「参考资料」回答问题。 如果参考资料没有答案,请说"我不知道"。 [参考资料] 1. (检索到的文档片段 1) 2. (检索到的文档片段 2) ... K. (检索到的文档片段 K) [用户问题] <用户原始问题>
朴素 RAG 看起来简单,但有一堆典型问题:
| 问题 | 表现 |
|---|---|
| 召回不准 | 检索到的文档不是最相关的 |
| 召回过多 | Top-K 太大,噪声多、窗口爆 |
| 召回过少 | Top-K 太小,漏掉关键信息 |
| 查询与文档不匹配 | 用户问「怎么报销」,文档写「费用申请流程」 |
| 多跳问题 | 答案需要跨多个文档拼接 |
| 过时信息 | 检索到的是旧文档 |
| LLM 不忠于检索 | 给了资料 LLM 还是凭记忆编 |
这些问题催生了「Advanced RAG」——在朴素 RAG 的前后加各种增强环节。
Advanced RAG 把流水线分为三段:Pre-Retrieval(检索前)→ Retrieval(检索)→ Post-Retrieval(检索后)。每段都可以增强。
| 段 | 目标 | 典型技术 |
|---|---|---|
| Pre-Retrieval | 优化查询,提升召回率 | 查询改写、查询扩展、查询分解 |
| Retrieval | 提升检索精度与召回 | 混合检索、多路检索 |
| Post-Retrieval | 过滤噪声、突出重点 | 重排序 Rerank、压缩、去重 |
| Generation | 让 LLM 忠于检索结果 | 提示工程、Self-RAG、引用约束 |
下面逐段展开。
用户的问题往往不适合直接拿去检索——太短、太模糊、用词与文档不一致。Pre-Retrieval 的目标是把「用户语言」转成「检索友好的查询」。
让 LLM 把用户问题改写成更利于检索的形式。
原始: "怎么报销" 改写: "员工费用报销流程 报销申请 提交方式"
把一个查询扩展成多个相关查询,分别检索后合并。
原始: "苹果营收" 扩展: → "Apple revenue" → "苹果 财报 收入" → "Apple Inc fiscal year revenue" (分别检索,合并结果)
把复杂问题拆成子问题,分别检索。
原始: "对比苹果和微软 2024 年的营收增长率" 分解: → 苹果 2024 营收 → 苹果 2023 营收 → 微软 2024 营收 → 微软 2023 营收 (分别检索后,LLM 综合计算)
让 LLM 先编一个假设性答案,用这个假答案去检索——因为「答案的语言」比「问题的语言」更接近文档。
问题: "苹果 2024 营收?" LLM 假设答案: "苹果公司 2024 财年总营收约 3910 亿美元..." 用假设答案 Embedding 去检索 → 召回真实财报文档
💡 HyDE 的妙处:问题与文档存在「语义鸿沟」——问题是疑问句,文档是陈述句。HyDE 让两者语言对齐,显著提升召回率。这是 Pre-Retrieval 里最巧妙的技术之一。
单一的向量检索(语义检索)有局限——它对关键词精确匹配、专有名词、数字不敏感。生产系统常用混合检索(Hybrid Retrieval)。
| 检索方式 | 擅长 | 弱项 |
|---|---|---|
| 语义检索(向量) | 概念、近义、跨语言 | 精确词、数字、专名 |
| 关键词检索(BM25) | 精确词、专名、代码 | 同义、概念扩展 |
| 混合检索 | 两者优势互补 | 实现复杂度略高 |
两路检索结果如何合并?常用倒数排名融合(Reciprocal Rank Fusion, RRF):
其中 r(d) 是文档 d 在某路检索中的排名,k 是常数(通常 60)。RRF 不依赖原始分数(语义与关键词的分数不可比),只看排名,简单有效。
⚠️ 混合检索的成本:它要维护两套索引(向量 + 关键词),检索时间也加倍。生产中常先用一路粗筛,再另一路精排——而非每路都全量。
检索回来的 Top-K 文档里,常有噪声(不相关的)。Post-Retrieval 的核心动作是重排序(Rerank)——用一个更精确的模型对 Top-K 重新打分。
<svg viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg"> <rect x="40" y="100" width="120" height="80" fill="#dbeafe" stroke="#2563eb" rx="6"/> <text x="100" y="130" font-size="13" fill="#1e3a8a" text-anchor="middle" font-weight="bold">查询</text> <text x="100" y="150" font-size="11" fill="#1e3a8a" text-anchor="middle">Embedding</text> <rect x="200" y="60" width="160" height="160" fill="#fef9c3" stroke="#ca8a04" rx="6"/> <text x="280" y="90" font-size="13" fill="#713f12" text-anchor="middle" font-weight="bold">第一阶段:召回</text> <text x="280" y="115" font-size="11" fill="#713f12" text-anchor="middle">向量检索</text> <text x="280" y="140" font-size="11" fill="#713f12" text-anchor="middle">快速粗筛</text> <text x="280" y="170" font-size="11" fill="#713f12" text-anchor="middle">Top-100</text> <text x="280" y="195" font-size="10" fill="#713f12" text-anchor="middle">(高召回,低精度)</text> <rect x="400" y="60" width="160" height="160" fill="#fce7f3" stroke="#db2777" rx="6"/> <text x="480" y="90" font-size="13" fill="#831843" text-anchor="middle" font-weight="bold">第二阶段:精排</text> <text x="480" y="115" font-size="11" fill="#831843" text-anchor="middle">Rerank 模型</text> <text x="480" y="140" font-size="11" fill="#831843" text-anchor="middle">逐对打分</text> <text x="480" y="170" font-size="11" fill="#831843" text-anchor="middle">Top-5</text> <text x="480" y="195" font-size="10" fill="#831843" text-anchor="middle">(高精度,慢)</text> <rect x="600" y="100" width="100" height="80" fill="#dcfce7" stroke="#16a34a" rx="6"/> <text x="650" y="135" font-size="13" fill="#14532d" text-anchor="middle" font-weight="bold">最终</text> <text x="650" y="158" font-size="11" fill="#14532d" text-anchor="middle">拼进 Prompt</text> <line x1="160" y1="140" x2="200" y2="140" stroke="#475569" stroke-width="2" marker-end="url(#a)"/> <line x1="360" y1="140" x2="400" y2="140" stroke="#475569" stroke-width="2" marker-end="url(#a)"/> <line x1="560" y1="140" x2="600" y2="140" stroke="#475569" stroke-width="2" marker-end="url(#a)"/> <defs> <marker id="a" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto"> <path d="M0,0 L0,6 L9,3 z" fill="#475569"/> </marker> </defs> </svg>
为什么需要两阶段:
Rerank 模型(如 Cohere Rerank、BGE Reranker)与 Embedding 模型不同——它输入查询+文档对,输出相关性分数,比单纯向量相似度精确得多。
| 维度 | Embedding | Rerank |
|---|---|---|
| 输入 | 单文本 | 查询+文档对 |
| 输出 | 向量 | 相关性分数 |
| 速度 | 快 | 慢 |
| 精度 | 中 | 高 |
| 用途 | 大规模召回 | 少量精排 |
💡 Rerank 是 RAG 性价比最高的优化。它在 Top-5 的最终质量上能带来 10-30% 的提升,成本只增加一次小模型调用。几乎所有生产 RAG 都用 Rerank——它是「写了 RAG 但没用 Rerank」与「专业 RAG」的分水岭。
Post-Retrieval 之后,要把检索结果交给 LLM 生成。这一段也有坑——LLM 可能不忠于检索资料,依然凭记忆编造。
[系统] 严格根据下面的「参考资料」回答。 - 资料中有答案才回答,引用资料编号 [1] [2] - 资料不足时回答"根据现有资料无法确定" - 不得使用资料外的知识
Self-RAG(Asai et al. 2023)让 LLM 在生成过程中自我判断:
| 反思点 | LLM 自问 |
|---|---|
| 是否需要检索 | 「这个问题需要查资料吗?」 |
| 检索是否相关 | 「召回的资料与问题相关吗?」 |
| 生成是否忠于 | 「我的回答有资料支撑吗?」 |
| 回答是否完整 | 「资料是否支持完整回答?」 |
⚠️ Self-RAG 的代价:每次生成要多调几次 LLM 做反思。它的价值在「关键场景」——医疗、法律、金融等不容幻觉的领域。对一般问答,过于昂贵。
把 RAG 推到生产,要面对几个工程挑战:
文档太长不能整篇检索,要切成片段(Chunk)。切多大、怎么切,直接影响召回质量。
| 切分策略 | 做法 |
|---|---|
| 固定长度 | 每 N 个 Token 一段 |
| 按句/段 | 不切断句子 |
| 按结构 | 按标题/章节 |
| 滑动重叠 | 相邻片段有重叠(如每段含上段末尾 20%) |
💡 切分是 RAG 最被低估的优化点。同样模型同样数据,切得好与切得差,召回质量差 30% 以上。生产 RAG 必须为不同文档类型设计不同切分策略。
文档会变(更新、删除、新增),索引也要跟着变。
| 做法 | 说明 |
|---|---|
| 全量重建 | 定期重建索引(简单但慢) |
| 增量更新 | 只对变更文档重新 Embedding(高效) |
| 版本管理 | 保留历史版本支持回溯 |
RAG 怎么知道好不好?要建立评估指标:
| 指标 | 衡量 |
|---|---|
| 召回率 Recall | 相关文档有没有被检索到 |
| 精度 Precision | 检索到的相关不相关 |
| 忠实度 Faithfulness | 回答是否忠于检索 |
| 答案相关性 | 回答是否切题 |
业界有 RAGAS、TruLens 等 RAG 评估框架,专门量化这些指标。
补齐 LLM 知识,除了 RAG 还可以微调(Fine-tuning)。两者常被混淆,要分清:
| 维度 | RAG | 微调 |
|---|---|---|
| 知识来源 | 外部库(动态) | 模型权重(静态) |
| 更新成本 | 低(改库即可) | 高(重新训练) |
| 实时性 | 强 | 弱 |
| 私密性 | 数据不进模型 | 数据进模型 |
| 适合 | 事实知识、动态数据 | 风格、能力、领域推理 |
💡 生产实践常组合用:RAG 补「事实知识」,微调补「能力与风格」。例如一个法律 Agent:用法律领域数据微调让模型「会法律推理」,再用 RAG 接入最新法条让它「知道当下法律」。两者不是二选一。
| 反模式 | 表现 | 后果 | 正确做法 |
|---|---|---|---|
| 只做朴素 RAG | 检索完直接拼 | 召回差、噪声多 | 三段式增强 |
| 无 Rerank | 召回完直接用 | Top-K 质量低 | 加 Rerank 精排 |
| 切分不合理 | 固定 512 Token 硬切 | 召回质量差 | 按文档类型切分 |
| 查询不改写 | 用户原话直接检索 | 召回不全 | Pre-Retrieval 改写 |
| 不评估 | 凭感觉调参 | 黑盒优化 | 用 RAGAS 量化 |
| 不更新索引 | 文档变了不重建 | 过时信息 | 增量更新 |
下一节《5.4 记忆的检索、更新与遗忘》把视角拉到记忆的全生命周期,讨论何时检索、何时更新、何时遗忘,并连接 Generative Agents 的经典架构。