先區分連線中斷、工作取消與執行失敗
瀏覽器停止收到 token,不代表後端工作已經停止。行動網路切換、反向代理逾時、頁面重新整理與用戶按下停止,都可能讓 HTTP 或 WebSocket 連線消失,但模型請求、RAG 查詢或工具呼叫仍在伺服器上執行。如果系統把「連線中斷」直接等同「工作失敗」,重試時便可能重複扣款、重複寫入 CRM,或在背景留下無人接收的推論工作。
我們通常將一次回覆建模為有固定 request ID 的工作,並分開記錄連線狀態與執行狀態。執行狀態至少要能表達 queued、running、cancelling、completed、failed 與 cancelled;前端斷線只會改變訂閱狀態,不應擅自改寫工作的最終結果。每個串流事件再帶上單調遞增的 sequence number,讓客戶端知道最後確認到哪一段,而不是以畫面上有多少文字來猜測進度。
取消必須沿著整條呼叫鏈傳播
取消不是關閉聊天視窗而已。前端送出取消命令後,API 層應驗證 request ID 與使用者權限,再觸發可傳播的 cancellation signal。RAG 檢索、模型供應商的串流連線、資料庫查詢與內部工具都應在合理的邊界檢查訊號。若某個第三方 API 不支援取消,工作狀態可先進入 cancelling,並丟棄後續輸出,但不能宣稱上游運算已立即停止。
取消與完成可能同時發生,因此狀態轉移必須具備原子性。伺服器要定義哪個結果勝出:常見做法是 completed 一旦持久化就不可改成 cancelled,而尚未提交最終結果的工作則接受取消。對會產生外部副作用的工具,還要區分「停止後續步驟」與「補償已完成操作」;取消郵件草稿很容易,撤回已送出的通知或已建立的 ERP 單據通常需要另一套補償流程。
續傳依賴事件紀錄,不是重新拼接字串
真正的續傳需要伺服器保留足夠的事件歷史。客戶端重新連線時提交 request ID 與 last acknowledged sequence,伺服器先重播缺少的事件,再接回即時串流。如果工作已完成,就直接傳送持久化的最終結果;如果歷史事件已過期,則回傳明確的 resync_required,要求客戶端重新取得完整快照。這比盲目重跑模型安全,也能避免同一段文字顯示兩次。
- 短暫斷線:保留工作的執行權,重新連線後依 sequence number 補送事件。
- 伺服器重啟:從共享儲存讀取工作狀態、已提交事件與工具執行紀錄,不能只依賴單機記憶體。
- 模型供應商中斷:若供應商無法從相同生成位置續傳,應標記原嘗試失敗,再建立新的 attempt,而非假裝 token 能無縫接續。
- 工具已執行:使用 idempotency key 查核既有結果,禁止因重連或重試再次產生副作用。
- 紀錄已淘汰:以完整訊息快照取代事件重播,並讓前端清楚重設本地狀態。
不要把尚未完成的半段回答直接放回新 prompt,然後要求模型「從最後一字繼續」。模型可能重複句子、改變論點,或遺失原本的工具上下文。若業務確實要求跨供應商或跨程序延續,應把已確認內容、引用來源、工具結果與待完成目標整理成結構化 checkpoint,並在介面上把它標示為新的生成嘗試。
用可驗證的狀態機處理重試與復原
前端應以伺服器狀態為準,而不是把 loading 布林值當成真相。重新載入頁面後,先查詢 request 狀態,再決定重接串流、顯示完成內容、提供重試,或提示部分操作可能已執行。重試要產生新的 attempt ID,但沿用同一個業務 idempotency key;如此既能保留每次模型呼叫的診斷資料,也能防止工具重複寫入。對長時間工作,心跳、租約與 worker ownership 也要分開設計,避免兩個 worker 同時接手。
測試時不要只模擬正常完成。應在 token 傳送途中關閉瀏覽器、在工具回傳後切斷連線、重啟 worker、延遲取消命令,並讓重連與完成同時抵達。驗收標準應包括:內容不重複、最終狀態唯一、取消可追蹤、工具副作用至多提交一次,以及事件過期時能回到一致快照。當這些規則被寫成狀態轉移與契約測試,AI 串流才會從展示功能變成可營運的企業能力。