長任務 Agent 的狀態持久化:checkpoint、冪等性與斷點續跑
前言
跑一次三分鐘的 Agent,程序掛掉就整趟重跑,頂多浪費幾塊錢。但當 Agent 要跑三小時、要等人工審核、要跨程序重啟時,「整趟重跑」就不再是選項:工具呼叫已經產生的副作用——寄出的信、扣的款、寫進資料庫的資料——沒有倒帶鍵。
這篇文章談長任務 Agent 的狀態持久化:checkpoint 怎麼做、冪等性怎麼保證,以及如何設計斷點續跑。
一、兩種失敗,兩種解法
先分清楚兩件事:
兩者缺一不可:只有 checkpoint 沒有冪等性,續跑的那一步會把郵件寄兩次;只有冪等性沒有 checkpoint,每次 crash 都要把幾十個步驟從頭重跑一遍。
二、三種持久化架構(如圖一)
**1. 框架級 checkpoint(LangGraph)**
LangGraph 的設計受 Google Pregel 啟發:每個 superstep 結束後,把完整狀態序列化,寫進 checkpointer。開發用 SQLite,生產換成 Postgres 或 DynamoDB。每次呼叫帶上穩定的 `thread_id`,crash 之後用同一個 `thread_id` 就能從最後一個 checkpoint 續跑。AWS 官方部落格示範了用 DynamoDBSaver 讓多個 worker 共享同一份狀態,這是 in-memory 儲存做不到的:in-memory 死了就沒了,多 worker 各自為政。
**2. 事件日誌重演(append-only log)**
把每一次模型回應、工具呼叫、工具結果,依序 append 到一條 durable event log。重啟時重播這條日誌,就能重建出完整狀態——推理迴路本身保持無狀態,所有狀態都從日誌長出來。Anthropic 在介紹 Managed Agents 時用的 Brain/Hands/Session 隱喻,Session 層指的就是這一條持久化的事件日誌:只要能從 session id 喚醒,harness 怎麼掛都救得回來。
**3. 持久化工作流引擎(Temporal)**
把 workflow 函數交給引擎託管,工具呼叫(activity)的執行結果由引擎記錄。一個 worker 死了,另一個 worker 接手繼續。有開發者實測:一個 workflow 被強制 kill 八次仍然正確完成——但要看清楚細節:完成的是 workflow(exactly-once 效果),而裡面的 activity 被執行了四次(at-least-once)。這就引出下一節。
三、冪等性:重跑那一步的安全帶(如圖二)
持久化引擎保證的是 **at-least-once**(至少執行一次),不是 exactly-once。經典的雙重扣款就是這樣發生的:activity 呼叫扣款 API 成功了,但在回報完成之前 worker 死了,引擎看不到結果於是重試,客戶被扣兩次。
正規解法只有一句話:**at-least-once 執行 + 冪等接收 = exactly-once 效果**。實作上有四條鐵律:
四、實作要點:LangGraph checkpoint 範例
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string("agent.db") # 生產換 Postgres
graph = builder.compile(checkpointer=memory)
# 關鍵:thread_id 穩定、可由任務推導,而非每次隨機
config = {"configurable": {"thread_id": f"{user_id}:{task_id}"}}
graph.invoke({"messages": [...]}, config=config)
# 程序 crash 後,用同一個 config 再次 invoke,
# 會從最後一個 checkpoint 續跑,而非從頭開始
搭配冪等 key 的工具包裝:
import hashlib
def idem_key(run_id: str, step_id: str) -> str:
# 確定性推導:同一次 run 的同一歩,重試時 key 不變
return hashlib.sha256(f"{run_id}:{step_id}".encode()).hexdigest()
def charge(key: str, amount: int):
if (r := receipts.get(key)) is not None:
return r # 已執行過:回傳第一次的結果,不再重做
r = stripe.Charge.create(amount=amount, idempotency_key=key)
receipts.put(key, r)
return r
五、上線前的持久化 checklist
結論
長任務 Agent 的可靠性,來自兩條簡單的紀律:**每步結束留痕**(checkpoint),**每步重跑無害**(冪等)。做到這兩點,crash 就只是「換個 worker 繼續」,而不是「整趟重來」。下次你的 Agent 要跑超過十分鐘,先問自己:如果現在拔掉電源,它回得來嗎?
參考來源
沒有留言:
張貼留言