精華筆記

· @aihub.tw

LINE OA AI 客服機器人

餵知識:FAQ 資料庫與轉真人機制

餵知識:FAQ 資料庫與轉真人機制

第 4 課接好 AI 之後,你的 LINE 機器人確實會「說話」了。但一般店家上線一兩天就會發現一個問題:顧客問「你們有提供發票嗎」,AI 回一串關於電子發票制度的通則文字;問「現在有優惠嗎」,AI 掰了一個根本不存在的折扣活動。AI 不是壞,是它真的不知道你的店——你沒餵它知識,它只好從訓練資料裡隨便猜。

更嚴重的狀況:顧客要退貨、有緊急問題急著找真人,機器人還是喋喋不休回一堆廢話。這才是會讓人崩潰的體驗。這堂課就解決這兩件事:讓機器人只說它知道的事,以及在該閉嘴的時候閉嘴、轉給真人。

這堂課適合誰 適合:想讓 LINE 客服機器人說出店家正確知識、不再亂回答的人(本課程屬進階應用專區)。需要基礎:會用 Google Sheets、有看過 n8n 介面。前置課:第 4 課(已接好 AI 回覆節點)。

這堂學什麼

  • 設計一張 Google Sheets FAQ 資料庫,規劃 keywords、question、answer 等欄位,讓 n8n 能精準比對
  • 建立三層決策流程:先查 FAQ 直接回覆→查不到才問 AI→AI 也不確定就轉真人
  • 防亂答的 System Prompt 架構:AI 只能根據 FAQ 回答,答不出來必須輸出固定標籤
  • 轉真人機制:關鍵字觸發 → 回覆顧客安撫訊息 → LINE Notify 通知店主 → 寫入待處理清單
  • 更新知識庫只要改 Sheets,流程本身永遠不用動

觀念一:三層知識決策樹

在開始動手之前,先把整個邏輯畫清楚。這套架構的核心精神是「能省就省,能確定就確定」:

FAQ 客服決策樹:三層優先順序

第一層:偵測「轉真人」關鍵字。顧客主動說「我要找真人」「客服」「投訴」,不用經過 AI,直接觸發通知機制。這是最高優先,任何問題都不能蓋過它。

第二層:查 FAQ 關鍵字,直接回答。如果顧客的訊息裡含有 FAQ 表格的 keywords 欄內的字,就直接拉出對應的 answer 回覆。這一層完全不需要呼叫 AI,省時省錢,而且答案 100% 是你自己寫的,不會出錯。

第三層:交給 AI,但要帶著 FAQ 當知識庫。前兩層都沒命中,才請 AI 出場——但把整張 FAQ 表格當作 context 帶進去,加上嚴格的 System Prompt 限制,讓 AI 只能根據這些資料回答,答不出來就輸出一個固定標籤,n8n 偵測到這個標籤再觸發轉真人。

這個架構有一個很重要的好處:每一層的職責都很清楚,出問題時你馬上知道要去查哪個環節。

觀念二:把知識放在 Sheets,不是放在 Prompt

很多人第一直覺是把 FAQ 直接貼進 AI 的 System Prompt。問題在於 Prompt 存在 n8n 節點裡,每次要更新都要打開 n8n 去改節點、測試、再存回去——非技術的小編或店長根本辦不到,一切都卡在會操作 n8n 的人身上。

FAQ 更新:硬刻 Prompt vs 維護 Sheets

把知識存在 Google Sheets 之後,更新流程變成:打開試算表 → 改一行 → Enter。n8n 每次收到訊息才去撈表格,所以永遠拿到最新的資料,完全不需要重啟流程。小編、店長自己就能維護,工程師不用每次被叫去改機器人。這才是長期可以活下去的架構。

手把手實戰

以下假設你已完成第 3、4 課的 webhook 接收和 AI 回覆基本流程。我們在這個基礎上加層。

建立 Google Sheets FAQ 資料庫

先在 Google Drive 新增一個試算表,取名例如「LINE OA FAQ」。第一行設定欄位標題,照下圖的規格設計:

Google Sheets FAQ 資料庫欄位設計

六個欄位說明:

欄位 必填 用途
keywords 必填 逗號分隔的同義詞,n8n 用這欄比對
question 必填 標準問法,讓 AI 理解語境
answer 必填 回給顧客的完整答案
category 選填 分類,之後做快速選單按鈕可用
active 選填 TRUE/FALSE,FALSE 就暫停這筆不用刪
updated_at 選填 日期,方便審查過期資訊

填幾筆範例資料讓流程有東西可測試。keywords 欄的同義詞要盡量齊全,例如「營業時間」這個主題,keywords 可以填:

營業時間,幾點開,幾點關,開門,打烊,幾點到幾點,上班時間

