構建 Linda:從零打造 AI 助手
工程
深入解析 Linda Assistant 背後的架構決策——從 SwiftUI 到伺服器端 AI 智能體。

從零開始創建一個全棧 AI 助手是一個複雜而耗時的過程。尤其是在市場上已經有多個現成的助手——ChatGPT、Claude、Gemini——由模型提供商打造,功能強大到足以吸引用戶。ChatGPT 在 iOS、Android 和 macOS 上擁有原生應用體驗,提供從編碼到圖像生成、從深度研究到日常對話、從寫作輔助到學習夥伴的全方位功能。那我們為什麼還要再造一個助手?
發起方式的差異
傳統 AI 助手是用戶發起型的——用戶觸發第一條訊息,助手作出回應。這適用於問答和基於任務的互動,但本質上是被動和回應式的。在大多數科幻電影和劇集中,我們想像的 AI 助手是主動的、「智能體化」的——能夠自主處理資訊並做出決策,及時且數據驅動。
我們希望智能體能主動監控外部世界的數據,為用戶總結資訊,並代表用戶採取行動——預訂餐廳、發送郵件,甚至根據用戶的日程和收件匣生成每日簡報。
這種發起方式的根本差異驅動了我們所有的架構決策。

系統架構概覽
Linda 由六個可獨立部署的服務組成,在 Kubernetes 上編排:
Next.js 後端作為中心樞紐——處理 API 路由、SSE 串流傳輸、身份驗證和資料庫存取。當任務到達時,它將繁重的工作委託給獨立運行智能體循環的 Worker 進程,Worker 從 RabbitMQ 消費任務並透過同一訊息代理發佈串流事件。Celery 調度器(基於 Python,由 RedBeat 提供持久化)處理定期任務,如每日簡報。
在基礎設施層面,Redis 管理串流數據塊快取、序列編號和活躍會話追蹤,而 Mem0 透過 Upstash Vector 提供基於向量搜尋的長期記憶。
串流傳輸問題
為什麼 HTTP 串流傳輸還不夠
HTTP 串流傳輸(Server-Sent Events)是即時傳遞 AI 回應的標準方式。當用戶發送訊息時,後端開啟到模型提供商的串流並即時轉發數據塊給客戶端。這對傳統的請求-回應模式運作良好。
但當智能體是發起者時會發生什麼?用戶並沒有在等待一個開啟的 HTTP 連接——他們可能已經關閉了應用。當沒有人在監聽時,我們不能簡單地開啟一個串流。
此外,在傳統設計中,智能體循環運行在與 HTTP 端點相同的進程中。但由於我們將智能體分離到獨立的 Worker 進程中,我們需要一種方式來橋接數據塊的生產端(Worker)和傳輸端(SSE 端點)。
使用 RabbitMQ 和 Redis 的解耦串流傳輸
我們的解決方案使用訊息佇列將數據塊生產與數據塊傳輸分離:

每個數據塊透過 Redis INCR 獲得單調遞增的序列號,客戶端可以檢測間隔並請求重放。所有數據塊也快取在 Redis 中,TTL 為 1 小時,這意味著後加入的客戶端可以從快取重放完整回應。活躍會話標誌防止重複處理——如果 Worker 已經在處理某個會話,新任務會被直接跳過。當智能體完成但用戶沒有活躍串流連接時,推送通知確保主動回應仍能到達用戶。
串流重放確保可靠性
網路中斷和應用後台化在流動裝置上是常態。當 iOS 應用重新連接時,它發送最後接收的序列號,只接收缺失的數據塊。這為我們提供了至少一次傳遞保證,而無需 WebSocket 的複雜性。
智能體循環
Linda 的核心是智能體循環——由 Vercel AI SDK 的 streamText 和工具調用驅動的多步 AI 推理過程。
工作原理
當 Worker 從 RabbitMQ 接收任務時,它從資料庫載入對話歷史並根據用戶權限建構工具集。然後運行 streamText,最多 20 個推理步驟,每個步驟可以涉及工具調用、用戶確認請求或文本生成。所有步驟完成(或用戶中止)後,最終訊息被持久化回資料庫。
上下文管理
對話可能跨越數百條訊息和工具調用,因此上下文窗口管理至關重要。Linda 使用 75,000 token 的上下文窗口,在所有模型間共享。當預估 token 超過窗口的 75% 時,壓縮流程啟動——將較舊的訊息總結為簡潔的摘要,同時保留最近的 6 條訊息原文。冗長的工具結果也會被壓縮以回收空間。這允許對話無限期運行而不會達到上下文限制。
工具系統與權限
Linda 內建 20+ 工具,涵蓋多個類別:

