
本篇是「应用实战篇」第 3 篇。前两篇我们分别解决了"如何让模型听指令"(第21篇 Prompt Engineering)和"如何让模型调工具"(第22篇 Function Calling)。但还有一个更普遍的需求:让模型回答它没学过、或知识已过时的问题。这正是 RAG(Retrieval-Augmented Generation,检索增强生成)的主场------先检索、再生成。
摘要
- 核心问题:LLM 知识冻结、易产生幻觉(《第17篇:模型幻觉与评估》),无法回答私有或实时问题。
- 主要贡献:系统讲解 RAG 的两条主链路(离线建库、在线检索---生成),以及切分、向量化、混合检索、重排等关键技术。
- 阅读收获 :
- 掌握文档切分(chunking)与向量化的工程权衡。
- 理解"混合检索 + 重排"为何优于纯向量检索。
- 能跑通一段可运行的迷你 RAG 代码,并知道何时用 RAG 而非微调或长上下文。
一、背景与动机
1.1 为什么单靠模型不够
《第03篇:Transformer 架构》奠定了 LLM 是"基于上下文预测下一个 token"的范式。它的知识全部来自预训练语料(《第04篇:预训练与微调》),带来两个根本限制:
- 知识截止(Knowledge Cutoff):训练之后的事件,模型无从知晓。
- 私有数据不可见:企业文档、个人笔记、内部 Wiki 不在公开语料中。
- 幻觉风险:不知道时,模型倾向于"自信地编造"(《第17篇》详述了成因与缓解)。
微调(《第24篇》将讲)能把知识"烧"进权重,但成本高、更新慢、且对小语料易过拟合。RAG 提供了一条更轻、更可控的路:把知识放在外部,用时再取------这和第22篇 Tool Use 的思路一脉相承,都是"把确定性能力交给外部系统,模型只负责推理"。
1.2 RAG 的定义
RAG = 检索(Retrieval) + 生成(Generation)。在生成回答前,系统先从知识库检索出与问题最相关的若干片段,把它们作为上下文拼进 prompt(呼应《第21篇》的"上下文段"),再由模型据此作答。模型从"凭记忆答"变成"带着资料答"。
二、技术原理
RAG 有两条主链路:离线建库链路 与在线检索---生成链路 (见配图 diagram_53)。
2.1 离线建库:文档 → 切分 → 向量化 → 入库
步骤 1:文档加载与清洗
把 PDF、网页、Markdown、数据库记录等统一为纯文本,去除页眉页脚、乱码、冗余空白。
步骤 2:切分(Chunking)
大模型上下文有限(《第12篇:长上下文技术全景》),不能把整本书塞进去,必须切成片段(chunk)。常见策略(见配图 diagram_54 左):
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 固定大小 | 按 token/字符数切,带重叠(overlap) | 简单、均匀 | 可能切断语义 |
| 按句子/段落 | 以标点或空行为界 | 保留句子完整 | 长短不一 |
| 递归切分 | 先按标题,再按段、句逐级切 | 兼顾结构语义 | 实现稍复杂 |
| 语义切分 | 按 embedding 相似度断点 | 语义边界最优 | 需额外计算 |
经验法则:chunk 大小常取 256--512 token,重叠 10--20%,既能保留上下文又避免碎片化。
步骤 3:向量化(Embedding)
每个 chunk 用一个嵌入模型(如 BGE、E5、OpenAI text-embedding)编码成高维向量。语义相近的文本,向量在空间中距离更近------这是检索的数学基础。
步骤 4:入库
把 chunk 文本 + 向量 + 元数据(来源、页码) 写入向量数据库(FAISS、Milvus、Qdrant、pgvector 等)。
2.2 在线检索---生成:查询 → 召回 → 重排 → 拼接 → 生成
步骤 1:查询向量化:用户问题同样编码成向量。
步骤 2:召回(Retrieve)
- 向量检索(Dense):用近似最近邻(ANN,如 HNSW、IVF)找最相似的 top-k chunk。
- 关键词检索(Sparse):如 BM25,基于词频匹配,擅长专有名词、编号、精确术语。
- 混合检索(Hybrid) :二者加权融合,兼顾"语义相关"与"字面命中"(见配图
diagram_54右)。实践中混合检索的召回质量通常优于单一方式。
步骤 3:重排(Rerank)
召回的 top-k(如 20)再经一个 Cross-Encoder 重排模型精排,取最相关 top-n(如 4--6)送入上下文。重排模型对"查询---文档"对联合编码,排序精度高于向量余弦,但慢------故只在少量候选上用,是"先粗召、后精排"的两段式。
步骤 4:拼接与生成
将重排后的片段拼成上下文,与问题、指令组成最终 prompt(呼应《第21篇》六段式),交给生成模型产出答案,并尽量附上引用来源以增强可溯性。
2.3 RAG 与 Function Calling 的配合
二者不是替代,而是互补:RAG 解决"取知识",Tool Use(第22篇)解决"做动作"。一个常见组合是------把检索本身做成一个工具 。例如定义一个 search_knowledge_base(query) 的 Function Calling 工具,让 Agent 在需要私有知识时主动调用,再基于结果作答。这样 RAG 能力被自然地嵌入第22篇的 ReAct/工具循环,模型自行决定"何时该检索"。
2.4 评估指标
RAG 质量要看两端:
- 检索端 :
Recall@k(正确答案是否在前 k 个被召回)、MRR、NDCG。 - 生成端:答案相关性、忠实度(是否忠于检索内容、不臆造)、引用准确率。
三、实战代码
下面是一段可运行 的迷你 RAG。为免依赖重型库,我们用 numpy 做向量检索,并用一个"假嵌入"函数演示流程------把嵌入替换成真实模型(如 sentence-transformers)即可上线。
python
import numpy as np
# ---------- 1. 离线建库 ----------
def fake_embed(texts):
"""演示用假嵌入:统计词频向量。真实场景换成 BGE/E5 等。"""
vocab = sorted({w for t in texts for w in t.split()})
v = np.zeros((len(texts), len(vocab)))
for i, t in enumerate(texts):
for w in t.split():
v[i, vocab.index(w)] += 1
# L2 归一化,使余弦=点积
return v / (np.linalg.norm(v, axis=1, keepdims=True) + 1e-9)
docs = [
"RAG 通过检索外部知识增强大模型的回答。",
"Function Calling 让模型以结构化方式调用外部工具。",
"向量数据库用于存储和检索文本嵌入。",
"重排模型能对召回结果做更精准的排序。",
]
chunks = [d for d in docs] # 真实场景需先做 chunking
doc_vecs = fake_embed(chunks) # 建库:每个 chunk 的向量
# ---------- 2. 在线检索 ----------
def retrieve(query, top_k=2):
q = fake_embed([query])[0]
sims = doc_vecs @ q # 余弦相似度(已归一化)
idx = np.argsort(-sims)[:top_k]
return [(chunks[i], float(sims[i])) for i in idx]
# ---------- 3. 拼接 + 生成(生成由 LLM 完成,此处模拟) ----------
def rag_answer(query):
hits = retrieve(query, top_k=2)
context = "\n".join(f"[{i+1}] {t}" for i, (t, _) in enumerate(hits))
prompt = (
"根据以下资料回答问题,并标注引用编号:\n"
f"{context}\n\n问题:{query}\n回答:"
)
# 真实场景:把 prompt 发给 LLM
return prompt, hits
q = "RAG 是怎么增强回答的?"
prompt, hits = rag_answer(q)
print("召回片段:")
for t, s in hits:
print(f" ({s:.2f}) {t}")
print("\n构造的 Prompt:\n", prompt)
运行后,retrieve 会基于词频相似度召回与问题最相关的片段(此处命中第 1 条关于 RAG 的描述),rag_answer 把它们拼进带引用标记的 prompt------这一步的 prompt 正是喂给生成模型的输入。将 fake_embed 换成真实 embedding、print 换成 LLM 调用,就是一个生产级 RAG 的最小骨架。
四、优缺点分析
| 维度 | RAG | 微调(Fine-tuning) | 长上下文(直接喂全文) |
|---|---|---|---|
| 知识更新 | 改库即可,实时 | 需重训,慢 | 随输入走,但贵 |
| 私有知识 | 天然适配 | 可注入但成本高 | 可,但占窗口、易丢失 |
| 可溯性 | 高(有引用) | 低(藏在权重) | 中 |
| 成本 | 检索便宜,生成常规 | 训练贵,推理省 | 推理极贵(长输入) |
| 适用规模 | 海量知识库 | 稳定模式/风格 | 中短文档 |
选型建议(决策树式):
- 知识频繁更新 / 体量巨大 / 需引用出处 → RAG。
- 需改变模型"风格、格式、稳定行为模式" → 微调(《第24篇》)。
- 文档短、需整体精读、且不超过窗口 → 长上下文直喂(《第12篇》)。
- 现实多数系统:RAG + 长上下文 组合,用检索把长文档压成相关片段再生成。
五、总结与展望
关键点回顾
- RAG 把"凭记忆答"变为"带着资料答",直接缓解知识截止与幻觉。
- 两条链路:离线建库 (切分→向量化→入库),在线检索---生成(召回→重排→拼接→生成)。
- 切分粒度、混合检索(BM25+向量)、重排是质量三支柱。
- RAG 与 Function Calling 互补:可把检索本身封装为工具,由 Agent 按需调用。
- 选型上 RAG 擅长"海量、易变、需溯源"的知识,微调擅长"稳定行为模式"。
未来方向
RAG 正从"向量检索"走向多模态 RAG (检索图文/表格/音视频)、GraphRAG (把知识建图、做多跳推理),以及Self-RAG(模型自决"是否检索、检索什么、答案是否够好")。它与第22篇的工具生态、下一篇的微调,共同构成应用落地的三块基石。
下期预告:《第24篇:Fine-tuning 实战------全参数微调 vs 参数高效微调》。我们将讲解何时该把知识"烧"进权重,LoRA/QLoRA 如何以极小代价微调,以及数据集构建与训练技巧。
参考资料
- Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, 2020(RAG 原论文)
- Gao et al., Retrieval-Augmented Generation for Large Language Models: A Survey, 2023
- 本博客:《第03篇 Transformer 架构》《第04篇 预训练与微调》《第12篇 长上下文技术全景》《第17篇 模型幻觉与评估》《第21篇 Prompt Engineering》《第22篇 Function Calling》
延伸讨论
- 思考题:你正在做的系统里,哪些回答其实"本应来自最新文档"却依赖模型记忆?这些是不是 RAG 的高价值落点?
- 实践作业 :把本文的
fake_embed换成sentence-transformers的真实模型,用一个自己的 txt 文档跑通端到端 RAG。 - 读者问答:你在 RAG 落地中踩过最深的坑是什么?(切分碎了?召回错了?上下文太长?)欢迎评论区分享,我会在下一篇结合微调一并回应。