一個字都別省。中文的問法非常多元,keywords 越完整,第二層直查命中率越高,呼叫 AI 的次數就越少。

填好之後,把這張試算表的分享設定調成「知道連結的人可以檢視」——n8n 接 Google Sheets 用的是 OAuth,不需要公開,但要確認你連 n8n 的 Google 帳號有這張表的讀取權限。

n8n:在 Webhook 節點後加轉真人偵測

打開第 4 課的 n8n 流程。在 Webhook 節點之後、AI 節點之前,加入一個 IF 節點做關鍵字偵測。

IF 節點的設定:

  • 要比對的值:Webhook 收到的訊息文字,路徑通常是 {{ $json.events[0].message.text }}
  • 條件:選 StringContains
  • 比對值:先填一個最主要的關鍵字,例如 真人

但一個 IF 只能填一個值,要同時偵測多個關鍵字怎麼辦?有兩種方法:

方法 A:多個 IF 節點串聯(簡單但笨)。把「真人」「客服」「投訴」各做一個 IF,True 分支都接到同一個「轉真人處理」節點。

方法 B:用 Code 節點做一次比對(推薦)。在 Webhook 後加一個 Code 節點:

// Code 節點 (Node.js)
const msg = $input.first().json.events[0].message.text || '';
const handoffKeywords = ['真人', '客服', '投訴', '不滿意', '退款', '退貨', '申訴'];

const needsHandoff = handoffKeywords.some(kw => msg.includes(kw));

return [{ json: { ...$input.first().json, needsHandoff } }];

接著用一個 IF 節點判斷 needsHandoff 是否為 true。True 就走轉真人分支,False 繼續往下走 FAQ 查詢。方法 B 只要在這個陣列新增字串就能加關鍵字,維護比方法 A 方便。

n8n:Google Sheets 查詢節點

轉真人偵測之後、AI 呼叫之前,加入一個 Google Sheets 節點:

  • Operation:選 Get Many Rows
  • Spreadsheet:選你剛建好的「LINE OA FAQ」
  • Sheet:選放資料的那頁
  • Filters:加一個條件 active = TRUE(只撈啟用中的 FAQ)
  • Return All:勾選

這樣每次顧客傳訊,n8n 就會把所有 active = TRUE 的 FAQ 全部撈回來,存成一個陣列。

接著在 Sheets 節點之後加一個 Code 節點,做關鍵字比對:

// Code 節點:FAQ 關鍵字比對
const msg = $input.first().json.events[0].message.text || '';
const faqs = $('Google Sheets').all().map(item => item.json);

let matchedFaq = null;
for (const faq of faqs) {
  const keywords = (faq.keywords || '').split(',').map(k => k.trim());
  if (keywords.some(kw => kw && msg.includes(kw))) {
    matchedFaq = faq;
    break;
  }
}

return [{
  json: {
    ...$input.first().json,
    matchedFaq,
    faqHit: matchedFaq !== null,
    faqContext: faqs.map(f => `Q: ${f.question}\nA: ${f.answer}`).join('\n\n')
  }
}];

這段程式做兩件事:

  1. 把訊息文字拿去跟每一筆 FAQ 的 keywords 比對,找到第一筆命中的就存進 matchedFaq
  2. 把所有 FAQ 整理成純文字格式存進 faqContext,等一下要塞給 AI 當知識庫

接著加 IF 節點判斷 faqHit 是否為 true:

  • True 分支:直接用 LINE Reply API 回覆 matchedFaq.answer 的內容,這條路不碰 AI
  • False 分支:走 AI 分支

True 分支的 HTTP Request 節點設定(第 3、4 課已做過,這裡只補重點):

{
  "replyToken": "{{ $json.events[0].replyToken }}",
  "messages": [
    {
      "type": "text",
      "text": "{{ $json.matchedFaq.answer }}"
    }
  ]
}

記得打開 Body 裡的 Expression 模式,才能用 {{ }} 帶入變數。

AI 節點:注入 FAQ 知識庫 + 防亂答 Prompt

FAQ 沒命中的訊息,走到 AI 節點。這一步的關鍵是怎麼寫 System Prompt。先看整體結構:

防亂答 System Prompt 解析

System Prompt 完整範本如下,直接複製再依你的店改:

# 角色定義
你是「{填入你的店名}」的 LINE 官方帳號客服助理,負責回答顧客的問題。
你的所有回答,只能根據下方「知識庫」的內容作答,不得自行猜測或補充知識庫以外的資訊。

# 知識庫
{{ $json.faqContext }}

# 回答規則
1. 若顧客的問題在知識庫中有對應資訊:請根據知識庫內容回答,語氣親切自然,
   回覆長度不超過 100 字,適時加上換行讓訊息易讀。
