AI 互動教室 ‹ 學 LLM 應用開發
下載 .py 開啟實戰 notebook ↗ 留言回報
RAG · RETRIEVAL-AUGMENTED GENERATION · 繁體中文

RAG:讓模型
先翻手冊再回答

LLM 什麼都懂一點,唯獨不懂你的資料。問它你店裡的 Wi-Fi 密碼,它不會說不知道—— 它會自信地編一個。RAG 的解法很樸素:回答前先從你的資料找出最相關的幾段,貼進提示詞, 再請模型只根據這些回答。下面是一間虛構的貓咪咖啡廳,切換開關看差別(內容是 notebook 的實測紀錄):

RAG
關——模型只靠自己

資料是一份十小節的繁體中文店務手冊;向量用第 1 課 gateway 的 qwen3-embedding-0.6b, 索引用上一課的記憶體 Qdrant,生成用 nemotron-3.5-lightning。notebook 從切段到評測每一步都量化。

01 · 問題與切段

先看它怎麼瞎掰,再決定怎麼切

notebook 第一格就問「Wi-Fi 密碼是多少?」——模型會給一個煞有介事的密碼(實測拿過 sakura2024camelliahouse),問「哪隻貓最怕生」要嘛發明一隻叫「豆漿」的貓、要嘛打太極說每隻貓都有個性。 這不是模型壞,是它沒有你的資料

RAG 的第一個設計決策是怎麼切:切太大帶進無關內容,切太小失去上下文。 這份手冊結構乾淨,用最自然的切法——一個 ## 小標=一段,切出 10 段、每段 59–91 字:

def chunk_by_heading(text): chunks = [] for block in text.split("\n## ")[1:]: # [0] 是 # 大標,不算一段 lines = [ln.strip() for ln in block.strip().splitlines()] chunks.append({"title": lines[0], "text": "## " + "\n".join(lines)}) return chunks
到 notebook 的 1️⃣–2️⃣ 節:瞎掰現場、切段
02 · 向量化、入庫、檢索

十段變十個向量,問題也走同一條路

十段文字一次 API 呼叫批次變成 1024 維向量(input 給整個 list), 存進記憶體 Qdrant,payload 放原文與標題——檢索回來要貼進提示詞的是文字,向量只是索引。 問題也經過同一個 embedding 模型變向量,問 Qdrant 最近的 top_k 段。

看 score 的落差:「哪一隻貓最怕生?」命中「店貓介紹:煤球」0.53,但另兩隻貓的段落也有 0.51—— 三段都在講貓,檢索分不太開,靠模型讀內文判斷;「Wi-Fi 密碼」則是 0.70 對 0.31,一段獨大。 notebook 有輸入框與 top_k 拉桿,多試幾個問題感受分數的意義。

到 notebook 的 3️⃣–4️⃣ 節:入庫、互動檢索
03 · 生成

三條規矩,少一條都不行

SYSTEM_RAG = ( "你是山茶屋貓咪咖啡廳的店員。只能根據下面的「參考資料」回答顧客問題," "資料裡沒有的就說「手冊裡沒有寫」,不要編造。用繁體中文簡短回答。\n\n參考資料:\n{context}" ) context = "\n\n".join(f"[{i+1}] {h.payload['text']}" for i, h in enumerate(hits))

只根據參考資料沒有就說沒寫不要編造——少了它們,模型會把參考資料和自己的想像混著講。 notebook 的 answer(question, use_rag=True/False) 一個開關切換兩種模式, 並排顯示同一題的兩個回答。

到 notebook 的 5️⃣ 節:提示詞、並排對照
04 · 評測與邊界

數字說話,以及「會說不知道」才是好 RAG

一個例子不算證據。7 題有標準答案的問題,每題用一個關鍵字判斷答對與否,兩種模式各跑一輪。實測多次: 沒 RAG 只矇對 0–2 題(例如「可以帶狗嗎」偶爾猜中「禁止」),有 RAG 7 題全對。 這種關鍵字命中是最陽春的評測,但它便宜、客觀——每次動了切段、top_k、提示詞,都該重跑。

問題沒 RAG(實測節錄)有 RAG
Wi-Fi 密碼是多少?❌ 山茶屋的 Wi-Fi 密碼是:sakura2024✅ meow2026
山茶屋週日幾點打烊?❌ 週日 18:00 打烊✅ 21:00
領養一隻貓要多少錢?❌ 約新臺幣 2,000~5,000 元,已包含疫苗…✅ 1500 元
你們有賣牛排嗎?(手冊沒有)沒有販售牛排(矇對,但無從分辨是知道還是猜)✅ 手冊裡沒有寫(檢索分數全部 < 0.4)

