System Design 是 FDE 面試最能展現工程深度的地方。
面試官不是要你「寫出可以跑的程式碼」,
他想看的是:你在有限資訊下,怎麼做設計決策、怎麼說清楚 trade-off。
面試情境
面試官:「請你設計一個供企業內部使用的 AI 知識庫問答系統,員工可以用自然語言查詢公司政策、產品說明和技術文件。第二題:設計一個 AI Copilot,讓員工用自然語言查詢公司內部數據,例如『今年 Q3 的營收比 Q2 成長了多少?』」
一、System Design 面試的本質
面試官考系統設計,不是要標準答案,是要看三件事:
考點 1:釐清問題的習慣
你有沒有在設計前先問問題?
→ 不假設,先問。沒有釐清需求就開始畫圖是大扣分。
考點 2:Trade-off 思維
你說的不是「最好的方案」,而是:
「在這個場景下,我選 X 而不是 Y,因為...」
考點 3:生產環境的現實感
Auth、RBAC、Scale、Cost、Failure Mode——
有沒有考慮到,是高手和普通人的分水嶺。
二、第一題:企業知識庫 Chatbot
步驟一:釐清需求(你要主動問)
你應該問的問題:
需求面:
├── 同時使用的用戶數量級?(100 人 vs 10 萬人,架構差很多)
├── 文件量多大?(1GB vs 1TB)
├── 回答需要引用文件來源嗎?
└── Latency 要求?(秒級 vs 毫秒級)
安全面(這是 FDE 常被忽略的):
├── 不同部門能看的文件不同嗎?→ 決定要不要 RBAC
├── 有合規要求嗎?(GDPR、SOC 2)
└── 資料能放在公有雲嗎?
這些問題決定你的架構複雜度。
先問,再設計。
步驟二:完整系統架構
┌─────────────────────────────────────────────────────────────┐
│ 用戶層 │
│ Browser / Slack Bot / Mobile App │
└───────────────────────────┬─────────────────────────────────┘
│ HTTPS
┌───────────────────────────▼─────────────────────────────────┐
│ API Gateway 層 │
│ ├── SSO Token 驗證(Google Workspace / Okta) │
│ ├── Rate Limiting(防止濫用) │
│ └── 請求日誌(Audit Log 起點) │
└───────────────────────────┬─────────────────────────────────┘
│ 已驗證的用戶 context
┌───────────────────────────▼─────────────────────────────────┐
│ Chatbot Service 層 │
│ │
│ ┌────────────────────┐ ┌─────────────────────────────┐ │
│ │ Query Processor │ │ RBAC Module │ │
│ │ ├── 意圖分類 │ │ 「這個用戶能看哪些文件?」 │ │
│ │ └── Query Rewrite │ │ 在查詢前過濾,不是查完再過濾 │ │
│ └────────────────────┘ └─────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ RAG Engine │ │
│ │ Query → Embedding → [Vector DB + RBAC Filter] │ │
│ │ → Reranker → Context Injection → LLM │ │
│ └──────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────┬───────────▼──────────┬──────────────────────┐
│ Cache Layer │ Response Generator │ Logging & Monitoring│
│ (相同問題) │ (加入引用來源) │ (審計 + 成本追蹤) │
└───────────────┴───────────────────────┴──────────────────────┘
設計決策一:Authentication 的選型
選項比較:
API Key JWT + SSO
────────────────────────────────────────────────────────
適合場景 機器對機器 人類用戶登入
身分追蹤 無(共用 key) 有(每個 token 帶用戶身分)
RBAC 支援 無(要額外設計) 原生(token payload 帶角色)
Token 輪換 麻煩 自動(SSO refresh)
企業整合 困難 直接接 Google Workspace / Okta
結論:企業知識庫選 JWT + SSO
token payload 帶 user_id、department、roles,
後面所有服務直接讀,不需要每次去查資料庫
面試官最想聽到的一句話:
「JWT token 的 payload 裡我會放 user_id 和 roles,這樣 RBAC 過濾不需要額外的資料庫查詢,每個 request 都能 stateless 地做權限判斷。」
設計決策二:RBAC 過濾的位置
這是面試最常被追問的細節。
兩種方案:
方案 A:查完向量 DB 後過濾(Post-filter)
向量 DB 查 Top-20
↓
過濾掉無權限的文件
↓
剩下 Top-3(品質差,因為前幾名都被移除了)
問題:
├── 給 LLM 的 context 品質不可預測
├── 如果高 rank 的文件都是機密,剩下的是低相關的文件
└── 可能造成 LLM 答錯(資料不夠)
方案 B:查詢時就過濾(Pre-filter)← 正確做法
帶著權限 filter 去查向量 DB
(只在用戶有權限的 namespace / collection 裡搜尋)
↓
查到的 Top-5 都是有權限且相關的文件
↓
Context 品質穩定
代價:
└── 如果 filter 太嚴,搜尋空間縮小,
ANN 的 recall 可能略降
→ 解法:per-role 的 collection 或 namespace 隔離
設計決策三:Cache 策略
Cache 的三個邊界問題:
問題 1:Cache key 怎麼設計?
❌ 只用問題文字 → 相同問題但不同角色,看到的答案不同
✅ hash(問題 + 用戶角色清單) → 不同角色分開 cache
問題 2:哪些問題適合 Cache?
適合: 不適合:
「年假幾天?」 「我的訂單狀態?」(個人化)
「請假流程是什麼?」 「今天的庫存量?」(即時資料)
「公司福利有哪些?」
判斷標準:
├── 答案是否與用戶身分無關(除了角色)
└── 答案是否在 TTL 內不會改變
問題 3:文件更新時怎麼 invalidate?
文件更新 → 需要主動清除所有引用此文件的 cache
設計:每份文件 embed 進 DB 時,記錄它影響哪些 cache tag
文件更新時,按 tag 批次清除 cache
設計決策四:Query Rewriting
為什麼要 Query Rewriting?
用戶原始問題:「我剛入職,想知道年假怎麼算」
↑
包含無關的個人背景,
直接拿去做向量搜尋,效果差
改寫後:「年假計算方式 員工請假規定」
↑
聚焦核心 intent,搜尋 recall 更高
什麼時候值得做 Query Rewriting?
值得: 不值得:
├── 問題很口語 ├── 問題已經是關鍵字形式
├── 問題包含個人化背景 ├── 低流量系統(額外 LLM 呼叫成本不划算)
└── RAG 召回率明顯不足 └── Latency 要求嚴格(Query Rewriting 增加 1 次 LLM 呼叫)
三、第二題:Internal AI Copilot
這題比知識庫 Chatbot 難,因為要即時查詢結構化資料,不是搜尋文件。
核心差異
知識庫 Chatbot:
問題 → 查文件(非結構化)→ RAG → LLM 回答
Internal Copilot:
問題 → 理解數據意圖 → NL2SQL → 查資料庫 → LLM 組合回答
難度增加的地方:
├── SQL 可能查錯(幻覺變成「查錯資料」)
├── 多來源 JOIN 的邏輯複雜
├── 資料欄位層級的 RBAC(不只是文件層級)
└── 大查詢的成本控制(BigQuery 按掃描量計費)
完整系統架構
┌────────────────────────────────────────────────────────────────┐
│ 用戶輸入 │
│ 「今年 Q3 的營收比 Q2 成長了多少?」 │
└───────────────────────────┬────────────────────────────────────┘
│
┌───────────────────────────▼────────────────────────────────────┐
│ Intent Classifier │
│ 分類:數據查詢 / 文件查詢 / 混合查詢 │
│ 本題 → 數據查詢,走 NL2SQL 路徑 │
└───────────────────────────┬────────────────────────────────────┘
│
┌───────────────────────────▼────────────────────────────────────┐
│ NL2SQL Agent │
│ │
│ 輸入:自然語言問題 + Schema Context + 用戶權限 │
│ 輸出:SQL(只允許 SELECT,有語法驗證) │
│ │
│ 錯誤處理: │
│ SQL 語法錯誤 → 把錯誤訊息反饋給 LLM → 自我修正(最多 3 次) │
└───────────────────────────┬────────────────────────────────────┘
│
┌───────────────────────────▼────────────────────────────────────┐
│ Tool Router(並行執行) │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌──────────────────┐ │
│ │ BigQuery Tool │ │ CRM Tool │ │ ERP Tool │ │
│ │ 財務/銷售數據 │ │ 客戶/訂單 │ │ 庫存/供應鏈 │ │
│ └───────┬────────┘ └───────┬────────┘ └────────┬─────────┘ │
│ └──────────────────┬┘ │ │
│ └────────────────────┘ │
│ ↓ │
│ Data Aggregator(整合多來源) │
└───────────────────────────┬────────────────────────────────────┘
│ 結構化資料
┌───────────────────────────▼────────────────────────────────────┐
│ LLM(把數字轉成自然語言回答) │
│ 回答:「Q3 營收為 $4.2M,較 Q2 的 $3.8M 成長 10.5%」 │
│ + 標注:數據來源:BigQuery sales_quarterly │
└────────────────────────────────────────────────────────────────┘
設計決策一:NL2SQL 的安全邊界
最重要的安全設計:
限制 1:只允許 SELECT
└── 用 AST 解析,不能用 keyword 比對(可被繞過)
└── 所有查詢用 read-only 的 Service Account 執行
限制 2:Schema 資訊最小化
└── 只給 LLM 看用戶有權限的 table 的 schema
└── 敏感欄位(員工薪資)從 schema context 裡排除
限制 3:執行前 Dry Run(BigQuery 支援)
└── 先估算掃描量,超過上限拒絕執行
└── 防止惡意或失控的 SQL 造成高額帳單
限制 4:SQL 審計
└── 每條執行的 SQL 都記錄到 Audit Log
└── 可以事後追查「AI 查了什麼資料」
設計決策二:NL2SQL 的失敗模式
失敗模式分類:
類型 A:SQL 語法錯誤
原因:LLM 生成的 SQL 有語法問題
處理:捕捉錯誤 → 把錯誤訊息回饋給 LLM → 讓它自我修正
最多重試 3 次,3 次都失敗 → 回報「無法處理此查詢」
類型 B:SQL 語法正確但語意錯誤
例子:用戶問「Q3 業績」,LLM 查了 Q4 的 table
原因:LLM 誤解了 schema 或問題
處理:這是最難自動偵測的失敗
→ 方案:執行後讓 LLM 對照問題驗證結果合理性(Self-check)
→ 根本解:讓用戶 review 生成的 SQL 再執行(Human-in-the-loop)
類型 C:問題超出 Schema 範圍
例子:用戶問「競品的市佔率?」
處理:LLM 偵測到 schema 裡沒有這個資料 → 明確說「資料庫裡沒有此資訊」
不能亂猜,不能幻覺
失敗率預期(實務參考):
簡單單表查詢:成功率 ~90%
複雜多表 JOIN:成功率 ~60-70%
→ 這個失敗率對生產系統意味著什麼?你要告訴客戶。
設計決策三:多來源查詢的並行策略
場景:用戶問「哪個銷售員今年業績最好,他的客戶滿意度怎麼樣?」
需要:BigQuery(銷售業績) + CRM(客戶滿意度)
策略比較:
順序執行:
BigQuery(500ms)→ CRM(300ms)→ 合計 800ms
並行執行(正確做法):
BigQuery(500ms)┐
├→ 合計 500ms(節省 37%)
CRM(300ms) ┘
但並行有前提:
├── 兩個查詢互相獨立(不需要 A 的結果才能做 B)
└── 如果需要 A 的結果才能決定 B 的 query → 不能並行
依賴關係判斷:
NL2SQL Agent 輸出查詢計畫時,同時輸出依賴關係圖(DAG)
執行引擎按 DAG 決定哪些查詢可以並行
四、兩題的對比分析
| 知識庫 Chatbot | Internal Copilot | |
|---|---|---|
| 資料類型 | 非結構化(文件) | 結構化(資料庫) |
| 核心技術 | RAG + Vector DB | NL2SQL + Tool Router |
| 幻覺形式 | 答案與文件不符 | SQL 查錯資料 |
| RBAC 難度 | 中(文件分類) | 高(欄位層級控制) |
| Latency | 較低(向量搜尋快) | 較高(SQL 查詢可能慢) |
| Cache 效益 | 高(政策問題重複率高) | 低(數據每日更新) |
| 成本主要來源 | LLM token + Vector DB | LLM token + BigQuery 掃描量 |
五、面試官地雷題
地雷 1:「RBAC 過濾為什麼要在查詢前而不是查詢後?」
答:Pre-filter 讓向量 DB 只在用戶有權限的文件空間裡搜尋。
Post-filter 可能讓 Top-K 的高相關文件全被過濾掉,
最後 LLM 只拿到低相關的文件,回答品質不穩定。
地雷 2:「NL2SQL 的 SQL 錯了,你怎麼辦?語法錯和語意錯分別怎麼處理?」
答:語法錯可以捕捉 error message 讓 LLM 自我修正(Self-Healing Loop)。
語意錯更難——SQL 跑成功了但查的是錯的資料。
對於高風險查詢,需要 Human-in-the-loop:
先把生成的 SQL 給用戶確認,再執行。
地雷 3:「Cache 用 role 做 key,但一個用戶可能有多個 role,組合爆炸怎麼辦?」
答:不用所有 role 的全排列,用「能看到的 permission set」做 cache key。
同樣 permission set 的用戶共享 cache。
或者,只 cache role 明確定義的場景,
避免過度複雜的 cache 設計。
地雷 4:「BigQuery 按掃描量計費,AI 生成的 SQL 可能全表掃描,你怎麼控制成本?」
答:執行前先做 Dry Run(BigQuery 支援),
估算掃描量,超過閾值拒絕執行並提示用戶縮小範圍。
另外,在 schema context 裡加入 partition 和 clustering 欄位的提示,
引導 LLM 生成有過濾條件的 SQL(WHERE 指定時間範圍 / 地區)。
六、面試回答完整示範
面試官期待聽到的回答結構:
第一分鐘(釐清需求):
「在開始設計之前,我想先確認幾個關鍵需求。
不同部門能看的文件不同嗎?這決定我是否需要 RBAC。
Latency 要求大概是多少?這影響我的 Cache 策略。
資料有合規要求嗎?這影響部署在哪個 Region。」
第二到五分鐘(高層架構):
「好,基於這些需求,我的架構分四層:
API Gateway 負責 Auth 和 Rate Limiting,
Chatbot Service 負責 RBAC 和 RAG 邏輯,
Cache Layer 用 role-aware key 避免資料洩漏,
Logging 記錄每個 request 的 user_id 和文件引用,供審計用。」
第三部分(關鍵設計決策):
「我想特別說兩個設計決策:
第一,RBAC 要在向量搜尋前就過濾,不是搜完再過濾——
原因是 post-filter 會破壞 Top-K 的相關性。
第二,Cache key 要帶用戶的 role set——
同一個問題,Manager 和 Employee 看到的答案不同。」
最後(Trade-off 和 Failure):
「這個設計最可能出問題的地方有兩個:
第一,文件更新後 Cache invalidation 的時間窗口——
用戶可能看到舊的回答,我的方案是文件更新時主動 purge 相關 cache。
第二,RBAC pre-filter 縮小了搜尋空間,
如果某個 role 的文件很少,recall 可能下降——
監控 per-role 的回答準確率可以及早發現。」
FDE 的核心不是把架構圖畫得滿,
而是說清楚每個設計決策背後的 trade-off,
以及你是在什麼場景條件下做了那個選擇。
