前幾天在整理 AI 系統的時候,我一直把注意力放在模型本身,Prompt 怎麼寫、模型怎麼選、輸出怎麼控制,但真的開始碰 RAG 之後才發現,有些看起來像是「模型回答錯」的問題,根本不是模型造成的。
最常見的情況就是:答案明明就在文件裡,AI 卻還是答錯。
第一次碰到這種狀況時很容易直覺認為模型不夠強,接著開始改 Prompt,叫它「請根據資料回答」「不要自行推測」「如果不知道就說不知道」,甚至換一個更大的模型。
但如果真正送進模型的資料本來就是錯的,再怎麼提醒它也沒有用。
所以今天我想把 RAG 拆開來看,尤其是最容易被忽略的 Retrieval。
最簡單的 RAG 流程大概可以寫成:
文件
↓
切成 Chunk
↓
Embedding
↓
存進 Vector Database
↓
使用者提問
↓
搜尋相關 Chunk
↓
交給 LLM
↓
產生答案
平常我們看到的通常只有最前面的問題和最後面的答案,所以回答錯的時候,很自然會把責任全部丟給 LLM。
但中間其實已經做了很多次決定。
文件怎麼切、每段多長、Embedding 用什麼模型、一次取幾筆資料、相似度門檻設多少,這些設定都會決定最後到底有哪些內容能進到 LLM 的 Context。
也就是說,LLM 可能根本沒看過真正的答案。
假設文件裡明明寫著:
申請截止日期為 9 月 30 日。
使用者問:
申請什麼時候截止?
結果 AI 回:
申請截止日期為 9 月 20 日。
以前我看到這個答案,大概會直接開始調 Prompt,但現在我會先去看 Retrieval 到底抓了什麼。
如果搜尋結果抓到的是:
第一階段資料繳交期限為 9 月 20 日。
那其實 LLM 根本沒有機會回答正確。
這屬於 Retrieval 的問題。
但如果搜尋結果明明已經抓到:
申請截止日期為 9 月 30 日。
模型最後卻回答 9 月 20 日,那才比較接近 Generation 的問題。
兩個最後都會呈現成「AI 回答錯誤」,處理方式卻完全不同。
使用者問題
↓
Retrieval 找對資料了嗎?
↓
有 → 檢查 Generation
沒有 → 檢查 Retrieval
這個拆法很簡單,但我覺得它比一直改 Prompt 有用很多。
另一個我以前很容易忽略的地方是 Chunk。
假設原始文件是:
申請資格:
大專院校在學學生皆可參加。
申請期限:
9 月 30 日以前完成線上報名。
繳交資料:
報名後七日內上傳相關文件。
如果切 Chunk 的方式剛好把標題和內容拆開,Vector Database 裡可能會變成:
Chunk A:
申請資格:
大專院校在學學生皆可參加。
申請期限:
Chunk B:
9 月 30 日以前完成線上報名。
繳交資料:
報名後七日內上傳相關文件。
人看得懂 Chunk B 的 9 月 30 日是在講申請期限,但對搜尋系統來說,「申請期限」這個關鍵語意反而留在 Chunk A。
這時候就算原始 PDF 完全正確,資料也確實存在,搜尋還是可能抓不到。
所以 RAG 的「有資料」跟「模型拿得到資料」其實是兩件不同的事。
另一個很直覺的做法是,如果怕搜尋不到答案,那就一次多抓一點。
Top-3 不夠就 Top-5,Top-5 不夠就 Top-10,甚至把一大堆相關內容全部塞進 Context。
但資料變多之後,雜訊也會一起進來。
例如文件同時有:
早鳥申請:9 月 10 日
一般申請:9 月 30 日
補件期限:10 月 5 日
結果公告:10 月 20 日
如果四段全部送進模型,模型還是得自己判斷使用者問的「截止」到底是哪一個。
所以 Retrieval 的目標不是單純「抓很多資料」,而是盡可能把真正需要的資料排在前面,同時減少不相關內容。
如果真的要把 AI Demo 往可以維護的系統推,我覺得至少要能看到:
Query
Retrieved Chunks
Similarity Score
Prompt
Model Output
Final Answer
不然最後只看到一個錯誤答案時,根本不知道問題發生在哪一層。
這也是我這幾天越來越明顯的感覺,AI Engineering 很多工作其實不是在想辦法讓模型「更聰明」,而是在想辦法讓系統出錯時可以知道它到底錯在哪。
RAG 特別明顯。
答案錯了,不一定先換模型;先看看它到底拿到了什麼資料,常常更快找到真正的原因。
明天我想繼續往這裡挖,因為就算 Retrieval 抓到了看起來正確的 Chunk,還有一個問題沒有解決:
我們到底要怎麼知道搜尋結果是真的好,而不是自己看幾題覺得差不多?