為什麼需要 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 點。內容經審核後公開。
正在載入討論…