为什么需要 RAG
同一个问题,不给资料时模型答错,把相关的一段文档放进提示词后就答对了。讲清楚 RAG 的基本流程、它解决什么问题,以及它不适合的场景。
- 约 25 分钟
- 难度:进阶
- 实测:2026-09-14 deepseek-flash
上一课的 RepoBot v1 在"httpx 默认会不会跟随重定向"这个问题上答错了,还编了一段版本历史来圆。改提示词、开思考都只能减少这类错误,不能根除,因为模型只能凭记忆回答,而它的记忆里 httpx 和 requests 是混在一起的。
人遇到这种问题会怎么做?去翻文档。这一课就让模型也"翻文档"。
实验:给它一段文档
httpx 的文档里有一篇 compatibility.md,专门讲它和 requests 的区别,其中有一节就叫 "Redirects"。我先用第 2 课会讲的切分函数把这一节找出来,然后同一个问题问两次:一次不给任何资料,一次把这一节文档放进提示词。
section = next(c for c in split_by_heading(load_docs()["compatibility.md"]) if c.startswith("## Redirects"))
answer, tokens = ask([{"role": "user", "content": QUESTION + "用两三句话回答。"}])
prompt = f"""根据下面的 httpx 文档片段回答问题。文档里没有提到的,就说文档里没有。
<doc>
{section}
</doc>
问题:{QUESTION}用两三句话回答。"""
answer, tokens = ask([{"role": "user", "content": prompt}])
找到的文档片段是这样的:
## Redirects
Unlike `requests`, HTTPX does **not follow redirects by default**.
We differ in behaviour here [because auto-redirects can easily mask unnecessary network
calls being made](https://github.com/encode/httpx/discussions/1785).
You can still enable behaviour to automatically follow redirects, but you need to
do so explicitly...
```python
response = client.get(url, follow_redirects=True)
```
Or else instantiate a client, with redirect following enabled by default...
```python
client = httpx.Client(follow_redirects=True)
```
两次的回答(完整代码见 code/04-rag/why_rag.py,你的措辞会不一样):
== 不给资料(输入 19 词元):
会,httpx 默认会自动跟随重定向(最多 20 次)。可以在请求中用 `follow_redirects=False` 关闭,或用 `max_redirects` 调整次数。
== 给了文档片段(输入 169 词元):
不会。文档明确说明 httpx 与 `requests` 不同,**默认不跟随重定向**。如果想自动跟随,必须显式设置,例如在请求中用 `follow_redirects=True`,或在创建客户端时用 `httpx.Client(follow_redirects=True)`。
不给资料,答错了,和 RepoBot v1 犯的是同一个错误。给了一段 150 个词元的文档,立刻答对了,还说明了为什么、怎么开启。多花的钱是 150 个输入词元,按第 01 模块第 4 课的价格,大约 0.00005 美元。
这就是 RAG 的全部思想:先找到相关的资料,再让模型照着资料回答。
RAG 是什么
RAG 是 Retrieval-Augmented Generation 的缩写,中文一般叫检索增强生成。名字很长,拆开就是三步:检索(retrieval)、增强(augmented,把找到的资料加进提示词)、生成(generation)。
上面的实验里,"找到 Redirects 那一节"是我手动做的,我事先知道答案在哪。真正的 RAG 系统要自动完成这一步:用户提出任何问题,程序都能在一大堆文档里找出最相关的几段。完整的流程分成两部分:
提前准备(只做一次,文档更新时再做):
文档 ──▶ 切成小块 ──▶ 为每块建立索引(向量、关键词)──▶ 存起来
(第 2 课) (第 3、4 课)
每次提问时:
用户问题 ──▶ 检索:找出最相关的几块 ──▶ 把这几块和问题一起放进提示词 ──▶ 模型回答
(第 3、4 课) (第 5 课:要求它注明引用)
这个模块接下来的几课,就是把这张图里的每个方框做出来:第 2 课切分,第 3 课向量检索,第 4 课关键词检索和两者的融合,第 5 课让模型带着引用回答,第 6 课评估整个系统好不好,第 7 课把它们装进 RepoBot。
RAG 解决了什么
模型不知道的东西。你公司的内部文档、你的项目代码、上周刚发布的新版本说明,模型训练时都没见过。RAG 把它们现场交给模型。
模型记错的东西。httpx 重定向这个例子就是。模型"知道" httpx,但记混了。有了原文,它不用凭记忆。
可以追溯。回答是根据哪几段资料得出的,程序是知道的,可以展示给用户。用户可以点开原文核对,出了错也能查到是检索错了还是模型理解错了。
更新很便宜。文档改了,重新处理改动的那几页就行。相比之下,想通过训练把新知识"教"给模型,成本高得多,而且效果不可靠(第 10 模块第 1 课会讲为什么微调不适合灌知识)。
为什么不把所有文档都塞进去
第 01 模块第 4 课做过这个实验:httpx 的全部文档大约 2.9 万个词元,全部放进去,模型也能答对。那为什么还要费劲做检索?
因为每次都塞 2.9 万个词元,要比只塞相关的几百个贵几十倍、慢得多,而用户每次只需要其中很小的一部分。httpx 的文档还算小,你公司的知识库可能有几千万个词元,根本放不进上下文。另外,无关的内容放得越多,模型被干扰、找错地方的可能性也越大。
所以两种办法各有适用范围:
| 资料规模 | 做法 |
|---|---|
| 几千到几万个词元,调用不频繁 | 直接全部放进去,简单可靠,还能命中缓存 |
| 更大,或者调用很频繁 | RAG,只放相关的部分 |
先考虑能不能全部放进去。能,就先这么做,别急着上 RAG。
RAG 不适合的情况
- 问题需要综合全部资料。"总结一下这份文档"、"文档里一共提到了几个参数",这类问题需要看全文,检索出几段是不够的。
- 资料本身质量差。文档过时、互相矛盾、写得含糊,RAG 只会让模型照着错的资料回答,而且因为有"出处",看起来更可信。
- 答案不在任何文档里。比如 httpx 的某个行为只在源码里有,文档没写。这时需要让模型去读源码,那是第 05 模块智能体要做的事。
- 需要推理而不是查找。"我这段代码为什么报错",光找文档不够,还要理解用户的代码。RAG 可以提供相关的文档作为辅助,但主要靠模型本身的能力。
练习
- 运行
code/04-rag/why_rag.py,看看你得到的两个回答是什么样的。 - 换一个问题,比如"httpx 的 Response 有 ok 这个属性吗",手动在
compatibility.md里找到相关的一节(提示:搜索 "Checking for success"),修改why_rag.py做同样的对比。 - 把
why_rag.py里放进提示词的文档换成一段无关的内容(比如timeouts.md的某一节),再问重定向的问题。模型会怎么回答?它会不会说"文档里没有提到"?
自测
1. RAG 的三个字母分别代表什么?这三步各做什么?
Retrieval(检索):根据问题找出最相关的资料;Augmented(增强):把找到的资料加进提示词;Generation(生成):模型根据资料生成回答。
2. 资料只有两万个词元,每天被问几十次。你会用 RAG 吗?
一般不会。资料不大、调用不频繁时,直接把全部资料放进提示词更简单可靠,固定的资料放在开头还能命中缓存,费用不高。RAG 适合资料太大放不进上下文,或者调用频繁、全部放进去太贵的情况。
3. 为什么说资料质量差时,RAG 可能让情况更糟?
模型会照着检索到的资料回答。资料是错的,回答就是错的,而且因为附带了"出处",用户会更容易相信它。RAG 的效果受限于资料本身的质量。
提问与讨论
这一课没看懂的地方,在这里问。看到别人的问题,也欢迎你来回答。
提问 +3 积分,回答别人 +6 积分。内容经审核后公开。
正在加载讨论…