2. 若顧客的問題在知識庫中完全找不到對應資訊,或資訊不足以確定回答:
   不可猜測、不可編造、不可說「應該是...」之類的模糊回覆。
   請直接輸出以下完整文字,不得更改任何字:
   
   HUMAN_NEEDED

3. 回覆請使用繁體中文台灣用語。禁止使用「您」,改用「你」。

# 絕對禁止
- 提及其他品牌或做比較
- 承諾知識庫未列出的折扣、服務或保證
- 說「我不知道」「無法確定」——這些情況一律輸出 HUMAN_NEEDED
- 回覆與店家服務完全無關的話題(請顧客聯絡真人客服)

幾個關鍵設計說明:

{{ $json.faqContext }} 是動態帶入的。這是前一步 Code 節點輸出的 faqContext 變數,把所有 FAQ 整理成純文字注入 prompt,所以只要 Sheets 更新,AI 下次就知道新的內容。

HUMAN_NEEDED 用全大寫,是為了讓後續的 IF 節點可以精準比對,不會被 AI 改字或混入其他回覆。如果你的 AI 模型回覆時習慣換行,可以改成 [HUMAN_NEEDED] 並在 IF 節點改用 Contains 而不是 Equals。

明確的分支指令比「請判斷」更可靠。Prompt 要寫清楚「符合條件 A 就做 X,符合條件 B 就輸出 Y」,不要留給 AI 自己決定。AI 的「自由裁量」是混亂答案的根源。

偵測 HUMAN_NEEDED,觸發轉真人機制

AI 節點之後加一個 IF 節點:判斷 AI 的回覆內容是否包含 HUMAN_NEEDED

True 分支(需要轉真人):執行下圖這整套動作序列:

轉真人機制:觸發條件 + 執行動作

動作 1:先回覆顧客安撫訊息

用 HTTP Request 呼叫 LINE Reply API:

{
  "replyToken": "{{ $json.events[0].replyToken }}",
  "messages": [
    {
      "type": "text",
      "text": "收到你的問題囉!我已通知客服人員,將在 5–10 分鐘內為你服務,請稍候 🙏"
    }
  ]
}

這步要放最前面。顧客等真人時需要知道「有人知道了」,否則他會以為訊息沒送到、開始狂傳。

動作 2:用 Messaging API 推播通知店主

早期很多教學會用 LINE Notify 來做這種通知,但 LINE Notify 已於 2025 年 3 月 31 日正式終止服務,notify-api.line.me 完全無法再使用。官方指定的替代方案就是用你這個 OA 的 Messaging API 主動推播(push message)——好處是不用再多申請一個服務,同一把 Channel Access Token 就能用。

做法是把通知「推播」給店主本人:請店主先用自己的 LINE 加入這個 OA 好友,隨便傳一句話,從 n8n 執行歷史的 webhook 裡撈到店主的 userId(以 U 開頭的字串),記下來。之後就用這個 userId 當推播對象。若要推到店家的內部群組,也可以把 OA 拉進群組、改推 groupId。

HTTP Request 節點設定:

  • Method:POST
  • URL:https://api.line.me/v2/bot/message/push
  • Authentication:Header Auth,名稱 Authorization,值 Bearer {{你的 Channel Access Token}}
  • Body:JSON,內容如下(把 to 換成店主的 userId):
{
  "to": "U店主的userId",
  "messages": [
    {
      "type": "text",
      "text": "{{ '需要真人接手!\\n顧客:' + $json.events[0].source.userId + '\\n原始訊息:' + $json.events[0].message.text + '\\n時間:' + new Date().toLocaleString('zh-TW', {timeZone:'Asia/Taipei'}) }}"
    }
  ]
}

注意 push message 和前面回覆顧客用的 reply message 會各算一則訊息,計入你的每月免費額度,這點下一課會細講。

動作 3:寫入 Google Sheets 待處理表

另開一個分頁「待處理」,欄位:userId / 訊息 / 觸發原因 / 時間 / 狀態(預設 FALSE)。用 Google Sheets 節點的 Append Row 把這次事件寫進去。你就有一個完整的待辦清單,處理完了自己去勾 TRUE。

False 分支(AI 有答案):直接用 HTTP Request 呼叫 LINE Reply API 把 AI 回覆送出去。這邊和第 4 課的做法一樣,不再重複。

測試整條流程

流程接好之後,用你自己的手機打開 LINE 加入這個 OA,分別傳這幾種訊息測試:

  1. 命中 FAQ 關鍵字的問題:例如你在 Sheets 有填「營業時間」,傳「你們幾點開門」,應該秒回 Sheets 裡的答案,不經過 AI。
  2. 沒命中 FAQ 但合理的問題:例如「你們有哪些品項」,AI 應該根據 FAQ 內容回覆,不亂掰。
  3. 完全超出範圍的問題:例如問你們 OA 一些奇怪的事,AI 應該不回覆你那些問題、不瞎扯,而是收到 HUMAN_NEEDED → 通知你。
  4. 直接傳「真人」:應該直接觸發轉真人,不走 FAQ 或 AI 流程。
  5. 傳「客服」:同上。

