事件驅動 Agent 實作課:別等使用者開口,讓事件自己叫醒 Agent
今日主題:事件驅動 Agent:Webhook、佇列與可靠性四現實
2026-10-31 | 資料來源:OpenAI Webhooks、Webhook 可靠性與 Claude Code 排程相關資料
前言
大多數 Agent 教學都從對話開始:使用者輸入一句話,Agent 回一句話,迴圈結束。但真正有價值的工作,往往不是這樣開始的。凌晨兩點進來一張新訂單、付款失敗、倉庫庫存跌破水位、對手上架新價格、部署完成、工單被指派——這些事都不會先敲門問你有沒有空,它們發生了,就需要有人處理。事件驅動 Agent,就是把 Agent 從「等人問」改成「聽事件」:事件一到,Agent 就被叫醒,做完該做的事,再把結果用事件送回去。
這篇文章講清楚四層架構與四個可靠性現實。如圖一,事件從來源出發,經過接收端與佇列,才交給 Agent;如圖二,失敗不是例外,而是設計的一部分,要有重試、死信與對帳。先把這兩張圖想清楚,再動手接 Webhook,否則你得到的不是自動化,而是一台半夜重複退款的機器。
一、兩種啟動方式:對話式與事件式
對話式 Agent 的啟動條件是人,事件式 Agent 的啟動條件是世界。這個差別會改變整個系統的形狀。對話式系統可以容忍使用者等幾秒,失敗了使用者會再問一次;事件式系統沒有人在旁邊按重新整理,錯過就是錯過,重複就是重複,失敗必須自己發現、自己重試、自己通報。
判斷該不該用事件驅動,有三個訊號。第一,工作有明確的觸發條件,例如訂單成立、檔案上傳、排程到期、監控告警。第二,工作不需要即時互動,幾分鐘內完成就可以,例如夜間彙總、分類、對帳、通知草稿。第三,工作量不均勻,平時安靜、偶爾爆量,例如促銷期間的訂單潮。符合這三點,事件驅動通常比定時輪詢划算:輪詢是不停敲門問有沒有事,事件驅動是事情發生時才被叫醒,省下空轉的成本,也縮短反應時間。
但事件驅動不是萬靈丹。需要即時來回討論、需要人在迴路中做判斷、觸發條件模糊到無法寫成規則的工作,還是留在對話裡比較好。實務上常見的混合做法是:事件叫醒 Agent 做第一輪處理,遇到不確定就留下提案等人確認,而不是硬把判斷也自動化。
二、四層架構:接收端、佇列、路由、工作者
如圖一,一個堪用的事件驅動 Agent 可以拆成四層。第一層是接收端:對外暴露一個 Webhook 端點,職責只有三件事,驗證簽章、把原始事件存下來、立刻回覆成功。不要在接收端跑模型、查資料庫、發通知,任何慢活都會讓來源端以為遞送失敗,進而重送,製造重複。
第二層是佇列:事件存下來之後就進佇列,由工作者慢慢消化。佇列是緩衝,也是分界。爆量時佇列先堆著,系統不會被沖垮;後端掛掉時事件還在佇列裡,等恢復後繼續處理,不必請來源端再寄一次。第三層是路由:依事件類型決定交給哪一個 Agent 或哪一組工具,例如付款失敗走催收草稿流程,新訂單走出貨檢查流程。路由只是分派邏輯,不是模型推理,不要讓模型決定事件要去哪裡,規則寫死反而穩定。
第四層是工作者:真正跑 Agent 迴圈的地方,讀取事件、呼叫工具、產出結果、把副作用寫回系統。工作者可以水平擴充,佇列越深就多開幾個,閒置時再縮回去。這四層分開之後,每一層都可以獨立除錯:事件沒進來是接收端的問題,進來沒處理是佇列的問題,處理錯對象是路由的問題,處理結果錯才是 Agent 的問題。
三、接收端三鐵律:驗簽、先存、快回
接收端看起來最簡單,出事率卻最高,原因在於它直接面對公開網路。第一條鐵律是驗證簽章:每個正規的 Webhook 來源都會用共享金鑰對原始請求內容做簽章,接收端必須用原始內容驗算,而且要用等時比較,避免時序攻擊。還沒有驗證之前,內容一律視為不可信,絕對不要因為欄位看起來合理就先執行。簽章驗證失敗就直接拒絕,不進佇列、不重試,因為重送一個偽造的請求不會變成真的。
第二條鐵律是先存再回:把原始事件連同標頭一起完整存下來,之後才做任何處理。原始事件是唯一的真相來源,日後除錯、重播、對帳都要靠它。只存解析後的欄位等於丟掉證據,萬一解析邏輯改版,舊事件就再也還原不了。第三條鐵律是快回:驗證與存檔完成就立刻回覆二字頭的成功狀態,不要等 Agent 跑完。GitHub 這類來源的逾時只有十秒左右,OpenAI 的背景任務完成通知同樣期待接收端迅速回應;回得慢,來源端就會重送,而重送正是重複副作用的起點。
四、可靠性的四個現實:重複、亂序、失敗、遺失
事件系統有四個無法迴避的現實。第一,遞送至少一次,不是恰好一次。網路不可靠,唯一能保證的是來源端會重送到收到成功回覆為止,所以同一事件出現兩次是正常現象,不是異常。解法是冪等處理:用事件編號做唯一鍵,寫入時以資料庫唯一約束擋下重複,副作用與冪等標記要在同一筆交易裡完成,否則標記寫了、副作用沒寫,或反過來,都會留下不一致。
第二,事件可能亂序。付款完成可能比付款建立晚到,也可能先到;只看單一事件就下結論,很容易把舊狀態蓋掉新狀態。解法是不要假設順序:用版本號或時間戳判斷新舊,過期的事件直接丟棄,或者每次處理前先向來源系統查詢最新狀態再決定。第三,處理一定會失敗:模型逾時、後端服務掛掉、配額用完。解法是指數退避加抖動的重試,重試幾次後仍失敗就送進死信佇列,由人工檢視或定時重播,而不是無限重試拖垮系統。
第四,事件可能遺失:來源端漏寄、接收端部署空窗、佇列被清空。解法是定期對帳:每天或每小時向來源系統拉一次清單,比對本地已處理的事件編號,缺了就補拉。這四個現實合起來,就是圖二的迴路:正常路徑往前走,異常路徑有地方去,而且每一條異常路徑最終都有人或程式接手。
五、雙向事件:Agent 完成後也要發事件
事件驅動不只是「外面叫醒 Agent」,還包括「Agent 完成後通知外面」。OpenAI 的 Webhook 機制就是一個清楚的例子:背景模式的回應完成、批次任務完成或失敗、微調任務結束、評測執行結束,都會以事件形式推到你註冊的端點,事件類型例如 response.completed、batch.completed、fine_tuning.job.succeeded。更進一步,Agent 會話本身也有事件:會話建立、需要人工動作、閒置、失敗,都可以觸發後續流程。
這個雙向設計解決了長任務的根本問題:Agent 跑十分鐘甚至幾小時,呼叫端不可能一直握著連線等。正確的做法是提交任務後立刻釋放連線,工作完成時由事件把結果推回來,接收端再依第四節的規則驗簽、存檔、入佇列、處理。同樣的道理也適用於你自己的系統:Agent 做完一件事,應該發出一個結果事件,讓下一個流程接手,而不是由 Agent 直接一路呼叫到底。事件是流程之間的合約,合約清楚,流程才能各自演進。
六、觸發器選型:五種來源,對照著挑
觸發器有五種常見來源,各有適合的場景。Webhook 適合外部系統主動通知,例如金流、電商平台、程式碼代管平台,優點是即時,缺點是你要扛公開端點與簽章驗證。排程適合固定節奏的工作,例如晨報、夜間彙總、每週稽核,優點是簡單可預期,缺點是不知道事件發生時機,只能等下一個週期。佇列消費適合內部系統解耦,例如訂單服務把事件丟進佇列,Agent 服務慢慢消化,優點是削峰填谷,缺點是要自己維運佇列。
資料庫或檔案變更適合已有系統改不動的場景,例如用變更資料擷取或檔案監看觸發處理,優點是不用改來源系統,缺點是語意較弱,常常需要額外查詢才能拼出完整事件。模型或平台回呼適合長任務收尾,例如 OpenAI 的背景回應與批次完成事件,優點是與模型生命週期綁定,缺點是事件種類由平台決定,不能自訂。選型時先問兩個問題:事件是誰先知道,以及晚幾分鐘處理會不會出事;前者決定來源,後者決定要不要即時觸發。
七、最小可跑骨架:接收、冪等、處理分開
下面是一段 Python 風格的最小骨架,重點不在框架,而在結構:接收端只做驗證與入佇列,工作者先查冪等標記再做事,副作用與標記綁在同一筆交易裡。
import hashlib
import hmac
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
# 一律對原始內容驗算,不用解析後的資料驗算
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
def handle_webhook(raw_body: bytes, signature: str, event_id: str):
# 接收端三件事:驗簽、存原始事件、立刻回成功
if not verify_signature(raw_body, signature, SECRET):
raise ValueError("簽章驗證失敗,直接拒絕")
store_raw_event(event_id, raw_body) # 寫入後才回覆
enqueue(event_id) # 其餘工作交給工作者
def run_worker(event_id: str):
# 工作者第一件事:查冪等標記,處理過就直接結束
if already_processed(event_id):
return
event = load_raw_event(event_id)
result = run_agent(event) # 模型推理與工具呼叫只在這裡發生
commit_with_idempotency(event_id, result) # 副作用與標記同一筆交易
骨架跑通之後,真正決定成敗的是四個實作要點。第一,簽章金鑰要放進祕密管理工具,輪換時新舊金鑰並行一段時間,避免一換就全斷。第二,冪等標記要有保存期限,事件編號表不能無限長下去,依業務保留三十到九十天通常足夠。第三,工作者要有併發上限與逾時,避免一次爆量把後端打垮,也避免單一事件卡住整個佇列。第四,所有事件都要能以編號追蹤:從接收、入佇列、處理、重試到死信,同一編號貫穿全部日誌,否則出事時連從哪裡查起都不知道。
八、上線檢查:先回答五個問題再開通
開通事件驅動 Agent 之前,先回答五個問題。第一,偽造的事件能不能進來:簽章驗證有沒有測試過錯誤金鑰、過期時間戳、被竄改的內容。第二,同一事件來兩次會不會做兩次:有沒有用真實重送做過測試,而不只是看程式碼覺得應該沒問題。第三,後端掛掉時事件去哪裡:重試幾次、間隔多久、死信佇列誰看、重播按鈕在哪裡。第四,事件亂序會不會寫錯狀態:有沒有版本判斷或處理前查詢最新狀態。第五,事件遺失怎麼發現:對帳任務有沒有排程、缺口告警的閾值是多少。
這五題都答得出來,事件驅動 Agent 才算真正上線。否則它只是在順境中看起來自動,一遇到重送、亂序或後端故障,就會把自動化變成自動出錯。
結論
事件驅動 Agent 的核心不是模型多聰明,而是系統多可靠。架構是四層:接收端驗簽存檔快回,佇列緩衝削峰,路由決定去向,工作者執行 Agent 迴圈;可靠性是四個現實:遞送至少一次所以要冪等,事件可能亂序所以要判斷新舊,處理會失敗所以要重試與死信,事件可能遺失所以要對帳。雙向事件再把長任務收尾接回來,讓 Agent 不必被人握著連線等。先把接收端與冪等做對,再談 Agent 要多自主——順序顛倒,自動化只會更快地把錯誤做兩次。
資料來源
- OpenAI 官方文件:Webhooks 指南(背景回應、批次與評測事件,簽章驗證與遞送指引):https://developers.openai.com/api/docs/guides/webhooks
- OpenAI API 參考:建立 Webhook 端點(可訂閱的事件類型一覽):https://developers.openai.com/api/reference/resources/webhooks/methods/create
- 非同步 AI 架構:處理 Agent Webhook 回呼與 OpenAI 非同步 Webhook(提交後釋放連線、完成時由回呼推回):https://instawebhook.com/blog/async-ai-architecture-handling-ai-agent-webhook-callbacks-openai-async-webhooks
- Webhook 驅動的 AI Agent:事件觸發自動化實作模式(驗證簽章、立刻回覆、背景執行):https://masterprompting.net/blog/webhook-driven-ai-agents-event-triggered-automation
- Webhook 可靠性:不遺失事件的遞送與接收設計(至少一次遞送、冪等接收、死信處理):https://dev.to/weston_carnes_d580b505e0c/webhook-reliability-delivering-and-receiving-events-without-losing-them-16j5
- 大規模 Webhook 系統:冪等、可重播、可觀測的設計(原始事件保存、順序處理、簽章陷阱):https://dev.to/antfarm-tech/webhooks-at-scale-designing-an-idempotent-replay-safe-and-observable-webhook-system-7lk
- Claude Code 排程任務解析:雲端常式與本機排程的觸發方式與錯過執行行為:https://agentsroom.dev/blog/claude-code-routines-cloud-vs-local-scheduled-tasks