客服 RAG 架構評估:一半的問題不在文件裡,在資料庫裡


社群裡看到一段討論。有人在幫學校做校園 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低寫法與客戶的問法有落差
歷史工單高含客戶資料,且含當年客服答錯的內容
版本更新說明低未整理成問答,但它是版本差異的唯一真相

歷史工單是覆蓋率最高、問法最真實的來源,非常值得用,但不能原樣入庫。四個步驟:

  1. 去識別化——客戶名稱、人名、電話、帳號、識別碼一律遮蔽。
  2. 只收已結案且有明確解法的單,未結案與閒置單全部排除。
  3. 用模型批次萃取成「問題描述 + 最終解法」的問答對,人工抽樣審過才入庫。
  4. 標註適用版本與處理日期,作為後續過濾與淘汰的依據。

第 3 步是最容易被省掉的一步,也是最貴的一步。跳過它直接塞原始工單,模型會很忠實地學到當年講錯的內容,而且事後你查不出來那句話是哪來的。

六、轉人工不是失敗,是設計的一部分

客服場景相對於校園 FAQ 有一個結構性的優勢:永遠有真人可以接手。所以策略應該很明確——寧可多轉,不要硬答。

觸發條件建議直接寫成規則,不要交給模型判斷:

  • 檢索分數低於門檻,或找不到明確依據
  • 同一問題來回超過 2–3 輪仍未解決
  • 偵測到負面情緒、投訴用語,或「要退費」「找你們主管」
  • 涉及金錢、合約、權限異動
  • 使用者明確要求真人

還有一個常被忽略的細節:轉出時必須附帶摘要——問題描述、已經試過的步驟、查到的單號、客戶的版本。丟一段空白對話給客服,是這類系統被第一線抵制最常見的原因。摘要做好,客服團隊才會站在你這邊;做不好,他們會想辦法讓這個東西下線。

七、不要接外部網路檢索當兜底

原討論串裡提到「本地 RAG 判斷不夠準的話,才走外部搜尋 API」。我不建議這樣做,理由有兩個:

第一,這是官方管道。答案的權威性來自你們自己的文件,一旦混入外部來源,你無法對任何一個答案負責。

第二——這點更現實——外部搜尋最容易撈到的,正是已經作廢的舊版說明。提問的人提到自己找到很多「學校陳年沒刪掉的網路 PDF」,那正是外部檢索會優先命中的東西。同樣的事發生在產品文件上就是:搜到三年前的舊版操作說明,然後很有自信地教客戶按一個已經不存在的按鈕。

正確的兜底不是「去外面找找看」,是明確說查不到,並給出承辦窗口或轉人工。

對這類系統,「誠實說不知道」的價值遠高於「勉強答一個」。這件事要寫進驗收條件——否則主管只會看到「答不出來」,而不是「正確地拒答」。

八、為什麼不需要 Graph RAG

原討論串問到有沒有上 Graph RAG。以這類資料型態——章節化的手冊、法規、FAQ、工單問答——我的判斷是不需要。

Graph RAG 適合的是實體之間關係複雜、且查詢本身需要多跳推理的場景。客服知識的結構其實很單純:一份文件、一個章節、一個版本區間。建圖與長期維護的成本會吃掉整個工期,而對這類問答的準確率提升有限。

真正該花力氣的是這三件事,順序照排:

  1. 切塊策略(章節切 + 標題前綴)
  2. metadata 硬過濾(版本 / 租戶 / 模組)
  3. 混合檢索(關鍵字 + 向量)

第三點值得多說一句:中文的專有名詞——「弱勢助學金」「批次輸出」「保險申報」——關鍵字檢索的命中率往往比向量高。純向量容易在近義詞之間滑走,正好就是文章開頭那個 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 落地的難處,大多不在模型選型,而在這些看起來很土的地方:句子有沒有補完整、表格該不該進向量庫、這份文件屬於哪一版、答錯的時候誰接手、以及——你到底要怎麼證明它做對了。

模型會一直變強,但上面這幾題不會因此自動消失。