超越基礎:實現 LLM Token 流的可恢復性與多裝置同步
對於許多開發者來說,從同步聊天機器人轉向非同步 AI agent(在使用者工作時於背景運行的工具),看起來似乎只是簡單的傳輸方式改變。常見的建議是使用 Server-Sent Events (SSE) 並搭配 Last-Event-ID 標頭來建立持久的串流。理論上這聽起來很簡單,但在實作中,大規模部署會引入顯著的架構摩擦。
當從簡單的 Demo 邁向生產級的 agentic 體驗時,三個核心需求隨之出現:可恢復的串流、可靠的取消機制,以及多裝置同步。雖然 SSE 技術上可以處理這些問題,但實作細節揭示了「可行」與「容易」之間的差距。
Token Streaming 的開銷
在深入探討架構挑戰之前,了解 LLM 回應的本質至關重要。無論你使用的是 Vercel AI SDK、OpenAI 還是 Anthropic,回傳的數據不僅僅是文本串流。每個「token」都包裹著大量的元數據 (metadata)。
例如,來自 Anthropic API 的單個事件可能包含 125 個字元的 JSON 元數據,僅為了傳遞 5 個字元的實際文本增量 (delta)。一旦你決定透過將串流儲存在資料庫中來實現持久化,這種元數據開銷就會成為關鍵的瓶頸。
可恢復串流的挑戰
為了讓串流具備可恢復性,用戶端必須能夠在連線中斷後重新連線,並請求缺失的 token。SSE 規範提供了 Last-Event-ID 標頭來實現此目的。如果每個事件都有唯一的 ID(例如 response_id:token_index),用戶端就可以告訴伺服器它上次中斷的确切位置。
然而,在具有無狀態伺服器副本的現代水平擴展架構中,這會引入巨大的寫入放大 (write amplification) 問題。因為任何一個副本都可能處理重新連線的請求,所以每一個 token 都必須即時寫入共享資料庫或快取中。
這造成了一個矛盾的局面:你為了每幾個字元的文本都在進行資料庫寫入,而這些請求通常根本不會發生中斷。一旦 LLM 完成回應,這些個別的 token 記錄就變得毫無用處,必須被清理以換取最終的「完整回應」記錄。結果是,為了僅造福一小部分用戶的功能,付出了高昂的基礎設施負擔。
在持久化世界中處理取消機制
在基礎的 SSE 設定中,連線中斷通常向伺服器發出取消 LLM 請求的訊號。但一旦你實作了可恢復串流,連線中斷不再代表使用者想要停止;它可能只是代表他們進入了隧道或重新整理了瀏覽器。
現在,取消操作需要一個專用的帶外 (out-of-band) 機制。你必須實作一個獨立的端點(例如 POST /cancel/{response_id}),將「取消標記」寫入共享資料庫。處理 LLM 推論的伺服器副本必須在生成 token 的間隙不斷輪詢 (poll) 這個共享存儲,以檢查是否應該中止上游調用。這增加了推論迴圈的延遲與複雜性。
多裝置同步的差距
支援多個裝置會引入兩個截然不同的問題:
- 狀態恢復: 由於 token 已經為了可恢復性而儲存在資料庫中,第二個裝置可以獲取歷史紀錄並接續目前的串流。
- 即時通知: 裝置 B 如何知道裝置 A 已經發送了提示詞 (prompt) 且回應正在串流中?
如果沒有持久的雙向連線,裝置 B 必須依賴輪詢。如原始資料所述,輪詢是一個兩敗俱傷的權衡:輪詢頻率低,你會承受高延遲;輪詢頻率高,你會用不必要的流量衝垮伺服器。
替代架構:Pub/Sub
鑑於這些摩擦,有些人認為 HTTP 從根本上來說並非非同步 agent 應用程式的最佳傳輸方式。發布/訂閱 (Pub/Sub) 模式透過將連線生命週期與 agent 生命週期解耦,提供了一個更優雅的解決方案。
在 pub/sub 模型中:
- 持久性: 頻道 (channel) 獨立於用戶端存在。Token 被發布到頻道中,並保持可用,以便用戶端在重新連線時可以「倒回」並收集。
- 多裝置: 多個用戶端可以訂閱同一個頻道,並即時接收相同的串流,無需輪詢。
- 效率: 傳輸層可以處理 token deltas 的壓縮以形成完整回應,減少用戶端追趕進度時需要處理的訊息數量。
- 路由: 取消和中斷可以作為訊息發布到頻道,伺服器進程會立即消耗這些訊息,從而消除輪詢資料庫以尋找取消標記的需求。
社群觀點
雖然 SSE 的挑戰很大,但一些開發者認為「難度」只是實作層面的問題。例如,一位貢獻者建議,以 Redis Streams 為後盾的 SSE 實作可以解決許多狀態與持久性挑戰,而無需完全放棄 SSE 協定。其他人則指向了新興工具,如 Durable Streams 或專門設計用於自動處理 token 事件和 socket 關閉的 API。
最終,選擇取決於應用程式的規模。對於簡單的機器人,SSE 就足夠了。但對於需要高可靠性和低延遲的複雜多裝置 agent,將狀態管理從資料庫移至傳輸層是一個強大的架構轉型。