Q&A 客服 Agent (Design Q&A Support Agent)
設計一個掛在訂房平台(Booking.com / Agoda)上的 AI 客服 Q&A Agent:使用者用自然語言問「如何取消訂房?」「這間能帶寵物嗎?」「我的訂單狀態?」,系統用 RAG(檢索後生成) 先撈平台真實資料、再讓 LLM 基於檢索結果作答。系統 延遲敏感(端到端 2-3 秒)、準確性是生死線(不得幻覺)、要支援 多輪對話、且 知識要即時。
招牌取捨:答案必須 完全 grounded 在檢索到的 context——當檢索品質不夠(similarity 低於閾值)或 grounding check 失敗時,系統 主動拒答並轉接真人客服,而不是讓 LLM 拿預訓練知識硬補。用「會拒答的準確」換掉「什麼都答的流暢」,因為在客服場景捏造一條取消政策的代價是信任崩潰與客訴。其餘決勝點:Intent routing + 平行檢索(結構化訂單 DB + 非結構化向量庫)、conversation-aware query rewriting(把「那間能帶狗嗎」改寫成自包含查詢)、hybrid search + rerank 提升 recall、streaming(TTFT) 把感知延遲從 2-3 秒壓到數百毫秒、離線 ingestion pipeline 保知識即時性。
配速(35 min 預算):~5 min 釐清需求 → ~4 min 容量估算 → ~3 min API + ~4 min 資料模型 → ~6 min 高階架構(RAG online path)→ ~9 min deep dive(chunking/indexing、hybrid query、防幻覺、多輪、freshness、協定)→ ~3 min 擴展瓶頸 → ~1 min 收尾。開場先一句話複述題目、宣告配速、把 chunking / 防幻覺標記為「待會 deep dive」——宣告配速本身就是高分動作,替你爭取「先框架、後細節」的許可。
1. 釐清需求與範圍 (Clarify & Scope ~5 min)
開場後先問清楚邊界,別急著畫 RAG box(LLM 題最常見死因:直接畫「query → LLM → answer」卻不問資料從哪來、要不要 grounded)。建議問題:
| 釐清問題 | 為何要問 / 影響到哪個決策 | 本題假設(依來源) |
|---|---|---|
| 要回答哪幾類問題? | 決定要接哪些資料來源、要不要 router | 三類:平台通用 FAQ / 飯店特定政策 / 使用者訂單 |
| 答案可以用 LLM 的預訓練知識嗎? | 直接決定整個 RAG + 防幻覺架構 | 不行,必須 grounded 在平台真實資料 |
| 延遲要求?哪段最敏感? | 決定要不要 streaming | 端到端 2-3 秒;感知延遲靠 streaming 壓 TTFT |
| 需要多輪對話嗎?要記上下文嗎? | 決定 Conversation Store + 改寫 | 需要 multi-turn,能理解上下文 |
| 答不出來時怎麼辦? | 決定 fallback / 轉真人 | 明確告知無法回答 + 提供轉接真人客服 |
| 知識會變嗎?多快要反映? | 決定離線 ingestion pipeline | 會(旺季改政策、FAQ 新增),需合理時間內反映 |
| 要不要顯示出處 / 來源? | 決定 response 帶 sources、可追溯 | 要(回傳 sources 供使用者驗證) |
「怎麼 chunking、怎麼防幻覺 我先標記,等 deep dive 再展開——現在先把 RAG 當成一個 black box:給它一個自包含 query,它回傳 top-K relevant context。這樣我們能先把『提問 → 理解意圖 → 檢索 → 生成』的端到端 online path 串起來。」把細節推遲,保住敘事連貫。
2. 功能性與非功能性需求 (FR / NFR 與設計北極星)
-
功能性需求 (FR)
- 使用者以 自然語言提問,取得基於平台知識庫的 準確回答。
- 系統能從 多種資料來源(FAQ、飯店政策、使用者訂單)檢索 相關資訊作為生成依據。
- 支援 多輪對話(multi-turn),能理解上下文脈絡。
- 無法回答時明確告知,並提供 轉接真人客服 選項。
-
非功能性需求 (NFR)
- 低延遲:端到端 2-3 秒 以內。
- 高準確性:回答必須 grounded(基於檢索文件),不得幻覺(捏造政策 / 虛構訂單狀態)。
- 高可用性:客服是使用者遇到問題的第一接觸點。
- 知識即時性:政策 / FAQ 變動後,合理時間內反映最新資訊。
設計北極星(NFR 直接導出後續所有選型的判準):
| NFR | 導出的設計北極星 | 後續對映選型 |
|---|---|---|
| 高準確 / 不得幻覺 | 答案只能來自檢索 context;品質不足寧可拒答 | RAG、hybrid search + rerank、grounding check、fallback |
| 低延遲 2-3 秒 | 感知延遲優先:先讓字逐字出現 | LLM streaming(TTFT)、平行檢索、gRPC HTTP/2 |
| 多輪上下文 | 送進檢索前先把問題改寫成自包含 | Conversation Store + conversation-aware query rewriting |
| 知識即時性 | online path 不負責更新,獨立離線 pipeline 處理 | Ingestion Pipeline、webhook、queue、write-then-delete |
| 高可用 | 第一接觸點不可掛;外部用即時協定承載推播 | WebSocket、stateless service、轉真人 fallback |
「這是一個 grounded、低感知延遲、上下文感知 的 RAG 系統」——記住這三個詞,後面每個選型都能從它推回去。準確性 > 流暢度 是本題與一般 chatbot 最大的分野。
3. 容量估算 (Back-of-Envelope ~4 min)
本題來源未硬給規模數字,估算的目的不是算到精確,而是 驅動 LLM / vector store / cache 的瓶頸判斷(估算 → 選型的 hand-off)。回連 02-Distributed-Systems/05-Numbers-to-Know。以訂房平台量級做合理假設:
請求流量(chat QPS)
假設 DAU ≈ 1,000 萬,其中 ~2% 當天會用客服 = 20 萬 對話 session/day
每 session ≈ 4 輪問答 → 80 萬 次 /day
QPS = 800,000 ÷ 86,400 s ≈ 9 req/sec(平均);尖峰抓 5x ≈ 45 req/sec
每次請求的成本結構(延遲預算分解)
端到端 2-3 秒 拆解:
query rewriting (1 次 LLM call) ≈ 200-400 ms
embedding query + ANN 檢索 ≈ 50-150 ms
(HYBRID 還要平行打 Booking DB) ≈ 20-50 ms(與檢索平行,取 max)
rerank (cross-encoder) ≈ 50-100 ms
LLM 生成(到 first token) ≈ 200-500 ms ← streaming 起點
LLM 生成(完整答案,逐字 stream) ≈ 1-2 s(使用者邊看邊讀)
知識庫 / 向量儲存
假設知識庫 100 萬 chunks × 1536 維 float32 ≈ 100萬 × 6 KB ≈ 6 GB 向量
→ 單一 vector store 實例可承載;瓶頸不在容量,而在 LLM 推論吞吐
下表數字多為 推導值 / 合理假設(來源僅明確給「2-3 秒端到端」「不得幻覺」「多輪」「知識即時」等輸入,未給 DAU / chunk 數)。重點是 延遲預算分解 與 「瓶頸是 LLM 推論而非儲存」 這兩個結論。
| 維度 | 數字 | 對後續決策的意義 |
|---|---|---|
| Chat QPS | 尖峰 ≈ 45 /sec(推導值) | 不算高,但每 req 含 2 次 LLM call,LLM 是成本中心 |
| 延遲預算 | 2-3 s 端到端(來源) | LLM 生成佔大頭 → 必須 streaming 壓 TTFT |
| LLM call/req | ≥ 2(改寫 + 生成;防幻覺可能 +1) | LLM 推論吞吐是真瓶頸,需 12-Ops-and-Reliability/02-Overload-Protection |
| 向量儲存 | ≈ 6 GB / 100 萬 chunks(推導值) | 容量非瓶頸;檢索品質(recall)才是準確性命門 |
瓶頸不在儲存、不在 QPS,而在 LLM 推論吞吐與延遲。所以優化重心是 (1) streaming 壓感知延遲、(2) 減少 / 並行 LLM call、(3) 過載保護——而非無腦 sharding 向量庫。
4. 用到的積木 / Building Blocks Used
架構即目錄:先給導覽圖,再展開。下表每個決策都對映一個既有 concept note(無 alias 連結);本表即「本案例練到哪些積木」的單一事實來源(single source of truth)。
| 設計決策 | 積木 | 在本題的作用(≤1 行) |
|---|---|---|
| 容量 / 延遲預算分解 | 02-Distributed-Systems/05-Numbers-to-Know | 拆 2-3 秒預算,認定 LLM 推論是瓶頸而非儲存 |
| 檢索後生成(核心架構) | 08-Search-and-AI/02-RAG | 提問前先檢索平台真實資料,再 grounded 生成 |
| 語意檢索 / ANN top-K | 08-Search-and-AI/04-Vector-Database | chunk embedding 存向量庫,query 轉向量做 ANN |
| hybrid search(向量+關鍵字) | 08-Search-and-AI/03-Elasticsearch | 關鍵字精確匹配(飯店名 / 政策編號)補向量 recall |
| 搜尋系統 / rerank 概念 | 08-Search-and-AI/01-Search-System | top-K 後用 cross-encoder reranking 排序最相關 |
| 結構化訂單查詢 | 06-Database-Tech/01-Database | Booking DB 以 user_id / booking_id 查訂單狀態 |
| 多輪對話歷史儲存 | 07-Caching-Storage/04-Redis | Conversation Store 用 Redis(TTL 自動過期 session) |
| 知識更新緩衝 | 09-Messaging-Coord/02-Queue | ingestion 更新事件入 queue,避免 pipeline 過載 |
| 離線知識處理流水線 | 09-Messaging-Coord/01-Data-Pipeline | chunk→embed→寫向量庫 的離線 ingestion pipeline |
| 外部即時雙向通訊 | 01-Networking/05-Realtime-Protocols | WebSocket 承載逐字回覆 + 轉真人推播 |
| 即時更新 / 推播模式 | 10-Design-Patterns/05-Real-time-Updates | streaming 逐 token、typing indicator、轉接通知 |
| 內部 service 同步呼叫 | 03-API-Design/04-RPC-and-gRPC | Chat↔內部服務用 gRPC(HTTP/2 二進位、強型別 contract) |
| LLM 過載保護 | 12-Ops-and-Reliability/02-Overload-Protection | LLM 推論吞吐有限,需限流 / 排隊 / 降級 |
| 答不出 / 拒答的可靠回退 | 12-Ops-and-Reliability/03-Reliable-Delivery | grounding 失敗 / 低分時安全回退轉真人,不丟使用者 |
| 知識空窗 / 無結果監控 | 12-Ops-and-Reliability/04-Observability | 監控「無相關結果」比例、文件 last_updated 告警 |
5. API 設計 (API Design ~3 min)
對話型系統,核心只有一個 POST /chat,但回應要帶 session_id(多輪)與 sources(可追溯)。理據見 03-API-Design/01-API-Design-Framework。
| Method | Path | 用途 | 重點 |
|---|---|---|---|
POST |
/chat |
送出一則訊息並取得回答 | body { session_id?, user_id, message };首次可省 session_id,由系統產生 |
Request / Response 形狀(依來源)
POST /chat
Body: {
session_id, // optional,首次對話可省略,由系統產生
user_id,
message
}
Response: {
session_id,
response, // agent 的回答(實際以 stream 逐字送達)
sources: [ { title, url, snippet } ] // 引用的資料來源,供使用者驗證
}
response 在 spec 上是一個欄位,但 實際傳輸是 streaming——透過 WebSocket / SSE 逐 token 推送,最後再補一則帶 sources 的訊息。把「API 合約」與「傳輸方式」分開講,是展示你懂 streaming 的關鍵動作(詳見 deep dive 6)。sources 不是裝飾——它是 可追溯性(groundedness 的對外證據),呼應防幻覺北極星。
6. 資料模型與儲存選型 (Data Model & Storage Choice ~4 min)
本題的特殊性:同時有結構化(訂單)與非結構化(知識)兩種資料,分別落在不同儲存,由 router 決定查哪個。
Vector Store(非結構化知識:FAQ / 飯店政策 / 幫助文件)
--------------------------------------------------
chunk_id PK
embedding VECTOR(1536) ← ANN 檢索用
text TEXT ← chunk 原文
metadata: {
hotel_id, ← 飯店特定政策才有,供 metadata 過濾
policy_type, ← cancellation / pet / check-in ...
source_title, source_url,
version, last_updated, ← 版本管理 + freshness
expires_at ← 時效性內容(促銷截止)自動過濾
}
Booking DB(結構化訂單:關聯式)
--------------------------------------------------
booking_id PK
user_id INDEX ← 依使用者查訂單
hotel_id FK
status, check_in_date, check_out_date, plan, payment_status ...
Conversation Store(多輪對話歷史)
--------------------------------------------------
session_id PK
user_id
history: [ {role, message, ts}, ... ] ← 使用者訊息 + agent 回覆
(Redis + TTL 自動過期;需長期保存時落關聯式 DB)
儲存選型:
| 資料 | 選擇 | 理由 |
|---|---|---|
| 非結構化知識 | Vector Store(pgvector / Pinecone / Weaviate) | 語意檢索靠 embedding + ANN;metadata 支援 hotel_id / policy_type 過濾 → 08-Search-and-AI/04-Vector-Database |
| 結構化訂單 | 關聯式 Booking DB | 訂單狀態 / 日期是精確查詢,以 user_id / booking_id 直查 → 06-Database-Tech/01-Database |
| 對話歷史 | Redis(TTL) | session 有自然生命週期,TTL 自動過期省維護;需持久保存才落 DB → 07-Caching-Storage/04-Redis |
| 關鍵字索引(hybrid) | 倒排索引 / Elasticsearch | 飯店名、政策編號這類精確匹配,向量搜尋抓不準 → 08-Search-and-AI/03-Elasticsearch、04-Database-Indexing/06-Inverted-Index |
「知識 = 向量、訂單 = 關聯式」這個 雙儲存切分 是本題的地基。不要把訂單塞進向量庫(精確狀態查詢用 ANN 是災難),也 不要把政策文件硬塞關聯式 DB 做 LIKE(語意檢索抓不到)。讓 router 決定查哪邊(deep dive 2)。
7. 高階架構 (High-Level Design ~6 min)
把前面的積木串成端到端流程。RAG 系統有兩條路徑:online path(提問即時回答) 與 offline path(知識 ingestion)——High-Level 只畫 online,offline 留到 deep dive 1。
(A) Online path:提問 → 理解 → 檢索 → 生成(三步)
Client(使用者送出訊息)
│ POST /chat { session_id?, user_id, message }
▼
Chat Service ──(讀對話歷史)──► Conversation Store (Redis)
│ 若無 session_id → 建新 session
▼
Query Processor ← 接收「使用者訊息 + 對話歷史」
├─ Intent detection:FAQ? 飯店政策? 訂單? → 決定查哪些來源
└─ Query rewriting:結合歷史,把「那間能帶狗嗎」改寫成
「台北 A 飯店是否允許攜帶寵物入住?」(self-contained)
▼
Retrieval Service ← 依 intent 路由
├─[KNOWLEDGE / HYBRID]─► Vector Search → Vector Store(ANN top-K chunks)
└─[BOOKING / HYBRID]─► Booking DB Lookup(by user_id / booking_id)
│ (HYBRID 時兩者「平行」發出,結果合併)
▼ ranking / rerank → top-K context
▼
LLM Service ← 組 prompt:
│ System prompt(角色/規範/格式)
│ + 檢索 context(chunks + 訂單資料)
│ + 對話歷史 + 當前問題
│ → LLM 基於 context 生成;context 不足 → 回「無法回答,建議轉真人」
▼
回傳 { response(逐字 stream), sources } → Client
(B) Offline path:知識 ingestion(保 freshness,deep dive 1/5)
知識來源(FAQ 系統 / 飯店後台 / CMS)
│ 內容變更 → webhook / change event
▼
Queue(緩衝更新事件,避免 pipeline 過載)
▼
Ingestion Pipeline: chunking → embedding → 寫入 Vector Store
│ write-then-delete:先寫新版 chunks,確認後再刪舊版(避免知識空窗)
▼
Vector Store(標記 version / last_updated / expires_at)
(C) Scaled 架構(端到端,含 streaming pipeline)
┌──────── 外部:WebSocket(雙向)────────┐
Client ──────────────►│ 逐字回覆 + typing indicator + 轉真人推播 │
└──────────────────┬─────────────────────┘
▼
Load Balancer (L4 / sticky)
│
▼
┌──────── Chat Service (stateless) ────────┐
│ 讀歷史 → 改寫 → 檢索 → 生成(嚴格循序) │
└───┬───────────┬───────────┬──────────────┘
gRPC (HTTP/2 同步) │ │ gRPC server-streaming
▼ ▼ ▼ ▼ (逐 token)
Query Processor Retrieval Service LLM Service
├─► Vector Store (ANN) │
├─► Elasticsearch (keyword)│ ← 推論吞吐瓶頸
└─► Booking DB (訂單) │ 需 overload protection
│
Conversation Store (Redis, TTL) ◄──── Chat Service ────────┘
(逐 token 經 WebSocket 即時轉發給 Client)
[離線]知識來源 → webhook → Queue → Ingestion Pipeline → Vector Store
關鍵 hand-off:外部 WebSocket(承載 streaming + 推播)→ Chat Service(編排循序流程)→ 內部 gRPC(低延遲同步)→ LLM Service 用 gRPC server-streaming 逐 token 吐回,Chat Service 即時經 WebSocket 轉發給 Client。online 與 offline 兩條路徑徹底解耦——這是 RAG 系統的標準骨架(見 08-Search-and-AI/02-RAG)。
8. 深入探討 (Deep Dives ~9 min)
通用機制(ANN 原理、embedding、queue 語意、gRPC vs REST 細節)只在 concept note 解釋;這裡只講「本題怎麼用、選哪個、為什麼」。任一機制超過 ~3 行就砍掉改連結。本題無 home note 的新材料(query rewriting、grounding check、query router、write-then-delete reindex)才在此 inline 教學並登記到 new_concepts_introduced。
8.1 知識庫文件如何 chunking 與 indexing
問題:怎麼把 FAQ / 政策 / 長文切塊並建索引,才能被有效檢索?
為何是瓶頸:chunk 太大 → 含太多不相關資訊、稀釋語意;chunk 太小 → 失去上下文、檢索到也讀不懂。chunking 直接決定檢索品質,而檢索品質決定準確性。
| chunking 策略 | 做法 | 適合場景 |
|---|---|---|
| Fixed-size + overlap | 固定字數(如 500 字)切,相鄰重疊(如 100 字) | 通用、結構不明的長文檔 |
| Structure-based | 依結構切(FAQ 每個 Q&A pair 一塊、政策每段一塊) | 結構化文件(FAQ、條款) |
| Semantic chunking | 依語意邊界切(句間語意相似度下降時切割) | 長篇、主題多樣的文件 |
本題建議做法(按文件型態分流):
- FAQ:每個 Q&A pair 自然成一個 chunk → structure-based(最直覺)。
- 飯店政策:按段落 / 政策條目切,並在每個 chunk 的 metadata 標
hotel_id/policy_type(cancellation / pet / check-in),檢索時可按 metadata 過濾。 - 幫助文件 / 長指南:fixed-size + overlap,避免切割邊界遺失上下文。
Embedding 與索引:每個 chunk 經 embedding model 轉向量,連同 metadata 寫入 Vector Store;檢索時 query 同樣轉向量,用 ANN(approximate nearest neighbor) 找 top-K。
→ 積木:08-Search-and-AI/04-Vector-Database、08-Search-and-AI/02-RAG
hotel_id / policy_type,不直接靠語意檢索?
語意檢索會把「A 飯店的寵物政策」和「B 飯店的寵物政策」都撈出來(語意極近)。但使用者問的是 特定 飯店,必須先用 hotel_id 做 metadata 過濾 再 ANN,否則會把別家飯店的政策誤當 context 餵給 LLM → 幻覺溫床。policy_type 同理可縮小檢索範圍。
8.2 問題同時需要結構化 + 非結構化資料時如何處理(Query Router + 平行檢索)
問題:很多真實問題跨兩種資料。例「我還能免費取消嗎?」需要 訂單(入住日期、飯店 ID)+ 政策(免費取消截止為入住前 X 天)。
為何是瓶頸:若只查一邊,答案必然不完整或錯誤。
Query Processor 分析問題後,把 intent 分為三類:
- KNOWLEDGE_ONLY:純知識(「退款通常幾天?」)→ 只查 Vector Store。
- BOOKING_ONLY:純訂單(「我的訂單編號?」)→ 只查 Booking DB。
- HYBRID:混合(「我還能免費取消嗎?」)→ 同時平行查兩者,結果合併後一起餵 LLM。
Intent 分類可用 LLM(few-shot prompting) 或 輕量分類模型 完成。
| 方案 | 做法 | 取捨 |
|---|---|---|
| A. 一律全查 | 不分類,每次都打向量庫 + 訂單庫 | 簡單;但多餘查詢浪費延遲與成本 |
| B. Router + 平行檢索 | 先分類 intent,HYBRID 時兩查詢平行發出 | 省掉不必要查詢;HYBRID 取兩者 max 而非 sum 延遲 |
選擇與理由:採 B。HYBRID 時 平行(非循序)發出 Vector Search + DB Lookup,總延遲取兩者 max,不疊加;分類用 LLM few-shot 兼顧彈性。
→ 積木:08-Search-and-AI/04-Vector-Database、06-Database-Tech/01-Database
8.3 如何防止 LLM 幻覺(招牌取捨:寧可拒答)
問題:客服場景捏造一條取消政策 → 使用者照做出錯 → 信任危機與客訴,後果特別嚴重。
為何是核心:這直接對應 NFR「不得幻覺」與本題 core_tradeoff——準確 > 流暢。
多層防護(四層,由前到後):
| 層 | 機制 | 目的 |
|---|---|---|
| 1. Prompt | system prompt 明令「只能依 context 回答,不足就說『我無法確定,建議聯繫真人客服』,不得捏造」;要求引用具體 source | 約束 LLM 行為、可追溯 |
| 2. Retrieval | hybrid search(向量 + 關鍵字)提升 recall;rerank(cross-encoder)把最相關 chunks 排前 | 確保 LLM 收到正確 context |
| 3. Validation | grounding check:驗證回答中的關鍵事實(日期 / 金額 / 政策)是否出現在 context;可用另一個 LLM call 或規則引擎 | 攔截已生成的幻覺 |
| 4. Fallback | 當 similarity score 低於閾值 → 主動拒答,不把低品質 context 餵 LLM;明確提供轉真人選項 | 守住底線:寧可不答也不答錯 |
對 LLM 回答做事後驗證:「以下回答是否完全基於提供的 context?有無 context 未提及的事實?」失敗則 回退安全回答。這是「會拒答的準確」的最後一道閘——比第 1 層 prompt 更可靠,因為 prompt 是軟約束、grounding check 是硬檢查。
選擇與理由:四層 疊加,不是擇一。第 2 層(retrieval 品質)是 根本——若 context 本身不相關,LLM 即使守規矩也答不好;第 4 層(低分拒答)是 底線——用「拒答」換「不答錯」,正是本題的招牌取捨。
→ 積木:08-Search-and-AI/01-Search-System(rerank)、08-Search-and-AI/03-Elasticsearch(hybrid keyword)、12-Ops-and-Reliability/03-Reliable-Delivery(fallback 轉真人)
prompt 只能約束「LLM 怎麼用 context」,管不了 context 本身對不對。Garbage-in-garbage-out:若檢索撈錯飯店的政策,LLM 再聽話也會自信地給錯答案,且還會「引用來源」讓錯誤更可信。所以 hybrid search + rerank(提升 recall + precision)是防幻覺的 根本,prompt 只是錦上添花。
8.4 如何處理多輪對話(conversation-aware query rewriting)
問題:後續問題常含代詞(「那間」「它」)或省略主語。直接把「那可以帶寵物嗎?」送進檢索 → 缺上下文 → 撈不到相關文件。
為何是瓶頸:檢索品質崩 → 整個 RAG 崩。
每次提問時,Query Processor 把 對話歷史 + 當前問題 一起送 LLM,要求改寫為 self-contained query:
原始:「那可以帶寵物嗎?」
改寫:「台北 A 飯店是否允許攜帶寵物入住?」
改寫後的 query 再送 Retrieval Service,檢索品質不受代詞 / 省略影響。
對話歷史管理(避免 token 爆炸 / 延遲上升):
- Sliding window:只保留最近 N 輪(如 10 輪)。
- 摘要機制:把早期對話壓縮成簡短摘要。
- 儲存:Redis(TTL 自動過期)或關聯式 DB(需持久保存時)。
→ 積木:07-Caching-Storage/04-Redis(Conversation Store + TTL)
8.5 如何確保知識即時性(Ingestion Pipeline + freshness)
問題:飯店政策隨時變(旺季調整、新增設施),FAQ 持續新增。知識庫沒更新 → 使用者收到過時答案。
為何是瓶頸:grounded 在過時資料 = grounded 在錯資料,一樣傷準確性。
Ingestion Pipeline 設計:
- 知識來源(FAQ 系統 / 飯店後台 / CMS)內容變更時,透過 webhook / change event 通知 pipeline。
- pipeline 對變更文件重跑 chunking → embedding → 寫 Vector Store。
- 為避免大量同時更新壓垮 pipeline,用 queue 緩衝 更新事件、依序處理。
每個 chunk 標 version / last_updated。更新時 先寫入新版 chunks,確認成功後再刪舊版——避免更新中途出現「新的還沒好、舊的已刪」的 知識空窗。時效性內容(促銷截止)標 expires_at,檢索時自動過濾過期 chunk。
監控與告警:追蹤各文件 last_updated,超過更新週期(如飯店政策 90 天未更)→ 告警提醒內容團隊;監控 retrieval「無相關結果」比例,突升代表有新類型問題尚未被知識庫覆蓋。
→ 積木:09-Messaging-Coord/01-Data-Pipeline、09-Messaging-Coord/02-Queue、12-Ops-and-Reliability/04-Observability
8.6 Client 與內部服務的通訊協定選擇
問題:外部(Client ↔ Chat Service)與內部(Chat Service ↔ 其他服務)需求截然不同。
外部:WebSocket vs SSE
| WebSocket | SSE(Server-Sent Events) | |
|---|---|---|
| 通訊方向 | 全雙工(雙向) | 單向(server → client) |
| LLM streaming response | ✅ | ✅ |
| Server 主動推播 | ✅ 原生支援 | ❌ 需額外機制 |
| 實作複雜度 | 較高(連線生命週期 / heartbeat / 重連) | 較低(建在標準 HTTP 上) |
| LB 相容性 | 需支援 WebSocket(sticky / L4 LB) | 標準 HTTP 即可 |
選擇:若只需「使用者問 → agent 逐字回」的簡單互動,SSE 足夠且更簡單(ChatGPT / Claude 採此)。但訂房客服 agent 需要轉接真人客服等 server 主動推播,故 偏向 WebSocket。
內部:gRPC。Chat Service 流程嚴格循序(讀歷史 → 改寫 → 檢索 → 生成),是標準同步 request-response。gRPC 相較 REST:HTTP/2 二進位延遲更低、protobuf 強型別 contract 降低跨團隊溝通成本。
例外:Chat ↔ LLM Service 需 streaming。LLM 逐 token 生成;若等完整答案才回 Client,使用者經歷 2-3 秒空白。改用 gRPC server-streaming:LLM 逐 token 推給 Chat Service,Chat Service 再即時經 WebSocket 轉發 Client。效果是 文字逐字出現,TTFT 從 2-3 秒降到數百毫秒,大幅降低 感知延遲。
→ 積木:01-Networking/05-Realtime-Protocols、10-Design-Patterns/05-Real-time-Updates、03-API-Design/04-RPC-and-gRPC
9. 擴展與瓶頸 (Scaling & Bottlenecks ~3 min)
主動找出系統會先在哪裂開並補強(訓練 drive 而非被動回答)。
| 瓶頸 / 風險 | 補強手段 | 積木 |
|---|---|---|
| LLM 推論吞吐 / 成本成天花板 | 限流 + 排隊 + 降級(尖峰時用較小模型 / 縮短 context);快取常見問答 | 12-Ops-and-Reliability/02-Overload-Protection、07-Caching-Storage/01-Caching |
| 感知延遲(2-3 秒空白) | streaming(gRPC server-stream + WebSocket 轉發) 壓 TTFT 到數百毫秒 | 10-Design-Patterns/05-Real-time-Updates、01-Networking/05-Realtime-Protocols |
| 檢索 recall 不足 → 幻覺 | hybrid search + rerank;metadata 過濾;低分拒答 | 08-Search-and-AI/03-Elasticsearch、08-Search-and-AI/01-Search-System |
| 知識過時 → grounded 在錯資料 | webhook + queue 觸發 ingestion;write-then-delete;expires_at 過濾;last_updated 告警 |
09-Messaging-Coord/01-Data-Pipeline、12-Ops-and-Reliability/04-Observability |
| 對話歷史過長 → token 爆炸 / 延遲上升 | sliding window(最近 N 輪)+ 早期對話摘要 | 07-Caching-Storage/04-Redis |
| WebSocket 連線多 → 連線狀態管理 | sticky session / L4 LB;stateless Chat Service,連線狀態外置 | 01-Networking/06-Load-Balancing |
| 向量庫資料量 / QPS 暴增(100x) | 向量庫分片 + 副本;快取熱門 query embedding 與檢索結果 | 08-Search-and-AI/04-Vector-Database、10-Design-Patterns/01-Scaling-Reads |
「系統 先在 LLM 推論裂開(每 req ≥2 次 LLM call、推論吞吐有限)→ 所以我用 過載保護 + 常見問答快取 + streaming 壓感知延遲;準確性會 先在檢索 recall 裂開 → 用 hybrid search + rerank + 低分拒答 補。儲存與 QPS 不是當前瓶頸。」先指出最可能裂開處再補強,比逐項羅列更有說服力。
10. 取捨總表與收尾 (Trade-off Recap & Wrap-up ~1 min)
| 決策點 | 選擇 | 放棄了什麼 | 為什麼值得 |
|---|---|---|---|
| 答案來源 | 完全 grounded + 低分拒答轉真人 | LLM 預訓練知識的「什麼都答」 | 客服捏造政策的代價是信任崩潰(招牌取捨) |
| 防幻覺 | 四層防護(prompt/retrieval/grounding/fallback) | 單靠 prompt 的簡單 | prompt 是軟約束,需 retrieval 根本 + grounding 硬檢查 |
| 多源查詢 | Query Router + HYBRID 平行檢索 | 「一律全查」的簡單 | 省多餘查詢;平行取 max 延遲不疊加 |
| 多輪上下文 | conversation-aware query rewriting | 直接送原始問題 | 代詞 / 省略會讓檢索崩,改寫成自包含才檢索得到 |
| 感知延遲 | streaming(gRPC stream + WebSocket) | 等完整答案的簡單 | TTFT 數百毫秒,把 2-3 秒空白變成逐字出現 |
| 外部協定 | WebSocket(非 SSE) | SSE 的簡單 | 需 server 主動推播(轉真人 / typing) |
| 知識更新 | 離線 ingestion + write-then-delete | online path 即時改 | online/offline 解耦;更新無知識空窗 |
「整題的單一錨點是 grounded:答案只能來自我們檢索到的平台真實資料,檢索品質不夠就拒答轉真人——我寧可少答,也不讓 LLM 用預訓練知識捏造政策。其餘都是把這個錨點撐起來的標準打法:RAG 雙儲存 + router 平行檢索 拿到對的 context、hybrid search + rerank + grounding check 守準確、query rewriting 撐多輪、streaming 壓感知延遲、離線 pipeline 保即時。瓶頸在 LLM 推論而非儲存,所以過載保護與快取常見問答是主要擴展手段。」引用既有決策用連結,不重新推導。
後續追問演練 (Interviewer Follow-ups — "What if…")
因為答案必須基於 平台真實資料(FAQ、飯店政策、使用者訂單),這些是 LLM 預訓練 沒有 的私有 / 即時資料。直接問 LLM 會用過時或捏造的知識作答(幻覺)。RAG 先 檢索 平台資料、再 grounded 生成,並回傳 sources 讓答案可追溯。見 08-Search-and-AI/02-RAG。
它是 HYBRID intent:需要 結構化訂單(Booking DB:入住日期、飯店 ID)+ 非結構化政策(Vector Store:該飯店免費取消截止為入住前 X 天)。Query Router 判為 HYBRID 後 平行 發出兩查詢,合併結果一起餵 LLM。少查任一邊答案都不完整。
是防幻覺的 第三層(output validation):對已生成的回答驗證「關鍵事實(日期/金額/政策)是否都出現在檢索 context 中」,可用另一個 LLM call 或規則引擎。失敗則回退安全回答(轉真人)。比第一層的 prompt 約束更可靠,因為 prompt 是軟約束、grounding check 是硬檢查。見 12-Ops-and-Reliability/03-Reliable-Delivery。
LLM 推論先裂(每 req ≥2 次 call、推論吞吐 / 成本有上限)。依序:(1) 快取常見問答(同樣的 FAQ 問題直接回快取,跳過 LLM);(2) 過載保護——限流 + 排隊,尖峰降級到較小模型 / 縮短 context;(3) Chat Service stateless 水平擴展;(4) 向量庫分片 + 副本扛檢索讀。儲存非瓶頸。見 12-Ops-and-Reliability/02-Overload-Protection、07-Caching-Storage/01-Caching、08-Search-and-AI/04-Vector-Database。
這類問題是 KNOWLEDGE_ONLY 且答案穩定——可做 語意快取(semantic cache):把改寫後 query 的 embedding 與標準答案快取,新問題 embedding 與快取做相似度比對,命中就直接回,完全跳過檢索與 LLM 生成。配 TTL,知識更新時失效。core 概念見 07-Caching-Storage/01-Caching。
觸發 fallback:當 LLM 不可用(或 grounding 失敗 / 低分),系統 明確告知無法回答並提供轉接真人客服——這正是 FR 4 與 NFR 高可用的設計。即「會拒答的準確」延伸成「會降級的可用」:寧可轉真人,不讓使用者卡死。見 12-Ops-and-Reliability/03-Reliable-Delivery、01-Networking/07-Fault-Handling。
準則是 是否需要 server 主動推播。SSE 是 單向(server→client),足以做「逐字回覆」;ChatGPT 只需這個。但訂房客服還要 轉接真人通知、typing indicator、agent 主動推播——這些是 server 主動發起的雙向互動,SSE 需額外機制、WebSocket 原生支援。代價是 WebSocket 連線生命週期管理較複雜、LB 需支援。見 01-Networking/05-Realtime-Protocols。
內部流程是 嚴格循序的同步 request-response(讀歷史→改寫→檢索→生成),gRPC 的 HTTP/2 二進位 延遲低於 REST,protobuf 強型別 contract 降低跨團隊溝通成本。例外是 LLM Service:它 逐 token 生成,用 gRPC server-streaming 邊生成邊推,Chat Service 再經 WebSocket 即時轉發——把感知延遲(TTFT)從秒級壓到數百毫秒。見 03-API-Design/04-RPC-and-gRPC。
若 先刪後寫 或原地改,更新中途會出現 知識空窗——舊版已沒、新版還沒好,這段時間檢索不到該知識,使用者收到「無法回答」。write-then-delete:先寫新版 chunks、確認成功後再刪舊版,全程至少有一版可檢索。配合 version / last_updated metadata 管理。見 09-Messaging-Coord/01-Data-Pipeline。
監控 retrieval 階段「無相關結果」的比例(similarity 普遍低於閾值)。若此比例突升,代表有 新類型問題尚未被知識庫覆蓋 → 告警內容團隊新增對應文件。另外追蹤各文件 last_updated,超過週期(如政策 90 天未更)也告警。這是把 observability 接進 RAG 品質回路。見 12-Ops-and-Reliability/04-Observability。
主動回想:白板重建 (Whiteboard from Scratch)
不看上文,照下面提示把整個設計重建一次。每格都該能 30 秒內口述。
常見陷阱 (Exam Traps for This Problem)
What:把它當一般 chatbot,直接讓 LLM 回答。
Why:答案必須基於平台真實資料,LLM 預訓練 沒有 飯店政策 / 使用者訂單 → 直接答 = 幻覺。
正確做法:用 RAG——先檢索平台資料再 grounded 生成,回傳 sources 可追溯。見 08-Search-and-AI/02-RAG。
What:使用者問「那間能帶狗嗎?」,直接拿這句去做向量檢索。
Why:「那間」缺上下文,檢索系統撈不到對的飯店政策 → 檢索崩 → RAG 崩。
正確做法:conversation-aware query rewriting——結合對話歷史改寫成自包含 query 再檢索。見 07-Caching-Storage/04-Redis。
What:在 system prompt 寫「不要捏造」就收工。
Why:prompt 是 軟約束,管不了 context 本身對不對;若檢索撈錯料,LLM 會自信地給錯答案還引用來源。
正確做法:四層防護——retrieval 品質(hybrid+rerank)是根本,再加 grounding check 硬驗證、低分拒答 fallback。見 08-Search-and-AI/01-Search-System、12-Ops-and-Reliability/03-Reliable-Delivery。
What:為了「統一檢索」,把訂單狀態也 embedding 進 Vector Store。
Why:訂單狀態是 精確結構化查詢(by user_id / booking_id),用 ANN 做近似檢索會撈錯訂單、漏訂單。
正確做法:雙儲存——訂單走關聯式 Booking DB 精確查、知識走向量庫語意查,由 router 決定查哪邊。見 06-Database-Tech/01-Database。
What:Chat Service 等 LLM 吐完整段才回 Client。
Why:LLM 生成要 2-3 秒,使用者經歷整段空白 → 感知延遲糟。
正確做法:streaming——LLM 用 gRPC server-streaming 逐 token 推,Chat Service 經 WebSocket 即時轉發,TTFT 降到數百毫秒。見 10-Design-Patterns/05-Real-time-Updates。
What:reindex 時先 delete 舊版、再 insert 新版。
Why:中途出現 知識空窗,那段時間檢索不到該知識 → 使用者收到「無法回答」。
正確做法:write-then-delete——先寫新版、確認成功後再刪舊版,全程至少一版可檢索。見 09-Messaging-Coord/01-Data-Pipeline。
Related Notes
- 08-Search-and-AI/02-RAG
- 08-Search-and-AI/04-Vector-Database
- 08-Search-and-AI/03-Elasticsearch
- 08-Search-and-AI/01-Search-System
- 06-Database-Tech/01-Database
- 07-Caching-Storage/04-Redis
- 07-Caching-Storage/01-Caching
- 09-Messaging-Coord/01-Data-Pipeline
- 09-Messaging-Coord/02-Queue
- 01-Networking/05-Realtime-Protocols
- 01-Networking/06-Load-Balancing
- 01-Networking/07-Fault-Handling
- 10-Design-Patterns/05-Real-time-Updates
- 03-API-Design/04-RPC-and-gRPC
- 03-API-Design/01-API-Design-Framework
- 04-Database-Indexing/06-Inverted-Index
- 12-Ops-and-Reliability/02-Overload-Protection
- 12-Ops-and-Reliability/03-Reliable-Delivery
- 12-Ops-and-Reliability/04-Observability
- 02-Distributed-Systems/05-Numbers-to-Know