怎麼知道 RAG 好不好
把 RAG 的評估拆成檢索和回答兩半。檢索看命中率,回答讓另一個模型當評委,對照參考答案判斷對錯、對照資料判斷是否忠實。評委判了 60 次,我逐條核對,發現它自己也會錯。
- 約 45 分鐘
- 難度:進階
- 實測:2026-09-14 deepseek-flash,deepseek-v4-pro(評委)
程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。
到目前為止,我們評估的都是"檢索找沒找對"。但使用者最終看到的是回答。檢索全對,回答也可能錯:模型可能讀漏了資料,可能把兩段資料混在一起,也可能忍不住加了自己的"知識"。反過來,檢索沒找到最準的那一塊,回答也可能是對的。
所以 RAG 的評估要分成兩半:檢索,和回答。這一課講怎麼做這兩半,重點是回答的評估,以及它的一個大坑。
兩半分開評
分開評的好處是出了問題知道去哪裡修:
| 檢索 | 回答 | 說明 | 該修哪裡 |
|---|---|---|---|
| 對 | 對 | 正常 | |
| 錯 | 錯 | 沒找到資料,模型也答不對 | 檢索:切分、改寫、檢索方法 |
| 對 | 錯 | 資料給對了,模型沒用好 | 生成:提示詞、模型 |
| 錯 | 對 | 可能是模型憑記憶答對了 | 看看是不是違反了"只根據資料" |
只看最終回答的對錯,你分不清是第二種還是第三種,就只能亂改一氣。
檢索的評估前面已經做好了:第 3 課的命中率和 MRR。這一課做回答的評估。
回答要評什麼
對 RAG 的回答,最常看的是兩件事:
- 正確性:回答對不對。需要一個參考答案來對照。
- 忠實度:回答裡的每個說法,是不是都能在檢索到的資料裡找到依據。它不管答案對不對,只管有沒有"越界"。
忠實度單獨拿出來評,是因為它能發現一種隱蔽的問題:回答碰巧是對的,但依據是模型自己的記憶,不是資料。這次對了,下次就可能錯,而且你沒法追溯。
準備參考答案
給第 3 課那 20 道題每題寫一個參考答案。每一個我都是照著檢索評估裡那一段原文寫的,比如:
{"question": "怎么关闭 SSL 证书校验?", ..., "reference": "传 verify=False,比如 httpx.get(url, verify=False);用 Client 时在创建客户端时传入。"}
{"question": "httpx 和 requests 在处理重定向上有什么不一样?", ..., "reference": "requests 默认跟随重定向,httpx 默认不跟随;httpx 要显式传 follow_redirects=True(单个请求或 Client 上都可以)。"}
參考答案不需要寫得很長,寫清楚必須包含的要點就行。但後面會看到,參考答案怎麼寫,會直接影響評分。
讓模型當評委
20 道題,每題兩個回答(一個不查資料、一個用 RAG),人工逐條判斷很累。常用的辦法是讓另一個模型來判斷,叫模型評委(LLM-as-a-judge)。評委用了更強的 deepseek-v4-pro,並且開啟思考:
def judge_correct(question, reference, answer):
return judge(f"""判断"回答"是否正确地回答了"问题"。以"参考答案"为准:回答包含参考答案的要点、且没有和它矛盾的内容,就算正确。
回答比参考答案多说了一些内容没关系,只要多说的部分没有错误。
输出 json:{{"correct": true 或 false, "reason": "一句话理由"}}
问题:{question}
参考答案:{reference}
回答:{answer}""")
def judge_faithful(context, answer):
return judge(f"""判断"回答"里的每一个事实性说法,是否都能在"资料"里找到依据。
回答说"资料里没有"之类的话不算事实性说法。只要有一处说法在资料里找不到依据,就判为不忠实。
输出 json:{{"faithful": true 或 false, "unsupported": "找不到依据的说法,没有就写空字符串"}}
资料:
{context}
回答:{answer}""")
幾個設計要點:
- 評委和被評的不是同一個模型。用同一個模型給自己打分,容易對自己的錯誤視而不見。
- 要求輸出理由。不只要 true 或 false,還要一句話說明為什麼。後面你會看到,理由是發現評委出錯的唯一線索。
- 判斷標準寫清楚。"多說了沒關係,只要多說的部分沒錯"這一句,是為了避免評委因為回答比參考答案詳細就判錯。
judge 函式用第 02 模組第 4 課的 JSON 模式呼叫評委。完整程式碼在 code/04-rag/rag_eval.py,一次執行大約 80 次呼叫,花費約 0.1 美元。
評委的判決
不查资料:答对 15/20
RAG: 答对 19/20,忠实于资料 19/20
[不查资料答错] 响应是 404 或 500 时,怎么让它直接抛异常?
评委:回答中“3xx不会抛”与参考答案“状态码不是2xx时会抛出”矛盾
[不查资料答错] httpx 和 requests 在处理重定向上有什么不一样?
评委:回答错误地声称httpx默认跟随重定向,与参考答案中“httpx默认不跟随”相矛盾。
[不查资料答错] 怎么限制连接池里最多同时有多少个连接?
评委:回答遗漏了参考答案中默认 max_connections=100 这一要点,未完整包含参考答案的默认值信息。
[不查资料答错] 怎么显示下载进度?
评委:回答未使用参考答案中的 response.num_bytes_downloaded 属性,而是手动累计长度
[不查资料答错] 网页返回的中文是乱码,怎么指定解码用的字符集?
评委:回答未提及参考答案中的 default_encoding 参数指定字符集的方法
[RAG 答错] httpx 和 requests 在处理重定向上有什么不一样?
评委:回答中关于 requests 暴露的属性 response.next 的描述有误,requests 并没有该属性。
[RAG 不忠实] 怎么显示下载进度?
找不到依据:显示下载进度需要用流式响应(streaming),并检查 `response.num_bytes_downloaded` 属性
看起來結論很清楚:RAG 把正確率從 15/20 提到了 19/20。但先別急著下結論。每一條"答錯",我都去看了原始回答,並對照 httpx 的文件和原始碼核實。
逐條核對評委
"3xx 不會拋",評委判對了。不查資料的回答說 raise_for_status() 遇到 3xx 不會拋異常。httpx 原始碼 _models.py 裡的 raise_for_status 是這樣寫的:只要不是 2xx(is_success 為假),就丟擲 HTTPStatusError,3xx 的錯誤型別叫 "Redirect response"。所以回答確實錯了。
重定向,不查資料的回答錯了,評委判對了。它又一次說 httpx 預設跟隨重定向,還說"0.20+ 之後預設跟隨",和 RepoBot v1 犯的錯一模一樣。
連線池,評委判得太嚴了。不查資料的回答正確地說出了用 httpx.Limits(max_connections=..., max_keepalive_connections=...),只是沒提預設值是 100。使用者問的是"怎麼限制",沒問預設值是多少。問題出在我的參考答案裡順手寫了"預設最多 100 個連線",評委就把它當成了必須包含的要點。
下載進度,評委判得有道理。不查資料的回答用 len(chunk) 手動累加已下載的位元組數。httpx 文件專門說過,開啟了壓縮時,解壓後的內容長度和實際下載的位元組數對不上,所以要用 response.num_bytes_downloaded。回答的做法在沒有壓縮時能用,但不是正確的做法。
編碼,評委判錯了。不查資料的回答說:在訪問 response.text 之前,設定 response.encoding = "gbk"。我查了 httpx 原始碼,Response.encoding 有一個 setter,允許在讀取 text 之前設定編碼(讀取之後再設定會丟擲 ValueError)。這是完全正確的另一種做法。評委判錯,理由是"未提及參考答案中的 default_encoding",它把參考答案當成了唯一正確的答案。
RAG 的重定向回答,評委判錯了。評委說"requests 並沒有 response.next 這個屬性"。可 httpx 文件 compatibility.md 第 50 行原文就是:"The requests library exposes an attribute response.next, which can be used to obtain the next redirect request." RAG 的回答照著文件寫,是對的。評委沒看資料,憑自己的記憶判斷,結果自己記錯了。
RAG 的下載進度,"不忠實"判錯了。我重新檢索了這道題,取回的前兩塊都來自 advanced/clients.md,裡面都有 num_bytes_downloaded,原文就寫著用流式響應並檢查這個屬性。回答完全忠於資料,評委沒看仔細。
核對之後的結果
| 評委給的 | 我核對後的 | |
|---|---|---|
| 不查資料 | 答對 15/20 | 答對 17/20 |
| RAG | 答對 19/20 | 答對 20/20 |
| RAG 忠實度 | 19/20 | 20/20 |
結論的方向沒變:RAG 明顯比不查資料好,不查資料的回答在兩道題上犯了真正的錯誤。但具體的數字變了,而且評委在 60 次判斷裡判錯了 3 次,還有 1 次判得過嚴。
這 4 次問題可以歸成兩類:
- 評委不用給它的材料,而用自己的知識判斷。
response.next那條就是。評委也是一個大模型,它也會記錯。 - 把參考答案當成唯一答案。編碼和連線池那兩條都是。有多種正確做法時,參考答案只寫了一種,評委就會把其他正確做法判錯。
怎麼讓評委更可靠
- 永遠抽查。評委的判決不能直接當成結論。至少把所有"錯誤"都人工核對一遍,再隨機抽一些"正確"的看看。
- 要求評委給理由。這次能發現問題,全靠理由。"requests 並沒有該屬性"這句理由一看就可疑,一查就知道評委錯了。
- 參考答案寫要點,並允許其他正確做法。在提示詞裡說明"參考答案之外的其他正確做法也算對"。參考答案裡只寫問題真正問到的內容。
- 忠實度評委要看資料。提示詞裡強調"只根據資料判斷,不要用你自己的知識"。
- 用人工標註校準評委。人工標註幾十條,看評委和人的判斷有多一致,一致率太低就改評委的提示詞。06 模組第 2 課會系統地講這件事。
這一課的結論
- RAG 的評估要分成檢索和回答兩半,分開才知道問題出在哪。
- 回答評估最常看正確性和忠實度。
- 模型評委能大大節省人力,但它會錯,而且錯得很自信。評委的輸出是需要核對的資料,不是最終結論。
練習
- 修改
eval_qa.jsonl裡連線池和編碼兩道題的參考答案,去掉問題沒有問到的內容,並在評委的提示詞里加上"參考答案之外的其他正確做法也算對",重新執行rag_eval.py。評委的判決變了嗎? - 在忠實度評委的提示詞里加上"只根據資料判斷,不要使用你自己的知識",重新執行,看下載進度那道題的判決有沒有變化。
- 挑 5 道題,自己當評委,判斷不查資料的回答是否正確。和模型評委比,你們有幾道題判斷不一致?
自測
1. 為什麼 RAG 的評估要把檢索和回答分開?
只看最終回答,分不清錯誤出在哪一步:是沒找到正確的資料,還是資料給對了但模型沒用好。分開評估,就知道該去改檢索(切分、改寫、檢索方法),還是改生成(提示詞、模型)。
2. 一個 RAG 回答是正確的,但忠實度評估顯示它"不忠實"。這說明什麼?
說明回答裡有些內容在檢索到的資料裡找不到依據,很可能來自模型自己的記憶。這次碰巧是對的,但換個問題可能就錯了,而且沒法追溯來源。當然,也可能是評委判錯了,需要人工核對。
3. 模型評委判一個回答"錯誤",理由是"沒有提到參考答案裡的某個方法"。你應該怎麼做?
先看回答用的方法是否也是正確的。參考答案往往只寫了一種做法,評委可能把其他正確的做法也判成錯誤。核實後如果回答是對的,就修改評委的提示詞(允許其他正確做法)或者修改參考答案。
提問與討論
這一課沒看懂的地方,在這裡問。看到別人的問題,也歡迎你來回答。
提問 +3 點,回答別人 +6 點。內容經審核後公開。
正在載入討論…