最後一列是 RAG 最被低估的價值:檢索永遠會回最近的幾段(哪怕都不相關), 但提示詞的規矩讓模型老實說沒寫,而不是拿不相關的段落硬湊。

到 notebook 的 6️⃣–7️⃣ 節:評測表、長條圖、範圍外問題
05 · 實戰

換你動手

LEVEL 1

top_k 改 1 再跑評測——哪一題最先出錯、為什麼?改 5 會不會因雜訊變差?

LEVEL 2

在手冊加一個新小節(例如「外送服務」),不改其他程式重跑——新知識立刻可用,這是 RAG 相對微調的最大優勢。

LEVEL 3

刪掉「手冊裡沒有寫」那句看牛排問題怎麼答;再加一個分數門檻,top-1 太低就不送模型。用評測找出不誤殺的門檻。

卡住了?每一題在 notebook 末節都有折疊解答——先自己做,再打開對照。

06 · 驗收

情境測驗

離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。

Q1 情境題

你要把公司的產品 FAQ(Markdown,每個問題一個 ## 小標)做成 RAG 知識庫。切段應該怎麼切?

切段要在「太大帶進無關內容、太小失去上下文」之間取捨,文件結構乾淨時最自然的切法就是跟著小標——本課的手冊就是這樣切出 10 段、每段 59–91 字。A 讓檢索失去意義(永遠整份都命中),提示詞塞滿無關內容;B 切太小會失去上下文——「牠對雞肉凍乾沒有抵抗力」單獨一句,誰也不知道「牠」是麻糬;D 會把小節攔腰切斷,一段的前後文被拆散在兩個 chunk 裡。

Q2 錯誤診斷

你問「你們有賣牛排嗎?」(手冊裡完全沒有),檢索卻照樣回來 3 段,而且全是不相干的內容。這代表什麼?

檢索回來的段落(top_k=3): 店貓介紹:麻糬(0.37)、店貓介紹:奶蓋(0.35)、入場規定(0.32)

檢索是「最近鄰」查詢,哪怕全部不相關也照樣回 top_k 段——範圍外問題的訊號不是「回不回」,是分數全部偏低(實測範圍內 top-1 至少 0.46,牛排最高只有 0.37)。所以要靠提示詞的「沒有就說沒寫」或分數門檻來擋。A 不成立:同一個模型把範圍內的問題都命中了(Wi-Fi 0.70);B 搞混了——top_k=3 回 3 段是設計不是 bug,調小照樣回不相關的;C 的話範圍內的問題也會亂掉,但評測是 7 題全對。

Q3 錯誤診斷

同事的 RAG 客服檢索一切正常,但回答會混入手冊裡根本沒有的內容。最可能的原因是?

問:你們有賣牛排嗎? 答:目前菜單主要提供飲品和少量貓友好點心,未列有牛排。 (手冊從頭到尾沒提過菜單內容——「飲品和點心」是模型自己腦補的)

這正是課裡 LEVEL 3 拿掉那句規矩後的實測結果:模型從「咖啡廳」自行腦補菜單,講得很像真的。修法是把三條規矩加回 system prompt——少一條都不行。B 症狀相似但題目已說檢索正常;C 搞錯層次——編造不是能力問題,是沒有被禁止,大模型一樣混著講;D 撈不到不存在的東西,手冊裡根本沒有菜單段落。

Q4 情境題

你把切段方式從「## 小標一段」改成固定 200 字一段,想知道整體有沒有變差。最佳做法是?

關鍵字命中評測陽春但便宜、客觀、可重複——每次動了切段、top_k、提示詞都重跑一輪,課裡那 7 題就是幹這個用的。A 是一個例子不算證據,別題可能默默壞掉;C 分數只是檢索訊號,不等於答案正確(三隻貓 0.53/0.51/0.51 分不開時是靠模型讀內文救回來的),而且切段變了分數本來就會變、不可比;D 等於拿使用者當測試,發現時已經晚了。

HANDS-ON · MOLAB

實作在 molab 跑(免費)

molab 的登入狀態進不了內嵌框架(瀏覽器的跨站 cookie 保護), 所以 notebook 要在新分頁執行——把它跟本頁並排開,左邊教學照樣對照。

  1. 登入 molab(GitHub / Google)
  2. 開啟課程 notebook,Fork 成自己的副本即可編輯
  3. 從第一格往下全部執行(首次安裝套件約 1 分鐘)——免費 CPU 環境即可,不需要 GPU

不想用 molab?下載 rag-zh_ext.py 後在自己電腦 uvx marimo edit --sandbox rag-zh_ext.py,依賴會自動安裝。