知识点思维导图
29 个知识节点
参考资料
Python(26) - RAG 入门
读完后,你应能完成以下任务:
- 绘制“Python(26) - RAG 入门 / 先给锚点:RAG ≈ 让模型「开卷考试」+ 你熟悉的「搜索 → 渲染」”的关键对象与数据流,解释“上下文窗口有限:模型一次能读的 token 有上限(几万到几十万不等),你公司几百份文档塞不下。 -> token 要花钱:输入越长越贵,每次提问都重发全部文档,成本爆炸。 -> 噪声拖垮效果:资料越多越杂,模型越容易被无关内容带偏(「大海捞针」问题,塞太多反而答得更差)。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(26) - RAG 入门 / RAG 全景:离线建索引 + 在线问答,两条流水线”设计正常与异常输入,验证“类比前端:离线建索引 ≈ 打包构建期(把内容预处理好、建好索引,跑一次),在线问答 ≈ 运行时请求(用户每来一次就走一遍检索 + 生成)。”,输出首个偏差位置与回归测试结果。
- 实现“Python(26) - RAG 入门 / 第一步 chunking:为什么要把文档切碎”的最小代码或配置,检验“chunking(切分 / 分块)是 RAG 里最不起眼、却最影响效果的一步。”,输出命令、结果与 Diff,并说明不适用边界。
上一篇(第 25 篇)你已经学会把文字压成一串坐标(Embedding),还会算「两段话语义有多近」。但单有 Embedding 还干不成事。本篇要解决的真实问题是:大模型不知道你公司的内部文档,也不知道今天发生了什么(它的知识有训练截止日期)。你问它「我们产品的退款政策是什么」,它要么瞎编(幻觉),要么说「我不知道」。RAG(Retrieval-Augmented Generation,检索增强生成)就是这件事的标准解法——本质是让模型「开卷考试」:先去你的资料库里检索相关片段,再把片段塞进 prompt 让模型「照着资料回答」。这是目前落地最广的 AI 应用形态(智能客服、文档问答、企业知识库几乎都是它)。
阶段五依赖链对齐:23(调 API)→ 24(结构化输出 + 函数调用)→ 25(Embedding·把文字变坐标)→ 26(本篇·RAG)→ 27(Agent)。本篇默认你已读过第 25 篇,知道 embeddings.create 怎么用、余弦相似度怎么算;这里把它们组装成一条能跑的问答流水线。
一、先给锚点:RAG ≈ 让模型「开卷考试」+ 你熟悉的「搜索 → 渲染」
先建立直觉。把大模型想象成一个博学但记不住你私事的考生:
- 闭卷考试:直接问模型「我们的退款政策是几天?」。它没见过你的文档,只能凭「常识」瞎答 → 幻觉。
- 开卷考试:先翻出政策文档里相关的那一段,连同问题一起递给它:「这是政策原文:『……7 天无理由退款……』,请据此回答」。模型照着抄就行 → 准确。
RAG 干的就是「开卷」这件事,而且整个流程你作为前端其实天天在做——它就是熟悉的「搜索 → 拿数据 → 渲染」模式:
| 前端做过的事 | RAG 里对应的环节 |
|---|---|
| 用户在搜索框输入关键词 | 用户提问(query) |
调搜索接口 /search?q=... 拿到相关结果 |
去向量库检索出最相关的几段资料 |
| 把结果数据塞进模板渲染成页面 | 把资料片段塞进 prompt 模板 |
| 浏览器展示最终页面 | 模型基于资料生成最终回答 |
边界(哪里不一样):前端搜索是关键词匹配(
includes、LIKE %xx%,字面对得上才命中);RAG 的检索是语义匹配——你问「咋退钱」,能命中写着「退款流程」的段落,哪怕一个字都没重合。这正是第 25 篇 Embedding 的价值:把「字面匹配」升级成「意思相近就算命中」。另一个关键差异:搜索结果是直接展示给人看的,而 RAG 的检索结果是喂给模型的中间料,最终答案由模型加工后产出。
1.1 为什么不直接把整个知识库塞进 prompt?
新手第一反应常是:「我把所有文档一股脑贴进 system prompt 不就行了?」三个硬约束让这条路走不通:
- 上下文窗口有限:模型一次能读的 token 有上限(几万到几十万不等),你公司几百份文档塞不下。
- token 要花钱:输入越长越贵,每次提问都重发全部文档,成本爆炸。
- 噪声拖垮效果:资料越多越杂,模型越容易被无关内容带偏(「大海捞针」问题,塞太多反而答得更差)。
所以正确做法不是「全塞」,而是「先检索出最相关的一小撮,只塞这一小撮」。这就是 RAG 的核心思想。
二、RAG 全景:离线建索引 + 在线问答,两条流水线
RAG 拆开看是两个阶段,别混在一起理解:
【离线 · 建索引】(资料入库时跑一次,类比前端的「构建/预渲染」)
原始文档
└─切分(chunking)──> 一堆小片段
└─Embedding──> 每段变成一串向量坐标
└─存入向量库 (id, 原文, 向量)
【在线 · 问答】(用户每次提问时跑,类比「运行时请求」)
用户问题
└─Embedding──> 问题也变成向量
└─去向量库找最近的 top-k 段──> 检索出最相关的几段原文
└─拼进 prompt 模板──> 「这是资料:xxx。请据此回答:用户问题」
└─调大模型生成──> 最终答案
类比前端:离线建索引 ≈ 打包构建期(把内容预处理好、建好索引,跑一次),在线问答 ≈ 运行时请求(用户每来一次就走一遍检索 + 生成)。把这两条线分清,后面代码就不会乱。
本篇按「切分 → 检索 → 生成拼装」三步逐个讲透,最后串成一个能跑的最小 demo。
三、第一步 chunking:为什么要把文档切碎
chunking(切分 / 分块)是 RAG 里最不起眼、却最影响效果的一步。不能拿整篇文档去做 Embedding,必须先切成小片段,原因有二:
- 检索粒度:用户问一个具体问题,命中的应该是「相关的那一段」,而不是「包含答案的整篇 1 万字文档」。整篇喂回去既浪费 token 又引入噪声。
- Embedding 表达力:把 1 万字压成一个向量,语义被「平均」得稀烂(什么都沾一点 = 什么都不像);一小段话压成向量,语义才聚焦。
类比:这就像前端的分页 / 虚拟列表 / 懒加载——不会把十万条数据一次性渲染,而是切成一屏一屏按需取。chunking 就是「把长文档切成可检索的最小单元」。
最常用的切法是固定长度 + 重叠(overlap):
// JS 等价实现,思路一模一样
// text:待切分的长文本;chunkSize:每段目标长度;overlap:相邻段重叠字符数
function splitText(text, chunkSize = 300, overlap = 50) {
const chunks = [] // chunks:存放切分结果的数组
let start = 0 // start:当前片段的起始下标
while (start < text.length) {
chunks.push(text.slice(start, start + chunkSize))
start += chunkSize - overlap // 留 overlap 重叠,防止切断语义
}
return chunks
}
实战里还有更聪明的切法(按段落 \n\n 切、按 markdown 标题切、按句子切),原则是尽量沿「自然语义边界」下刀,别把一句话拦腰斩断。入门阶段用「固定长度 + overlap」足够,先跑通再优化。LangChain / LlamaIndex 这类框架内置了 RecursiveCharacterTextSplitter 等现成切分器,等理解了原理再用框架不迟。
四、第二步检索:向量库就是「语义版的搜索引擎」
切好的片段要先转成向量、存起来,用户提问时再去「找最近的几段」。这一步直接复用第 25 篇的 Embedding 能力。
4.1 离线:把片段灌进库
⚠️ 关键坑:建索引和查询必须用同一个 embedding 模型。向量是模型「私有的坐标系」,A 模型的坐标和 B 模型的坐标不在一个空间里,混用就是拿北京的经纬度去查上海的地图——算出来的距离毫无意义。
4.2 在线:用余弦相似度找 top-k
检索的本质是「问题向量离哪几个片段向量最近」。距离度量最常用余弦相似度(第 25 篇讲过:值越接近 1 越相似)。这里手写一版,把黑盒拆开看:
注意「怎么退钱」和原文「无理由退款」一个字都不重合,但语义相近,所以能命中——这就是语义检索碾压关键词搜索的地方。
4.3 真实场景:用现成向量库,别自己手撸
手写 numpy 版是为了讲清原理。真实项目几万、几百万条片段时,每次都全量算一遍余弦太慢,要用专门的向量库(内置近似最近邻索引 ANN,毫秒级返回)。入门最易上手的是 Chroma:
常见向量库选型(了解即可):Chroma(最轻量,本地起步首选)、FAISS(Meta 出品,纯本地、快,无服务端)、pgvector(给 PostgreSQL 装个扩展就能存向量,已有 PG 的项目最省事)、Milvus / Qdrant / Pinecone(面向规模化、可托管)。原理都一样:存向量 + 近似最近邻检索,换库主要是换 API。
五、第三步生成:把检索结果拼进 prompt
检索拿到相关片段后,最后一步是把它们拼进 prompt,让模型照着回答。这一步回到第 24 篇的 Prompt 工程——核心是用 system prompt 立规矩:「只准根据我给的资料回答,资料里没有就说不知道」。
这段 system_prompt 的拼装就是整个 RAG 的「临门一脚」。注意它和第 24 篇讲的结构化输出、few-shot 完全可以叠加使用——比如还能要求模型「在答案末尾标注引用了哪条资料」,方便做溯源。
六、串起来:一个最小可跑的 RAG
把上面三步合并,就是一个能跑的最小 RAG。整体不到 50 行,结构是「离线建一次索引 + 在线反复问答」:
这条 50 行的流水线,就是市面上绝大多数「文档问答 / 智能客服 / 企业知识库」的内核。剩下的工程化(换持久化向量库、加 chunking 策略、加缓存、加引用溯源、加重排序)都是在这个骨架上长出来的枝叶。
七、前端新手最容易踩的坑
| 坑 | 现象 | 正解 |
|---|---|---|
| 以为 RAG 是「微调模型」 | 想着「拿数据训练模型让它记住」 | RAG 不改模型,只是检索后塞进 prompt。改模型那叫微调(fine-tune),是另一条路、成本高得多 |
| 建库和查询用了不同 embedding 模型 | 检索全是离谱结果 | 两边必须同一个模型,向量是模型私有坐标系,不通用 |
| 不切分,整篇文档做 embedding | 检索命中一大坨、答案飘、token 爆 | 必须先 chunking 切成小片段 |
| chunk 切太大或太小 | 太大引入噪声、太小丢上下文 | 从 300~500 字 + 适当 overlap 起步,按效果调 |
| 以为检索是关键词匹配 | 纠结「问题里没出现这个词怎么办」 | 它是语义匹配,意思相近就能命中,字面不必重合 |
| 不给模型「找不到就拒答」的护栏 | 检索失败时模型脑补、幻觉 | system prompt 明确「资料里没有就说不知道」 |
| 检索质量差却怪模型不行 | 答得不准,第一反应去换更强的生成模型 | RAG 的天花板是检索——没召回到正确片段,再强的模型也答不对(garbage in, garbage out) |
最后这条最关键,单独强调:RAG 答得好不好,七成看检索、三成看生成。 新手总爱在「换更贵的模型」上花力气,但真正的杠杆通常在前半段——切分策略、embedding 质量、top-k 取值、要不要加重排序(rerank)。调 RAG 时,先把「检索到的片段打印出来肉眼看」,确认召回对了,再去管生成。
八、总结
- 先给锚点:RAG ≈ 让模型「开卷考试」+ 你熟悉的「搜索 → 渲染」:上下文窗口有限:模型一次能读的 token 有上限(几万到几十万不等),你公司几百份文档塞不下。 -> token 要花钱:输入越长越贵,每次提问都重发全部文档,成本爆炸。 -> 噪声拖垮效果:资料越多越杂,模型越容易被无关内容带偏(「大海捞针」问题,塞太多反而答得更差)。
- RAG 全景:离线建索引 + 在线问答,两条流水线:RAG 拆开看是两个阶段,别混在一起理解:
- 第一步 chunking:为什么要把文档切碎:chunking(切分 / 分块)是 RAG 里最不起眼、却最影响效果的一步。
- 第二步检索:向量库就是「语义版的搜索引擎」:⚠️ 关键坑:建索引和查询必须用同一个 embedding 模型。
- 第三步生成:把检索结果拼进 prompt:检索拿到相关片段后,最后一步是把它们拼进 prompt,让模型照着回答。
- 串起来:一个最小可跑的 RAG:把上面三步合并,就是一个能跑的最小 RAG。
学完自测
选择所有正确答案;提交后逐项核对判断依据。