Eric TechBlog
AIAI Assistant

Architecture Overview

AI 個人助理的系統架構總覽,包含需求分析、模組拆分、請求生命週期,以及從原型到生產的演進路徑。

我們要設計什麼?

這個系列會一路用同一個例子來說明:一個以 email 檢索為核心的 AI Assistant。用戶可以用自然語言和它對話,它能:

  • 回答問題,並記得過去聊過的事
  • 搜尋用戶的 email,先找出相關信件,再視需要讀全文
  • 從 email 中提取 deadline、會議時間或待辦事項
  • 操作外部服務(行事曆、任務管理),但重要操作需要人類確認
  • 即時串流回應,而非等幾秒後一次性輸出

聽起來簡單,但當你把這些能力拆開來看,背後是一個有相當複雜度的分散式系統。

這一系列與 RAG 系列怎麼搭配?

這裡會聚焦在「如何把 AI Assistant 做成一個完整系統」。如果你想深入理解檢索本身,像是 Chunking、BM25、Embeddings、RRF 與 Reranking,建議搭配閱讀 RAG Introduction 與後續子文章。


需求分析

功能性需求

能力描述
對話支持多輪對話,記住對話脈絡
搜尋在非結構化資料(email)中找到精確答案
記憶跨對話記住用戶偏好、過往資訊
工具操作整合外部服務,能讀也能寫
HITL 安全機制寫入操作前取得用戶確認

非功能性需求

需求目標
延遲首個 token 在 1-2 秒內出現(串流)
成本控制每次對話的 token 消耗
可靠性任一子系統失敗不應讓整個對話崩潰
擴展性從單用戶原型可以演進到多用戶生產系統
安全性敏感操作不能在無人確認下執行

模組拆分

一個 AI Assistant 系統可以拆成以下核心模組:


一次請求的生命週期

用戶發送「幫我找 John 上週寄來、提到合約截止日的 email」後,系統會經歷以下步驟:

Phase 1:前處理(~100ms)

  1. 訊息驗證:檢查訊息格式、確認最後一則是用戶訊息
  2. HITL 檢查:是否有待處理的用戶決定?有的話先處理
  3. 上下文組裝
    • 搜尋相關記憶(語意搜尋,取 top 3)
    • 搜尋超出視窗的歷史訊息(語意搜尋,取 top 10)
    • 搜尋過去的相關對話(語意搜尋,取 top 3)

Phase 2:HITL 前置判斷(~500ms,可選)

Approval Agent:用輕量 Agent 判斷此請求是否需要操作外部服務

  • 如果需要 → 送出確認請求,結束此次請求,等待用戶回覆
  • 如果不需要 → 繼續

Phase 3:執行(~2-10s)

  1. 執行已確認的操作:如果用戶在上一輪批准了某個工具,先執行它
  2. 主 Agent 循環:帶著完整上下文(記憶 + 歷史 + 工具),進入多步驟的思考-行動-觀察循環
  3. 串流回應:推理過程、工具呼叫、文字回應即時串流給前端

Phase 4:後處理(背景,不阻塞)

  1. 持久化:儲存用戶訊息和 AI 回應
  2. 記憶提取:從對話中提取值得記住的資訊
  3. 對話反思:生成對話摘要、學習筆記

關鍵設計決策

1. 為什麼要拆出 Approval Agent?

直覺的做法是讓主 Agent 直接呼叫工具,呼叫到需要 HITL 的就暫停。問題是:主 Agent 可能先做了好幾步不需要 HITL 的操作(搜尋 email、分析內容),然後才決定要操作行事曆。這時候暫停,前面的工作就浪費了。

更好的做法:在主 Agent 啟動前,用一個輕量的 Approval Agent 快速判斷「這個請求有沒有可能需要外部操作」。如果需要,立刻送出確認請求。用戶批准後,再讓主 Agent 帶著「已批准」的資訊開始工作。

2. 為什麼後處理要異步且容錯?

記憶提取和對話反思都是「錦上添花」的功能,失敗了也不應該讓用戶看到錯誤。用 Promise.allSettled 而非 Promise.all,確保任一後處理失敗不影響其他。

3. 為什麼只傳最新一則訊息給 API?

傳統做法是前端傳整個訊息歷史。但對話越長,payload 越大。既然伺服器已經持久化了所有歷史訊息,前端只需要傳最新的一則。伺服器自己從 DB 讀取歷史,搭配語意搜尋只取相關的部分。

這個設計同時解決了兩個問題:

  • 減少網路傳輸
  • 控制送進 LLM 的 token 數量

4. 為什麼上下文要分四層?

層級來源數量作用
記憶跨對話提取≤3用戶偏好、背景知識
關聯對話過去的對話≤3類似問題的經驗
歷史訊息同一對話的舊訊息≤10被視窗截斷的上下文
最近訊息同一對話≤10當前對話脈絡

每一層用語意搜尋篩選最相關的,而非全部塞進去。這在保持回答品質的同時,嚴格控制了 token 預算。


技術選型

層級選擇原因
前端框架Next.js (App Router)SSR + API Route + Server Actions 一站式
AI SDKVercel AI SDK統一的 LLM 介面、串流支持、工具定義
LLMGemini 2.5 Flash速度快、成本低、支持結構化輸出
EmbeddingGemini Embedding與 LLM 同一提供者,簡化管理
外部整合MCP over HTTP標準協議、動態發現工具
持久化JSON 檔案 → SQL DB原型用檔案,生產用資料庫

模型無關設計

模型選擇取決於你的需求。本系統的設計是模型無關的,透過 Vercel AI SDK 的抽象層,切換模型只需要改一行配置。選擇 Gemini 是因為它在速度和成本上對個人助理場景有優勢,但架構本身不綁定任何特定模型。


從原型到生產的演進路徑

  • 單用戶,本地運行
  • JSON 檔案存儲
  • 所有搜尋在記憶體中完成
  • 沒有認證、沒有多租戶
  • 加入認證(NextAuth / Clerk)
  • JSON → SQLite / PostgreSQL
  • 記憶體搜尋 → 向量資料庫(Pinecone / pgvector)
  • 加入速率限制
  • 部署到 Vercel / Railway
  • 多租戶隔離
  • Embedding 預計算 pipeline(寫入向量索引,不在請求路徑上)
  • 訊息佇列處理後處理任務(記憶提取、反思)
  • 可觀測性(OpenTelemetry / Langfuse)
  • A/B 測試不同的 system prompt
  • Evaluation pipeline 持續評估回答品質

接下來

這篇文章給了你系統的全貌。接下來的每一篇會深入一個模組:

每篇都可以獨立閱讀,但建議按順序,因為後面的設計會建立在前面的基礎上。如果你在閱讀過程中發現自己卡在檢索原理,可以回跳到 RAG 系列補強底層觀念。

Last updated on

On this page