RAG:检索增强生成
一句话定位:RAG(检索增强生成)让模型回答之前先查资料——用检索到的原文兜住模型的幻觉,是知识密集应用的第一台整车。前置:Embedding 与向量化——检索的引擎已经造好,这里装车。
模型什么都懂,除了你的数据
通用大模型训练时读过整个互联网,问它「什么是机器学习」对答如流。
可你问它「我们公司的退款政策第三条是什么」,它要么答不上,要么更糟——一本正经地编一个看起来像真的答案(这就是幻觉:凭空编造)。模型的参数里没有你的私有数据,而把文档全部塞进提示词又装不下。
矛盾在这:模型会答但不知情,文档知情但在模型外面。直接把文档全贴给模型?几十页的提示词先撑爆窗口,再稀释注意力——而且每问一次都要贴一遍,成本按次计。
RAG 的解法是让两边合作:先从文档里把相关段落找出来,只把这几段塞进提示词,再让模型照着答——检索负责知情,生成负责表达。资料更新了重新建一次库,答案自动跟上,模型一行代码不用动。
图:朴素 RAG 管线——①②③ 离线建库一次,④⑤ 在线每问必跑;top-k 即「取最相似的前 k 段」;模型只负责照着找回的段落说话
渐进拆解:开卷考试怎么抄才对
类比:开卷考试
RAG 就是让模型参加一场开卷考试。闭卷考试(纯提示词)全凭记忆,记不住就编;开卷考试允许翻书——但有个前提:你得先翻到那一页,还得看懂那一段。
五段管线对应考试的三个环节:①②③ 是考前把书整理好(章节、目录、索引);④ 是考场上翻书——按题意定位到相关段落;⑤ 是照着书写答案。开卷考试的分数瓶颈几乎总在「翻书」这一步:翻错页(检索不到)、翻到相似但不对的页(排序错)、翻到了却没用书上的话答(自由发挥),都是常见失分方式。
骨架与两个旋钮
朴素管线的骨架示意(split / embed / store / model 均为示意接口,落地时换任一 embedding 服务与向量库即可),核心就两大块三个动作——入库、检索、生成:
# 离线:① 解析出的 docs → ② 切块(每 500 字一段、相邻重合 50 字,即 chunk)→ ③ 向量化入库
chunks = [c for doc in docs for c in split(doc, size=500, overlap=50)]
vectors = [embed(c) for c in chunks]
store.add(chunks, vectors)
# 在线:④ 检索 + ⑤ 生成
def rag_answer(question):
hits = store.search(embed(question), top_k=3) # ④ 最近邻 3 段
context = "\n---\n".join(h[0] for h in hits)
prompt = f"仅根据以下资料回答,找不到就说不知道:\n{context}\n\n问题:{question}"
return model.chat(prompt) # ⑤ 照着资料答
十几行就能看清 RAG 的骨架——它也是所有花哨 RAG 变体(混合检索、重排、查询改写)的公共底座,后面每一层优化都是在给这十几行打补丁。
两个参数值得单独说。块大小是最先要调的旋钮:块太大(2000 字),一段里混了多个主题,检索时向量被稀释、命中不精准;块太小(100 字),上下文断裂,「 previous 段说的情况」这类指代全部失效。500 字上下是个常见起点,但答案的粒度才是真正的标尺——块应该大到装下一个完整的意思单元,小到一段只说一件事。top-k 是第二个旋钮:k 越大漏得越少,但噪音段落越多、提示词越贵——k 的取值要和上下文预算联动(见Context 工程),不是越大越好。
再看两种回答的对比,体会「有据可依」的差别。同一个问题「发票开错了怎么换?」——不带检索的模型按通用经验答「联系商家重新开具即可」,语气笃定,但你的公司流程里其实要先在系统提交换开申请;带检索的回答会先引用政策原文第 5 条,说出系统入口和时限,甚至带上「超过 90 天需走特殊审批」这种只存在于你们文档里的细节。前者是「平均水平」,后者才是「你的公司」。
场景走读:40 页政策库
用一个具体场景把五段串起来——假设你在给客服系统建「退款政策库」,原始资料是 40 页 PDF 政策文档。离线与在线的分工:
| 维度 | 离线建库 | 在线答题 |
|---|---|---|
| 跑几次 | 做一次,资料更新才重建 | 每问一次 |
| 做什么 | ①解析把 PDF 抽成纯文本(表格和页眉页脚是最常见的脏数据来源);②切块按每段 500 字、相邻重合 50 字切开——重合量是防「一句话被拦腰斩断」的保险:关键条款恰好跨块时,至少有一个块保有完整上下文;③每块向量化后入库,40 页文档约切出 80 个块 | 用户问「过了七天还能退吗」,④问题向量化后检索,找回最像的 3 块——恰好是「七天无理由」条款和「特殊商品除外」条款;⑤把这两块塞进提示词,模型照着条款回答,还顺带把「除外情形」也讲清楚 |
| 延迟特征 | 批处理,几分钟建完,慢一点也无所谓(几十分钟几小时都可接受) | 用户在等:检索通常几十毫秒,延迟大头在生成 |
同样的 40 页文档,不加检索直接问,模型只能按它训练时见过的「一般电商政策」编一个平均答案——可能对,但不是你们公司的政策。
延迟的不对称决定了优化方向:宁可建库时多花功夫(更好的切块、清洗),也别把脏数据留给每一问。
大白话与技术定义
RAG = 先查后答。模型的一个很常见的翻车诱因是「没资料硬答」,先查后答把「硬答」的场合换成了「有据可依」。
检索增强生成(Retrieval-Augmented Generation)将外部知识库检索与生成模型结合:文档离线切分为语义块(chunk)、向量化入库;查询时把问题同样向量化,取最相似的 top-k 片段填入提示词上下文,由模型基于片段生成答案,从而把参数内的隐式知识替换为可更新的显式知识。该范式 2020 年由学界提出后成为知识密集型任务的主流架构。
两个工程细节值得提前知道。相似度度量除余弦外还有内积与欧氏距离,向量归一化后余弦与内积等价——选哪个通常跟随 embedding 模型的官方建议,训练时用什么目标就对齐什么度量。大规模库的检索走的是近似最近邻(ANN)索引:把向量空间预先分区建图,查询时只搜邻近区域,用极小的漏检概率换百倍的检索速度——这层被向量库封装,应用层无感,但要知道它的存在:检索结果偶尔「漏掉最像的一条」,多半是近似性在起作用,不是代码写错了。
动手:不连网,把朴素管线整个跑通
上一节的四个示意接口里,split / embed / store 三个在这里换成真身、model 一步外置:不连任何服务、不装向量库,一块内存矩阵加纯 numpy 把 ①→⑤ 走完——embedding 用玩具级的 TF-IDF(Embedding 篇里「字面相似 · 关键词的地盘」那一路),换真服务时只需改一个函数。三段代码按顺序在同一个 Python 会话里跑,先是离线建库(①②③):
import re
import numpy as np
from collections import Counter
# ① 解析:fixture——三篇迷你「公司文档」(真实场景这步是把 PDF/网页抽成纯文本)
docs = ["本公司支持七天无理由退货。自商品签收之日起七日内,买家可申请退货退款,商品需保持完好。食品、定制类商品不支持无理由退货。",
"发票开错时,需先在订单详情页提交换开申请,注明正确抬头与税号,审核通过后一到三个工作日内重新开出。",
"会员等级按季度积分计算,积分可在积分商城兑换优惠券。会员退订后,剩余积分将于三十日内清零。"]
# ② 切块:定长窗口 + 相邻重合(正文「500 字 / 50 字」的微缩版)
def split(text, size=35, overlap=8):
step = size - overlap
return [text[i:i+size] for i in range(0, max(len(text)-overlap, 1), step)]
# ③ 向量化:TF-IDF——零依赖的玩具 embedding
def fit_embed(chunks):
toks = lambda s: re.findall(r'[\u4e00-\u9fff]', s) # 玩具分词:单字
df = Counter(w for c in chunks for w in set(toks(c)))
idx = {w: i for i, w in enumerate(df)}
idf = np.array([np.log((len(chunks) + 1) / (n + 1)) + 1 for n in df.values()])
def embed(text): # 查询与文档共用同一把尺
v = np.zeros(len(df))
for w in toks(text):
if w in idx: v[idx[w]] += 1
v = v * idf
return v / np.linalg.norm(v) if v.any() else v # 归一化后余弦 = 内积
return embed
chunks = [c for t in docs for c in split(t)] # ①② 跑完,得到块清单
embed = fit_embed(chunks) # ③ 学出词表与 idf
store = np.array([embed(c) for c in chunks]) # ③ 入库(内存矩阵当向量库)
# ④ 检索:问题变向量,取最近邻 top-k(在线,每问必跑)
def search(qv, store, top_k=2):
return np.argsort(-(store @ qv))[:top_k]
# ⑤ 生成:把命中段落拼进提示词——打印出来发给任一对话模型,就是完整的 RAG
def build_prompt(question, hits, chunks):
context = "\n---\n".join(f"[{i+1}] {chunks[i]}" for i in hits)
return f"仅根据以下资料回答,找不到就说不知道:\n{context}\n\n问题:{question}"
question = "过了七天还能退吗"
hits = search(embed(question), store)
print(f"建库 {len(chunks)} 块;命中块序号:{[int(i) for i in hits]}")
print(build_prompt(question, hits, chunks))
实测输出(相似度上「七天无理由退货」的第一块以 0.43 明显领先第二名 0.11):
建库 6 块;命中块序号:[0, 1]
仅根据以下资料回答,找不到就说不知道:
[1] 本公司支持七天无理由退货。自商品签收之日起七日内,买家可申请退货退款,
---
[2] 可申请退货退款,商品需保持完好。食品、定制类商品不支持无理由退货。
问题:过了七天还能退吗
读法有三层:
- 检索是对的:问题里的「七天」把「七天无理由退货」的两块顶到最前——在这个 fixture 上,字面重合够用。
- 重合的保险看得见:块边界把「买家可申请退货退款,商品需保持完好」拦腰斩断,相邻重合让第二块从「可申请退货退款」重新接上——正文说的「防拦腰斩断」眼见为实。
- 玩具的边界也看得见:TF-IDF 只认字面,把问题换成「钱怎么回来」,词表里一个字都对不上,检索会静默返回按序号排的无关节选——朴素管线的「召回不到」就是这样不报错地发生的。语义级检索要换 Embedding 篇的真模型,换真服务只改
embed一个函数、store换向量库,管线形状不动。
最后一步 ⑤ 把打印出的提示词发给任一对话模型即完成——打印出来的这段文字,就是模型实际收到的全部上下文。
边界与取舍:朴素管线天然解决不了的三个问题
朴素管线能跑,但有三类失效是它的架构固有的,怎么调参数都治不了——每一类都对应一层进阶优化:
图:三类失效与各自的补丁方向——调块大小、调 top-k 都治不了,只能加优化层(进阶篇)
- 该召回的没召回:资料用了另一套术语——工程师的文档管这叫「订单逆向流程」,客服话术里叫「退货退款」,用户嘴里是「钱怎么回来」,三套说法指的是同一件事,向量空间里却可能是三团距离不近的点。朴素向量检索对此无解,需要查询改写与混合检索补位(进阶篇)。
- 召回的排错了序:向量相似度只能保证「意思近」,不能保证「最相关」——真正有用的段落排在最后,相似但无用的段落反而把模型带偏。需要专门的重排模型(rerank)重打分(进阶篇)。
- 答案没有依据:模型拿到段落仍可能自由发挥——混入训练记忆、综合过度、干脆无视资料,管线没有「答案必须出自哪一段」的约束机制。需要引用标注(citation)与证据校验(进阶篇)。
这三个问题的解法(查询改写、混合检索、重排、引用校验)全部在RAG 进阶展开——先把朴素管线亲手跑通,进阶的每一个补丁才有对照物。
什么时候连 RAG 都不需要:
- 问题能靠模型参数知识答对(通用常识)——检索是空转;
- 数据量小到能全塞进上下文窗口(几十条)——直接给全文效果更好;
- 要的是实时计算——问「现在的库存还有多少」不该靠检索文档回答,那是工具调用(Function Calling)的活:RAG 检索的是静态知识,不是动态状态。
RAG 的适用前提始终是:知识在模型外面、量大到装不下、又必须答得有据。
自测
合上文章先自己答,再展开对照:
朴素 RAG 管线的哪三个质量问题它天然解决不了?
①召回不到:用户的说法与资料的术语体系岔开,向量检索找不到本该命中的段落——需要查询改写与混合检索。②排序错:top-k 找回了相关段落,但最有用的没排到前面,相似但无用的段落把生成带偏——需要 rerank 重排。③答案无据:模型拿到段落仍可能自由发挥,答案混入参数记忆或过度综合,没有「出自哪一段」的约束——需要引用标注与证据校验。三类失效都是朴素架构固有的,调块大小、调 top-k 都治不了,只能加优化层。
参考与延伸
- 《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(2020-05):RAG 范式的原始论文 — arxiv.org/abs/2005.11401
- Anthropic《Effective Context Engineering for AI Agents》(2025-09-29):检索内容如何进入上下文预算的取舍原则 — anthropic.com/engineering/effective-context-engineering-for-ai-agents
下一站:RAG 进阶——给朴素管线打上切块谱系、查询优化、混合检索、重排四层补丁,再用评估验收补丁真的有效。