社群裡看到一段討論。有人在幫學校做校園 AI 助理,本地 PDF 做 RAG,遇到一個很典型的狀況:
問宿舍租金,2 人房、4 人房、6 人房的金額都答得很準。接著追問一句「那弱勢生呢?」——它直接跳去講外籍生。
底下的討論很快滑向「這是上下文的問題」「短期記憶很難做」。我的看法不太一樣:這不是記憶的問題,是檢索 query 的問題,而且解法便宜得多。
這個案子的細節不是我的重點。我想借這個題目,把「如果要在一個真實的客服場景導入 RAG,架構該怎麼設計」整理成一份可以拿去跟主管討論的東西。因為同樣的坑,換到客服場景只會更深——校園 FAQ 答錯是學生白跑一趟,客服答錯是客戶照著錯的步驟動了線上資料。
一、多輪對話翻車,先查改寫,不要急著做記憶
先把那個 bug 解掉,因為它是所有 RAG 系統的第一個坑。
那弱勢生呢? 這句話,單獨拿去做 embedding 會發生什麼事?它沒有任何語意錨點——沒有「宿舍」、沒有「住宿費」,只有一個身分別。向量最接近的自然是「身分別」那一章,而弱勢生、外籍生、僑生通常就寫在同一節裡。撈錯段落,然後很流暢地答錯。
解法是在檢索前面插一個步驟:把最近 2–3 輪對話和當前問題,用小模型合成一句可以獨立理解的問句。
使用者實際輸入:
那弱勢生呢?
改寫後送去檢索:
弱勢學生的宿舍住宿費減免金額是多少?
換到客服場景是一模一樣的形狀:
使用者實際輸入:
那這個呢?
改寫後送去檢索:
V2 版的報表模組匯出 Excel 時,欄位格式跑掉要怎麼處理?
配一組對話槽位(主題、模組、單號、版本)跟著改寫一起帶進去,多輪省略的問題大概就解掉七成。
重點是不要自建通用記憶系統。那是開放領域對話才需要的東西,複雜、難測、難除錯。限定領域的客服或校務問答,用「改寫 + 槽位」就夠了,而且出了問題你可以直接把改寫後的句子印出來看——這件事對後面的除錯有多重要,講到觀測性時會再提。
二、客服跟 FAQ 機器人的根本差異
這是我認為多數人低估的一點。
一般 FAQ 機器人只處理「知識型」問題:怎麼設定、在哪裡、支援什麼格式。但真實的客服對話裡,有將近一半的問題不是知識問題,是狀態問題:
- 「我上週報的那張單好了沒?」
- 「這個月帳單為什麼多了三千?」
- 「幫我把權限開給新來的同事。」
這些答案不在任何文件裡,在資料庫裡。無論你的知識庫做得多完整、切塊多漂亮、embedding 換到多新的模型,都答不出來。
所以客服系統的架構從一開始就不該是「RAG」,而是「RAG + 工具呼叫」。第一層要做的不是檢索,是意圖分流:
身分 / 租戶解析
↓
查詢改寫
↓
意圖分類(小模型)
├─ 知識型 → 混合檢索 → 生成答案 + 引用
├─ 狀態型 → 唯讀工具(查單 / 查帳 / 查設定)
├─ 操作型 → 寫入工具(開單 / 改設定)→ 需確認
└─ 情緒 / 低信心 → 轉人工(帶摘要)
注意「身分 / 租戶解析」在最前面。它的輸出有兩個去處:一個是檢索的過濾條件,一個是工具呼叫的權限依據。這兩件事都不能等到後面才做。
工具邊界的兩條紅線
第一條:**唯讀工具可以讓模型自由呼叫,寫入工具一律要使用者明確確認。**執行前把「我要把 X 改成 Y」完整顯示出來,按下確認才動。不要讓模型自己決定退費、改資料、刪東西。
第二條:**權限綁在登入身分上,不綁在模型上。**模型只是代為呼叫,後端仍然要驗證「這個帳號有沒有權限查這張單」。最容易出事的情境是有人在對話裡誘導模型去查別人的資料——如果後端信任模型傳來的參數,那就是一次資料外洩。
連帶的前提是:知識型問題可以匿名問,但一旦要查單,就必須先完成身分認證。
三、能查表就不要檢索
這條我認為是投資報酬率最高的一項。
宿舍費率、方案價格、額度上限、SLA 時效——這類資料在原始文件裡是表格。PDF 表格經過切塊和向量化之後,欄位會錯位、跨頁會斷裂,撈出來的東西看起來很像答案,但數字是錯的。而這恰好是答錯代價最高的一類問題。
作法很土但有效:把費率、額度、期限這類資料人工整理成資料表,就那幾十列,一次性成本極低。然後在意圖分類時多切一條:
問題類型判定
├─ 金額 / 資格 / 期限 / 狀態 → SQL 查表 ← 正確率是確定的
└─ 操作說明 / 原因 / 概念 → 向量檢索
查表還送你一個額外好處:出處可以精確標到「依據 V2 版收費標準第 3 項」,比向量檢索回傳的段落乾淨太多,剛好對上很多案子會要求的「可解釋性」。
四、多租戶系統最容易出事的一項:版本 × 租戶過濾
如果你的系統是多租戶的——一個機構一個資料庫 那種架構——這一節是上線的必要條件,不是優化項。
問題長這樣:OrganizationA 用的是 V1 版、還客製過報表模組;OrganizationB 是 V2 版標準功能。同一個問題,正確答案不一樣。
這比「今年的規則跟去年不同」難處理得多,因為客製的差異往往根本沒有文件。少了這層過濾,模型會用非常專業的語氣講別的客戶的功能——而且第一線客服很難一眼看出來。
作法是每個 chunk 入庫時帶齊 metadata,檢索時當成硬過濾條件,而不是交給模型自己看日期判斷:
# chunk metadata
doc_title: 文件標題
section_path: 章/節/小節(一併寫進 chunk 內文前綴)
module: 模組代號
version_from: 自哪一版起適用
version_to: 至哪一版止(null = 仍適用)
applies_to_tenant: 指定客戶;null = 通用
source_url: 知識庫文章連結,回覆時附上
owner: 維護責任人
updated_at: 最後更新時間
對話一開始就從客戶主檔取出「目前版本 + 已購模組 + 客製清單」,寫進 session 槽位,套用到每一次檢索。取不到版本資訊時,寧可先問「請問您是哪個單位」,也不要猜。
這裡順帶一個低成本高回報的切塊技巧:把 文件標題 + 章節標題 + 適用版本 當作前綴寫進 chunk 的內文。這一步對命中率的提升,通常比換一個更貴的 embedding 模型還大。
五、歷史工單是金礦,也是地雷
客服知識庫的來源大致四類,品質差距很大,不能用同一套流程入庫:
| 來源 | 覆蓋率 | 主要風險 |
|---|---|---|
| 操作手冊 / SOP | 中 | 常與實際版本不符 |
| 知識庫 / FAQ | 低 | 寫法與客戶的問法有落差 |
| 歷史工單 | 高 | 含客戶資料,且含當年客服答錯的內容 |
| 版本更新說明 | 低 | 未整理成問答,但它是版本差異的唯一真相 |
歷史工單是覆蓋率最高、問法最真實的來源,非常值得用,但不能原樣入庫。四個步驟:
- 去識別化——客戶名稱、人名、電話、帳號、識別碼一律遮蔽。
- 只收已結案且有明確解法的單,未結案與閒置單全部排除。
- 用模型批次萃取成「問題描述 + 最終解法」的問答對,人工抽樣審過才入庫。
- 標註適用版本與處理日期,作為後續過濾與淘汰的依據。
第 3 步是最容易被省掉的一步,也是最貴的一步。跳過它直接塞原始工單,模型會很忠實地學到當年講錯的內容,而且事後你查不出來那句話是哪來的。
六、轉人工不是失敗,是設計的一部分
客服場景相對於校園 FAQ 有一個結構性的優勢:永遠有真人可以接手。所以策略應該很明確——寧可多轉,不要硬答。
觸發條件建議直接寫成規則,不要交給模型判斷:
- 檢索分數低於門檻,或找不到明確依據
- 同一問題來回超過 2–3 輪仍未解決
- 偵測到負面情緒、投訴用語,或「要退費」「找你們主管」
- 涉及金錢、合約、權限異動
- 使用者明確要求真人
還有一個常被忽略的細節:轉出時必須附帶摘要——問題描述、已經試過的步驟、查到的單號、客戶的版本。丟一段空白對話給客服,是這類系統被第一線抵制最常見的原因。摘要做好,客服團隊才會站在你這邊;做不好,他們會想辦法讓這個東西下線。
七、不要接外部網路檢索當兜底
原討論串裡提到「本地 RAG 判斷不夠準的話,才走外部搜尋 API」。我不建議這樣做,理由有兩個:
第一,這是官方管道。答案的權威性來自你們自己的文件,一旦混入外部來源,你無法對任何一個答案負責。
第二——這點更現實——外部搜尋最容易撈到的,正是已經作廢的舊版說明。提問的人提到自己找到很多「學校陳年沒刪掉的網路 PDF」,那正是外部檢索會優先命中的東西。同樣的事發生在產品文件上就是:搜到三年前的舊版操作說明,然後很有自信地教客戶按一個已經不存在的按鈕。
正確的兜底不是「去外面找找看」,是明確說查不到,並給出承辦窗口或轉人工。
對這類系統,「誠實說不知道」的價值遠高於「勉強答一個」。這件事要寫進驗收條件——否則主管只會看到「答不出來」,而不是「正確地拒答」。
八、為什麼不需要 Graph RAG
原討論串問到有沒有上 Graph RAG。以這類資料型態——章節化的手冊、法規、FAQ、工單問答——我的判斷是不需要。
Graph RAG 適合的是實體之間關係複雜、且查詢本身需要多跳推理的場景。客服知識的結構其實很單純:一份文件、一個章節、一個版本區間。建圖與長期維護的成本會吃掉整個工期,而對這類問答的準確率提升有限。
真正該花力氣的是這三件事,順序照排:
- 切塊策略(章節切 + 標題前綴)
- metadata 硬過濾(版本 / 租戶 / 模組)
- 混合檢索(關鍵字 + 向量)
第三點值得多說一句:中文的專有名詞——「弱勢助學金」「批次輸出」「保險申報」——關鍵字檢索的命中率往往比向量高。純向量容易在近義詞之間滑走,正好就是文章開頭那個 bug 的另一種形式。至於 reranker,資料量不大的時候先不用急,把前兩件事做對的收益大得多。
九、把「又準又快」拆成可以驗收的指標
提問的人說,需求方給的要求是「要準又要快」。這種要求無法驗收,也無法反駁——做到什麼程度都可以被說不夠。
唯一的解法是把它拆開、寫成數字,然後拿去對齊:
| 指標 | 定義 | 建議門檻 |
|---|---|---|
| 檢索召回率 | 黃金題組中,正確文件出現在前 k 筆 | ≥ 90% @k=5 |
| 答案正確率 | 檢索正確的前提下,答案與標準答案一致 | ≥ 90% |
| 錯誤回答率 | 抽樣審查中,內容錯誤且未標示不確定 | ≤ 2% |
| 自助解決率 | AI 完結、未轉人工且 24 小時內未再問同題 | ≥ 35% |
| 轉人工率 | 分母扣掉「本來就該轉」的類別 | 觀察值 |
| 回應延遲 P95 | 知識型分支,首字出現時間 | ≤ 3 秒 |
兩個提醒:
**第一,召回率和正確率一定要分開量。**混在一起你永遠不知道該調檢索還是調提示詞——是根本沒撈到對的文件,還是撈到了卻答錯?這是兩個完全不同的修法。
**第二,轉人工率低不等於好。**硬答會把它壓下來,同時把錯誤回答率推上去。這兩個指標必須一起看,任何一方單獨達標都不算通過。
指標要成立,前提是有一組黃金題組:100–150 題,直接從歷史工單抽最高頻的問題,每題標註標準答案與應該引用的文件。多輪省略題要獨立成一組——因為那是目前最主要的失敗模式。每次改檢索或改提示詞就重跑一次,避免修好一處、壞掉三處。
十、落地順序:先做 Copilot,不要直接對外
最後這一條,是我認為整份評估裡最重要的一個判斷。
兩種上線模式的差別,不在準確率,而在錯誤答案在哪裡被攔下來:
Copilot 模式 直接對外自動回覆
───────────── ─────────────────
客戶提問 客戶提問
↓ ↓
AI 產生建議 + 出處 AI 直接回覆客戶
↓ ↓
客服採用或改寫 ← 錯誤在此攔下 客戶照著操作 ← 錯誤已經生效
↓ 成本 = 改一句話 ↓ 成本 = 資料要救回來
送出回覆 事後補救與客訴
副產物:採用/改寫紀錄 副產物:無
→ 評測集與知識缺口
右邊那條路省下了客服工時,但把錯誤直接交到客戶手上。而左邊那條路有一個容易被忽略的副產物:客服「採用 / 改寫 / 棄用」的紀錄。
那批資料是你用任何其他方法都買不到的。它同時告訴你兩件事:哪些答案真的能用,以及哪些問題你的知識庫根本沒寫。評測集和知識缺口清單,都從這裡長出來。
所以分期建議是:
| 階段 | 交付 | 進入下一階段的條件 |
|---|---|---|
| P0 Copilot | 查詢改寫、混合檢索、版本過濾、引用來源、客服端建議面板 | 建議採用率達標,錯誤回答率 ≤ 2% |
| P1 評測與觀測 | 黃金題組、評測腳本、追蹤與指標儀表板 | 評測集穩定,指標可持續觀測 |
| P2 對外(限縮) | 只開放低風險知識型問題,其餘一律轉人工 | 自助解決率達標,客服端無明顯抱怨 |
| P3 工具擴充 | 先開唯讀查詢,最後才是寫入型操作 | 每項寫入工具獨立評估與灰度 |
P1 之所以跟 P0 並行,是因為它的原料就是 P0 產生的操作紀錄。沒有 P0 就沒有乾淨的評測集——這是不建議跳過 Copilot 階段的第二個理由。
直接跳到對外自動回覆,通常會在上線第三週因為一則錯誤回答被喊停,然後整個案子就冷掉了。
兩個順帶的工程建議
**流程編排工具的定位要收斂。**用 n8n 這類工具做串接、排程、通知沒問題,但把「改寫 + 檢索 + 引用組裝 + 拒答判斷」收成一支自己的服務,讓編排工具去呼叫它。理由很單純:你要能寫測試、能看 diff、能跑回歸。純靠視覺化流程做多輪對話加評測,會非常痛——原討論串裡「不好除錯是最大痛點」的感受不是錯覺。
**對話紀錄要存在對的地方。**如果上頭指定要用時序資料庫存對話,可以折衷:對話內容、引用來源、使用者回饋存關聯式資料庫(要做關聯查詢、標註、評測),延遲、token 數、命中率、拒答率打到時序資料庫配儀表板(那是「快」的證據)。兩邊都有交代,而你保住了做評測需要的資料。
小結
回到開頭那個 bug。
「那弱勢生呢?」答錯,看起來像是模型不夠聰明、上下文不夠長、記憶做得不好。但真正的原因只是——你把一句沒有主詞的話,原封不動丟給了向量檢索。
我覺得這件事很有代表性。RAG 落地的難處,大多不在模型選型,而在這些看起來很土的地方:句子有沒有補完整、表格該不該進向量庫、這份文件屬於哪一版、答錯的時候誰接手、以及——你到底要怎麼證明它做對了。
模型會一直變強,但上面這幾題不會因此自動消失。