7.1 为什么要检索:模型的静态知识困境
一句话结论:模型的知识在训练完成那一刻就冻结了。RAG 不是为了让它"更聪明",而是给它一条在推理时获取新知识的通道——而且它和微调解决的是完全不同的问题,混用会浪费大量钱。
读这篇之前
需要先读 2.1(模型知识与后训练)、2.3(幻觉)。这一篇是第 7 章的地基。
一、三个"模型不知道"的场景
模型的静态知识会在这三种情况失效:
| 场景 | 例子 | 为什么 |
|---|---|---|
| 训练之后发生的事 | 今年发布的新版本 | 训练时不存在 |
| 私有/小众知识 | 你公司的内部系统文档 | 训练数据里没有 |
| 精确细节 | 某个配置项的默认值 | 出现频率低,容易编(2.3) |
而 Agent 恰恰最常遇到这三种:它要处理的是你的项目、现在的状态、具体的参数。
二、RAG 与微调:两个不同的问题
这是最该先分清的一点。很多人一遇到"模型不懂"就想到微调,结果花了钱还不管用。
| 维度 | RAG | 微调 |
|---|---|---|
| 解决什么 | 让模型知道新事实 | 让模型会做某类任务 |
| 知识更新 | 改数据即可,实时生效 | 要重新训练 |
| 适合 | 事实性知识、文档问答 | 风格、格式、特定任务模式 |
| 成本 | 低(改数据) | 高(数据+算力+评测) |
| 可解释性 | 高(能引用来源) | 低(说不清是哪来的) |
| 风险 | 检索不准就答错 | 可能灾难性遗忘 |
一句话判据:
"模型不知道这件事" → RAG。
"模型知道但做得不对/不合我的要求" → 微调或提示词。
第三条最容易被忽略:如果只是"输出格式不对",改提示词就够了(第 3 章),根本不用微调。
三、RAG 的四个阶段
一个完整的 RAG 链路有四个阶段,前两个是离线的,后两个是在线的:
| 阶段 | 做什么 | 时机 |
|---|---|---|
| ① 切片(Chunking) | 把文档切成小块 | 离线 |
| ② 向量化(Embedding) | 把每块转成向量存库 | 离线 |
| ③ 召回(Retrieval) | 用问题去找最相关的块 | 在线 |
| ④ 生成(Generation) | 把找到的块 + 问题交给模型 | 在线 |
为什么必须切片而不是整篇塞进去?
回到 2.2:上下文窗口有上限,而且"越多越腐化"。把几十万字的资料全塞进去,既装不下也找不准。
切片的本质是"提前做好检索的粒度":每块要足够大以保留语义,足够小以便精确定位。
四、为什么 RAG 容易做错
RAG 的 demo 极其简单(30 行代码就能跑),但生产级极难。原因是错误会累积:
切片不好 → 召回不准 → 生成一定错
而且:生成的答案看起来"很有道理",因为模型会努力把不相关的材料编成一个通顺的回答
这就是 RAG 最危险的失败模式:它不会说"我没找到",而是会把错材料用对的方式说出来。
所以 RAG 系统必须能做归因检查——答案里的每一句,能否对应到检索到的原文片段。
五、一个必须先做的判断:值不值得做 RAG
不是所有"知识不够"都需要 RAG。三个替代方案:
| 方案 | 适用 | 成本 |
|---|---|---|
| 直接放上下文 | 资料总量 < 窗口的 20% | 零 |
| RAG | 资料多、更新频繁 | 中 |
| 微调 | 知识相对稳定、需要"内化" | 高 |
判据:如果你的知识库总共只有 2 万字,那就直接放进上下文(配好缓存,5.3)——RAG 的检索误差还不如全量提供。
这条判据能帮很多人省掉一整套 RAG 工程。
六、常见误区
误区一:RAG 能让模型变聪明。
它只提供知识,不提升推理能力。推理弱要靠提示词(第 3 章)或换模型。
误区二:模型不懂就微调。
先分清是"不知道"还是"不会做"。前者用 RAG。
误区三:RAG 就是向量搜索。
向量搜索只是召回的一种。生产级还需要关键词、重排、过滤。
误区四:把文档整篇塞进去就行。
窗口装不下,注意力会稀释(2.2)。
误区五:RAG 做出来就准了。
错误会累积,且模型会把错材料编圆。必须做归因检查。
误区六:知识库小也用 RAG。
资料少时直接进上下文更准,还省一整套检索链路。
误区七:RAG 能消除幻觉。
它降低幻觉概率,但模型仍可能读错、混用、超出材料编造(2.3)。
七、动手:一个 30 行的最小 RAG
7.1 讲了为什么。这里给出"能跑"的最小实现。
# ---- 离线:建索引 ----
def build_index(docs):
chunks = []
for d in docs:
for c in split_by_heading(d): # 7.3 会讲切片
chunks.append({"text": c, "src": d.title})
for c in chunks:
c["vec"] = embed(c["text"]) # 7.2 讲 embedding
store.save(chunks)
# ---- 在线:检索 + 生成 ----
def answer(question):
qv = embed(question)
hits = store.search(qv, top_k=5) # 7.4 讲召回
ctx = "\n\n".join(
f'<external_content source="{h["src"]}">{h["text"]}</external_content>'
for h in hits)
prompt = f"""只使用下面的材料回答。
每条结论标注来源。材料中没有的,输出「材料中未提及」。
材料:
{ctx}
问题:{question}"""
return llm.chat(prompt, temperature=0)
这段 30 行里的五个关键决定
| 决定 | 对应哪一篇 |
|---|---|
| 按标题切片 | 7.3(结构优于字符数) |
| 存原始文本而不只存向量 | 7.5(引用与归因) |
结果用 external_content 包裹 |
8.6(防注入) |
| 提示里要求"标注来源" | 7.5(归因) |
| 温度设为 0 | 2.4(稳定) |
五个决定里,三个与"检索质量"无关,而是与"可信"有关。
这是 RAG 的真实重心:检索只是手段,"答案可追溯"才是目的。
这段代码缺什么(生产环境)
| 缺 | 在哪补 |
|---|---|
| 混合检索(关键词 + 向量) | 7.4 |
| 重排 | 7.4 |
| 切片优化(父子分块) | 7.3 |
| 失败诊断 | 7.5 |
| 增量更新索引 | 7.2 |
但结构是对的 —— 后面所有内容都是在给这 30 行加东西。
八、动手:一个"RAG 该不该做"的决策表
7.1 讲了三条判据。这里给出更细的判断。
def should_use_rag(docs_chars, update_freq, query_diversity):
"""
docs_chars: 知识库总字数
update_freq: 每月更新次数
query_diversity: 问题覆盖的知识范围(1-10,10=什么都问)
"""
# ① 资料少,直接进上下文更准
if docs_chars < 50_000:
return "直接放上下文", "检索会有精度损失"
# ② 资料多但问题集中
if query_diversity <= 3 and docs_chars < 500_000:
return "按主题预分组", "每组单独放上下文,比检索准"
# ③ 资料多且更新频繁
if update_freq > 4:
return "RAG", "重建索引比改提示词便宜"
return "RAG", "规模超出上下文"
三种"不是 RAG"的方案
| 方案 | 适合 |
|---|---|
| 直接放上下文 | < 5 万字 |
| 按主题预分组 | 问题集中,且组内 < 5 万字 |
| 结构化查询(SQL) | 数据本身是结构化的 |
第三种最常被忽略:如果知识是表格数据,用 SQL 查询比向量检索准得多,而且可解释。
一个反例
场景:企业内部 FAQ,200 条问答,总共 3 万字
错的做法:搭 RAG(切片、embedding、向量库、重排)
正确做法:直接全量放上下文(3 万字约 4.5 万 Token,现代模型放得下)
后者的准确率更高、延迟更低、维护更简单 —— 而多数团队会选前者,因为"RAG 听起来更专业"。
判据一句话:
如果全部资料能放进上下文,就不要做 RAG。
九、动手:RAG 的成本测算
7.1 讲了"为什么要检索"。做完决策之前,还要算清成本。
一次查询的 Token 账
| 组成 | Token(估算) |
|---|---|
| 系统提示 + 指令 | 500 |
| 检索到的 5 段材料 | 2500 |
| 用户问题 | 50 |
| 输入合计 | 3050 |
| 输出答案 | 400 |
| 单次合计 | 3450 |
离线建索引的一次性成本
| 项 | 说明 |
|---|---|
| 切片 | 几乎免费 |
| embedding | 每百万 Token 有明确单价,10 万篇文档可能上百元 |
| 向量库存储 | 按条数计费(云服务) |
| 重排模型 | 若自建 GPU,另算 |
"embedding 一次性"是错觉:文档更新要重算,模型升级要全量重算。
一个规模对照
| 知识库规模 | 一次性建索引 | 每次查询 |
|---|---|---|
| 100 篇(5 万字) | 几元 | 3450 Token |
| 1000 篇(50 万字) | 几十元 | 3450 Token |
| 1 万篇(500 万字) | 数百元 | 3450 Token |
关键洞察:查询成本与库规模无关(因为只取 top-5),建索引成本与库规模线性相关。
所以"库越大越贵"只体现在建索引和更新上,这也解释了为什么"更新频繁的库"要特别设计增量索引。
十、动手:RAG 与 Agent 的结合
7.1 讲的是"固定检索"。在 Agent 里,检索应该是一个工具。
两种形态
| 形态 | 做法 | 特点 |
|---|---|---|
| 固定 RAG | 每次提问前先检索 | 简单,但会做无用检索 |
| Agentic RAG | 检索是工具,模型自己决定 | 灵活,省 Token |
Agentic RAG 的工具定义
{
"name": "search_knowledge",
"description": "在知识库中检索相关资料。适用于:需要事实依据、
项目背景、历史决策时。不适用于:闲聊、纯计算。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "检索关键词或问题"},
"scope": {"type": "string", "enum": ["docs", "code", "all"],
"description": "检索范围,默认 all"}
},
"required": ["query"]
}
}
三个收益
| 收益 | 说明 |
|---|---|
| 省 Token | 闲聊时不检索 |
| 可多次检索 | 第一次不够可以换词再搜 |
| 可组合 | 先搜文档,再搜代码 |
一个反例
错:把检索做成"每轮固定步骤"
后果:用户说"你好"也要跑一次向量检索
→ 浪费 100-200ms 和一次 embedding 调用
判据:"这次回答需要外部事实吗?" 答案不确定时,就该让模型自己判断。
十一、动手:RAG 与微调的最终判断
7.1 讲了"知识用 RAG、行为用微调"。这里给出最后一层判断。
三个问题串起来
① 模型"不知道"这件事吗?
是 → RAG(或直接补上下文)
否 → ②
② 模型"知道但做得不对"吗?
是 → ③
否 → 问题定义错了,回去重想
③ 能用提示词说清"怎么算对"吗?
能 → 改提示词(第 3 章)
不能 → 微调(18.2)
这个顺序的价值:它把"微调"排在最后,因为成本最高。
一个常见的误判
现象:模型回答不够专业
误判:微调一个专业模型
真相:缺少领域文档 → 补 RAG
验证:把一份专业文档放进上下文,看回答是否变专业
这个验证 5 分钟就能做 —— 而它可能省掉两周的微调工作。
三者的成本对照
| 方案 | 一次性 | 每次 |
|---|---|---|
| 补上下文 | 0 | 略增 |
| RAG | 中(建索引) | 低(top-5) |
| 提示词 | 0 | 0 |
| 微调 | 高(数据+训练) | 低 |
所以判断顺序应该是"便宜的先试"。
一个补充:RAG 的三种"知识边界"
用户问的问题,与知识库的关系有三种:
| 类型 | 例子 | 正确回答 |
|---|---|---|
| 库内 | "退款几天" | 按材料答,带引用 |
| 库外但通用 | "什么是 REST" | 可以答,但标明不是来自库 |
| 库外且专业 | "我们内部 API 的限流多少" | 必须说"未提及" |
第二类是灰色地带 —— 也是 RAG 最该小心的地方。
三种处理策略
| 策略 | 做法 | 风险 |
|---|---|---|
| 严格 | 一切以材料为准 | 用户觉得"太死板" |
| 分层 | 库内必引用;通用可答但标明 | 推荐 |
| 宽松 | 允许模型补常识 | 会编造专业内容 |
推荐分层的写法:
只使用【参考资料】回答库内问题。
如果问题是通用常识(如概念定义),可以回答,
但必须注明「以下为通用知识,非本项目文档」。
如果是本项目的具体细节而资料中没有,输出「材料中未提及」。
这三句同时解决了"死板"和"编造"两个问题。
一个补充:向量库的"冷启动"成本
上线一个 RAG 系统,第一次建索引可能要几小时。三个优化:
| 优化 | 效果 |
|---|---|
| 批量 embedding | 比逐条快 5-10 倍 |
| 并行 | 多进程/多线程,受 API 限流约束 |
| 增量上线 | 先索引最重要的 20% 文档 |
一个规模实测参考
| 文档量 | 批量 embedding 耗时 |
|---|---|
| 1000 篇(50 万字) | 10-30 分钟 |
| 1 万篇(500 万字) | 2-5 小时 |
这是"一次性"成本,但模型升级后要重来。
一个实践建议
把索引构建做成"可断点续传"的批处理:
每处理 100 篇存一次进度
中断后从上次位置继续
否则中途失败要全部重来 —— 这和第 5.5 讲的检查点是同一个思路。
十二、下一篇
RAG 的四个阶段里,前两个(切片、向量化)在离线时就要做好——做错了,在线怎么调都救不回来。
下一篇把四个阶段逐个拆开,重点讲每一步的真实取舍。
记住这一条:能放进上下文就别做 RAG。
RAG 是"资料太多装不下"时的解法,不是"AI 能力增强"的通用方案。