每一個都測過一遍,確認 n8n 執行歷史裡每個節點都走到預期的分支,沒有意外跳到錯的路。LINE Reply API 呼叫成功會回 {} 空 JSON,失敗的話注意看錯誤訊息。

常見坑

坑 1:AI 不肯輸出 HUMAN_NEEDED,老是給「抱歉我無法回答」

這是 prompt 寫法問題。「請直接輸出 HUMAN_NEEDED」對某些模型沒用,它會把 HUMAN_NEEDED 包在其他文字裡,例如「這個問題我需要轉 HUMAN_NEEDED 給...」。解法有兩個:第一,在 prompt 裡再強調一次「不得在 HUMAN_NEEDED 前後加任何文字」;第二,把 IF 節點的比對條件從 Equals 改成 Contains——只要回覆裡有這段文字就觸發。第二個方法更實用,因為即使 AI 換行或加了空格,Contains 也照樣抓得到。

坑 2:Google Sheets 撈資料的速度讓顧客等太久

n8n 每次呼叫 Google Sheets API 大約需要 12 秒,加上 AI 呼叫又 25 秒,整個流程跑完可能 5~8 秒,顧客感覺像卡住。兩個改善方向:第一,把「先查 FAQ 關鍵字直接回」的邏輯做好——大部分常見問題都直查命中,不走 AI 那條,整體速度就快很多;第二,如果你的 FAQ 資料量超過 200 行,可以考慮改用 n8n 的「Schedule Trigger」每 10 分鐘把 Sheets 撈回來存到 n8n 的 Static Data,正式回覆時從 Static Data 讀而不是每次重撈。

坑 3:轉真人之後 AI 還是繼續插嘴回覆

這是因為沒有做「暫停 AI 的狀態記錄」。你通知了店主,但下一條訊息進來,n8n 還是走正常流程讓 AI 回。解法:在 Google Sheets 待處理表加一個「handoff_active」欄,轉真人時寫入 userId + TRUE。在流程最前面加一個 Sheets 查詢,看這個 userId 有沒有 handoff_active = TRUE 的紀錄,有的話直接回「正在連繫客服人員,請稍候」,跳過後面所有節點。店主處理完之後去把那筆改成 FALSE,AI 才會重新接手。

坑 4:$('Google Sheets').all() 報錯「Cannot read properties of undefined」

這是 n8n 節點名稱對不上。$('Google Sheets') 的括號裡必須填你在 n8n 介面上看到的節點「顯示名稱」,如果你把那個節點改名成「查詢 FAQ 表」,就要改成 $('查詢 FAQ 表').all()。報這個錯就去確認 Code 節點裡引用的名稱和畫布上的節點名稱是否完全一致,包括大小寫和空格。

坑 5:keywords 比對到不相關的詞

例如你的 keywords 填了「有」這個字,結果任何帶「有」的問題都命中這筆 FAQ。關鍵字太短或太通用就會這樣。解法:keywords 一律填兩個字以上的詞組;如果真的需要單字,在 Code 節點的比對邏輯裡加長度限制——例如 kw.length >= 2 && msg.includes(kw);或者把比對邏輯從 includes 改成用正則加字邊界。

作業

  1. 實際填好你自己店家的 FAQ 表格,至少 10 筆,涵蓋最常被問的問題類別(例如:營業時間、訂位方式、退換貨政策、停車、付款方式)。
  2. 把五種測試情境都跑過一遍,截圖下來對照:是否每種都走到正確的分支?
  3. 選做:在 Google Sheets 待處理表加「處理人」欄,有多個客服時可以在這欄填入誰負責這張單。
  4. 思考作業:你的店有哪些問題屬於「一定要真人接手」的類型?把這些加進 handoffKeywords 陣列裡。

下一課預告

機器人現在已經懂事多了:懂你的知識、知道自己不懂的要轉給真人、不會亂說話。但還差最後一關——真正上線給陌生顧客用,會遇到你在測試時想不到的狀況:流程偶爾出錯要怎麼監控?LINE 的 200 則免費額度(2026 輕用量方案)用完了怎麼辦?哪些節點會產生費用、如何精算成本?有顧客故意傳奇怪東西攻擊機器人怎麼辦?

第 6 課「上線實戰:測試、成本控管與常見坑」把這些實戰問題全部講清楚,讓你的 LINE 客服機器人從「可以動」變成「可以放心用」。

#LINE OA#FAQ#Google Sheets#n8n#轉真人#客服機器人

← 回所有文章