持久執行讓 AI 代理在程序失效、逾時、部署或長時間等待後繼續多步驟工作,而不會重複已完成的副作用。這需要持久保存進度、可安全重複的寫入,以及處理結果不明呼叫的復原規則。
單純的重試迴圈不是持久執行。回應遺失後重試寄出電郵或付款,可能正好造成系統原本想復原的問題。
以外部副作用為中心建立工作流程模型
假設一項工作流程會建立發票、寄出發票,再把工作標記為完成。如果寄送成功,但 worker 在記錄成功前失效,下一次執行便不能安全地假定成功或失敗。
sequenceDiagram
participant W as Workflow
participant L as Operation ledger
participant E as Email provider
W->>L: Reserve send_invoice:task_442
W->>E: Send with operation key
E-->>W: Response lost
Note over W: Worker restarts
W->>L: Read operation status
W->>E: Reconcile by provider ID or key
E-->>W: Already sent
W->>L: Mark completed
W->>W: Continue from checkpoint
工作流程紀錄說明系統打算做甚麼,供應商或權威後端則說明實際發生了甚麼。復原流程會先核對兩者,再決定是否重複呼叫。
將工作流程與活動分開
工作流程協調步驟、等待、分支和重試;活動則執行外部副作用,例如呼叫 API 或寫入檔案。使用以重播為基礎的引擎時,工作流程決策應保持確定性,網絡呼叫、時鐘、隨機值和供應商 SDK 則隔離在活動內。
Temporal 的持久執行模型記錄事件歷史,並透過確定性重播在失敗後恢復工作流程狀態。你可以採用該引擎,也可以使用操作數據表和佇列實作較小的狀態機。兩者要求相同:持久保存的歷史必須決定從哪裏繼續。
讓每項可重試寫入具備冪等性
一個邏輯副作用只指定一個 key,並在用戶端、worker 和供應商重試時重用。儲存第一個最終結果,並拒絕以不同參數重用同一個 key。
Stripe 的冪等請求合約會保存某個 key 第一次請求的狀態碼和回應內容,包括失敗。其保留行為是 Stripe 特有的,但設計原則可以通用:重複請求是否屬於同一項操作,應由伺服器判斷,而不是模型。
如果供應商不支援冪等性,就在配接器周圍加入操作帳本。這能減少自身重試造成的重複,但無法證明逾時的供應商呼叫確實失敗。應加入結果核對查詢;若影響重大的結果仍無法判斷,則要求用戶審查。
對已驗證事實建立檢查點
實用的檢查點會記錄已完成的操作 ID 和下一個合法狀態轉換。在驗證副作用後寫入,並在可行時令狀態轉換與自身的數據庫寫入不可分割。
不要只對模型訊息建立檢查點。對話紀錄中的「電郵已寄出」可能只是模型聲稱的結果,而不是供應商回應。應儲存供應商訊息 ID 或操作紀錄參照。
持久狀態指引會區分工作進度、記憶和業務數據。
決定每一層的重試責任
模型 SDK、HTTP 用戶端、佇列、工作流程引擎、工具伺服器和供應商都可能重試。這些重試配置互相疊加後,原本三次嘗試可能變成數十次呼叫。
每項操作都應記載:
- 哪些失敗可以重試;
- 由哪一層執行重試;
- 嘗試次數上限與最長經過時間;
- backoff 和供應商速率限制訊號;
- 每次嘗試重用的 idempotency key;
- 用盡嘗試次數後的最終狀態。
驗證與授權失敗應停止;速率限制可以等待;寫入發生網絡逾時時,應先核對結果再重試。模型不應從錯誤字串臨時推測這些規則。
將等待視為持久狀態
用戶批准、排定時間、webhook 和長時間執行的部署都是工作流程狀態,而不是持續休眠的程序。保存等待條件與喚醒訊號,讓重複訊號不會造成問題,並在延後的批准恢復執行時,重新驗證其代表的用戶仍有權限。
把取消公開為狀態轉換。取消應停止未來步驟,但不能抹去已完成的副作用。補償操作,例如把發票作廢,應與取消分開定義。
測試結果不明的時間窗
成功路徑測試會漏掉最困難的狀態:伺服器可能已完成請求,但用戶端沒有收到回應。應在外部調用後、完成紀錄前立即注入失敗,改由另一個 worker 重新啟動,再驗證結果只會核對一次。
也要在批准期間、backoff 期間和取消後中斷。原始嘗試與恢復執行應共用工作流程和操作 ID,並記錄在同一條追蹤中。失敗復原指引會把這些情況轉成完整操作手冊。
下方提示會在不接觸生產環境的情況下設計第一次中斷測試。如果預期結果是「代理會自行處理」,這個工作流程就仍未具備復原合約。