| 類別 | 工具 |
|---|---|
| 通訊 | send_email、search_emails、send_notification |
| 任務管理 | create_task、update_task |
| 文件 | create_document、update_document、search_documents |
| 創意 | create_slides、update_slides、create_drawing、generate_image |
| 資訊 | get_current_time、get_location、search_history |
| 互動 | ask_question、request_upload、read_uploaded_file |
| 簡報 | create_briefing |
權限感知執行
並非所有工具都應自動執行。代表用戶發送郵件需要明確批准,但搜尋文件不需要。Linda 使用三級權限模型:工具可以是自動確認(立即執行,如 search_documents)、手動確認(智能體暫停並發送推送通知供用戶審查和批准)或自動拒絕(從智能體工具集中完全移除)。
權限可按受派人和任務進行配置,甚至可以有條件地自動確認——例如,僅當收件人在公司網域內時自動批准 send_email。
透過 MCP 實現可擴展性
除了內建工具,Linda 還支援 Model Context Protocol (MCP) 伺服器作為擴展。用戶可以連接外部服務——日曆、發票系統、家居自動化——智能體動態發現其工具。
為避免預先載入所有擴展工具(這會膨脹上下文),我們使用三個元工具實現延遲載入。智能體首先調用 search_tools 使用嵌入對所有可用擴展工具進行語義搜尋,然後調用 read_tool 獲取所需工具的完整 schema,最後調用 use_tool 在 MCP 伺服器上調用它。這樣智能體只在實際需要時載入工具詳情,即使連接了數十個擴展,基礎提示詞也保持精簡。
觸發器:讓智能體主動化
在傳統助手中,只有用戶訊息觸發智能體循環。Linda 支援四種觸發類型。
最直接的是用戶訊息——標準路徑,訊息作為 AgentTask 發佈到 RabbitMQ。但更有趣的是自主觸發器。
定時任務由 Celery 調度器管理,使用 RedBeat 實現持久化。當定時任務觸發時,它回調後端的 /api/tasks/{id}/execute 端點,將任務發佈到 Worker 佇列。這驅動每日簡報、定期數據檢查和定時報告。
時區處理在註冊時完成——cron 表達式從用戶本地時區轉換為 UTC,因此 Celery 調度器始終在 UTC 下運行。
Webhook 事件允許外部系統直接觸發智能體。當 Webhook 到達時,後端在任務的會話中創建新訊息並將任務發佈到佇列。類似地,郵件事件讓 Linda 監控收件匣,在相關郵件到達時觸發智能體運行,實現自主分類、總結或回覆。
iOS 應用:原生 SwiftUI 體驗
iOS 客戶端使用純 SwiftUI 建構,採用模組化套件架構。

核心是 AssistantCore,一個包含 API 客戶端、SSE 客戶端和數據模型的共享 Swift 套件。視圖按功能組織——聊天、任務、文件、幻燈片、簡報、擴展、設定等。
iOS 上的 SSE
SSEClient 實現為 Swift actor 以確保執行緒安全。它使用 URLSession.bytes 串流傳輸 SSE 事件,具有自動重試和指數退避(最長 30 秒,最多 10 次重試)。事件以 AsyncThrowingStream<SSEEvent, Error> 形式傳遞,自然契合 Swift 的結構化並行模型。
Kubernetes 部署
所有服務使用 Kustomize 部署到 Kubernetes:

每個服務可以獨立擴展。Worker Pod 是無狀態的——它們從共享的 RabbitMQ 佇列拉取任務,因此增加更多 Worker 可線性提升吞吐量。Celery 調度器作為單實例運行,RedBeat 確保持久化。
記憶:讓助手學會記住
Linda 使用 Mem0 實現長期記憶——一個向量儲存支援的記憶服務,允許智能體跨對話記住用戶偏好、過去的決策和上下文。
Mem0 服務作為獨立微服務運行,提供 REST API,由 Upstash Vector 支援語義搜尋。當智能體學到值得記住的內容(用戶偏好、專案上下文)時,它將其儲存為記憶。在未來的對話中,透過語義相似度檢索相關記憶並注入智能體的上下文。
這與對話歷史不同——記憶是提煉後的事實和偏好,跨會話和任務持久化。
幻燈片生成
Linda 更具創意的功能之一是 AI 驅動的幻燈片生成。當用戶要求製作簡報時,專門的子智能體使用受 NotebookLM 風格啟發的詳細設計提示為每張幻燈片生成 KonvaJS 場景 JSON。這些場景由叢集中運行的 Headless Chrome Pod 渲染為 PNG,然後連同縮圖上傳到 S3。整個幻燈片組還可以匯出為 PDF。五種不同的佈局——封面頁、章節頁、內容頁、圖表頁和圖文頁——使簡報視覺上豐富多樣且專業。
我們學到了什麼
建構一個主動式 AI 助手讓我們認識到解耦就是一切。將智能體循環從 HTTP 層分離是最重要的決策——它使主動觸發器、獨立擴展和協議靈活性成為可能。沒有它,定時任務、Webhook 或郵件觸發器都不切實際。
權限必須是第一優先級。 當智能體能代表用戶發送郵件和創建任務時,權限系統需要從第一天起就健壯且細粒度,而不是後來才補上。
在客戶端,可靠性需要重放機制。網路中斷在流動裝置上是常態,帶序列號的數據塊配合 Redis 快取讓我們的串流層具備了韌性,而無需增加 WebSocket 的複雜性。
我們還學到延遲載入工具至關重要。有了 MCP 擴展,可用工具可以增長到數百個。將它們全部載入到提示詞中會耗盡上下文窗口;對工具描述的語義搜尋使事情保持可控。
最後,記憶不是歷史。對話歷史是所說內容的原始記錄。長期記憶是提煉後的知識——用戶偏好、過去的決策、習得的上下文。兩者對於一個真正有用的助手都是必需的,且必須以不同方式儲存和檢索。
想查看項目概覽?請訪問 Linda Assistant 項目頁面。