Agent 任務 API 怎麼設計?分清完成、排隊與等待核准
模型有輸出,不代表 Agent 任務已完成。以互動資料流拆開完成、排隊、拒絕與故障,再把 SDK 的等待核准狀態接到產品 API,避免前端誤判與重複執行。
本文目錄
Agent 任務可能查詢資料、執行工具,或在修改外部資料前等待使用者核准。產品 API 必須把這些狀態交代清楚:畫面應該顯示結果、繼續等待,還是請使用者做決定?不能只因 SDK 回傳了一個物件,就讓前端顯示「已完成」。
假設前端呼叫摘要 API,後端回傳 success: true 與任務 ID。前端因此顯示「摘要完成」,但工作還躺在佇列裡。使用者重新整理看不到結果,又送一次請求。現在同一份資料正在生成兩次摘要,後端卻認為每一次回覆都沒有錯。
這裡需要修正的是契約:請求已接受、工作已完成、結果可取得,以及等待核准,是不同狀態。LLM 本身可能花多久只是其中一部分,網路重送與背景處理才是客戶端必須理解的邊界。
把任務狀態寫進公開契約
先讓回覆型別表達狀態
同步請求等待結果完成後回覆內容;非同步請求在可靠地接受任務後回覆任務 ID 與查詢位置。依 HTTP 語意,202 Accepted 表示已接受處理,但尚未完成,也不保證最終成功。回覆應提供目前狀態與查詢方式。RFC 9110,15.3.3
我會避免把所有回覆塞成一組可選欄位,改用可辨識的聯集型別。以下省略一般的追蹤資訊,留下客戶端需要判斷的部分:
type Reply<T> =
| { kind: "completed"; data: T }
| { kind: "accepted"; jobId: string; statusUrl: string }
| { kind: "pending_approval"; jobId: string; approvalId: string }
| { kind: "failed"; code: string; retryable: boolean };
function renderReply<T>(reply: Reply<T>) {
switch (reply.kind) {
case "accepted": return `等待處理:${reply.jobId}`;
case "pending_approval": return "需要核准後才能繼續";
case "completed": return "結果已完成";
case "failed": return `處理失敗:${reply.code}`;
}
}
這個型別不會替 HTTP 決定狀態碼,但能防止程式在 accepted 分支讀取不存在的結果。正式端點還要讓 HTTP 狀態、回覆內容與文件相符,客戶端才能知道何時等待、何時修正輸入、何時可以重試。
比較同步回應與非同步工作查詢。HTTP 狀態、工作狀態與內容拒絕各有自己的語意。
HTTP 200,工作狀態 completed,生成結果已可取得。
- HTTP 狀態
- 200
- 工作狀態
- completed
- 生成結果
- 可取得
- 011. 輸入驗證完成
格式檢查通過。
- 022. 安全政策完成
範例政策通過。
- 033. 工作執行完成
已取得生成結果。
- 044. 狀態與結果完成
結果與 completed 狀態已可讀取。
- 055. HTTP 回應完成
HTTP 200;依範例契約回傳。
圖中的資料只用來說明流程,不是實際服務的測試結果。 狀態碼是這份範例契約的選擇,不是所有 API 都必須採用的對應方式。非同步查詢的 200 只表示成功取得工作紀錄;仍須檢查 job.status。沒有真正建立佇列或發送請求。
這個互動圖解先聚焦同步與背景工作,狀態與回覆是教學示例。在圖解選擇成功、輸入不合法、政策拒絕或供應商故障,觀察請求停在哪一個階段。再切換同步與非同步模式;非同步情境提供排隊中與 worker 已執行的兩個觀察時點。worker 執行後可能完成,也可能失敗;輸入不合法與政策拒絕則在建立工作前被拒絕,不能當成已接受。
試著選非同步、供應商故障與 worker 已執行,會看到查詢任務的 HTTP 狀態為 200,工作本身卻是 job.status: "failed"。HTTP 成功表示成功讀取任務紀錄,沒有把失敗的工作變成成功。正式契約還需要定義執行中、取消等狀態,以及它們允許的轉換。
拒絕與故障不能只靠訊息猜
輸入格式錯誤,需要使用者修改參數;政策拒絕,需要說明服務範圍;供應商暫時故障,可能允許之後重試。三者如果全部回傳同一個內部錯誤,前端會給出錯的操作建議,監控也會把正常的政策執行算成系統故障。
如果依拒絕訊息中的文字選擇 HTTP 狀態碼,改文案、翻譯或模型改寫就可能改變 API 行為。比較可維護的做法是由服務層回傳固定錯誤種類,例如 INVALID_INPUT、POLICY_REFUSAL、PROVIDER_UNAVAILABLE,API 層再依公開契約轉成對應狀態碼。
retryable 也需要有具體意義。驗證失敗不應重送相同輸入;暫時故障只能在截止時間與重試次數限制內嘗試。若前一次工作可能已執行,重試前要處理重複效果。供應商回覆的原始錯誤或內部堆疊留在受控紀錄中,不應為了方便偵錯直接送給所有客戶端。
把 SDK 執行狀態轉成產品狀態
OpenAI Agents SDK(@openai/agents)的一次執行可能正常完成,也可能為了核准工具呼叫而暫停。暫停時,finalOutput 可以仍為空,interruptions 列出待決定的操作,state 則保留可繼續執行的狀態。產品可把這種結果轉成 pending_approval,保存狀態與任務的關聯,核准或拒絕後再接續同一次執行。OpenAI:Results and state
這是 SDK 的執行狀態,並不是 HTTP 202。若核准要稍後進行,應用程式還得保存任務、驗證決策者權限,並處理狀態到期或動作內容已變更的情境。不能把前端重送同一句話當成「核准」,也不能在使用者按下核准時另開一個沒有原狀態的新任務。OpenAI:Guardrails and human review
Claude Agent SDK(@anthropic-ai/claude-agent-sdk)提供 Claude Code 的 Agent 執行環境。若設定 outputFormat: { type: "json_schema", schema },應從 result 訊息的 structured_output 取資料,同時確認 subtype === "success" 且欄位存在,再驗證產品自己的規則。不能因收到一個 result,就把格式重試失敗或缺少資料的情境轉成完成。Claude:Agent SDK overview、Structured outputs
若產品功能是檢查專案或修改程式碼,Codex SDK(@openai/codex-sdk)控制的則是本機 Codex thread,可透過 thread.run() 執行並接續工作。它與泛用的 OpenAI Agents SDK 是不同介面;不論接哪一種執行環境,產品的任務 ID、HTTP 回覆、查詢權限與防止重複執行的規則仍要由應用程式設計。OpenAI:Codex SDK
可靠交接與防止重複執行
接受背景任務,要先有可靠的交接
回覆 202 前,任務至少需要被可靠保存,之後才能查詢。只在 HTTP 處理函式中啟動一個未等待的 Promise,程序若重啟,任務可能就消失。API 回得快,卻無法證明背景工作已交給 worker。
一種設計是把任務資料與待送出的工作事件寫入同一筆資料庫交易,之後由發送程序把事件交給佇列。這樣可以檢查尚未發送的事件,補做交接。即使佇列重送,worker 也要能辨識已完成的工作。可靠保存與交接需要一起設計,否則 API 的「已接受」就沒有後續執行依據。
狀態查詢要重新驗證權限。知道某個 jobId,不代表可以讀取它的結果。任務表應記錄擁有者或租戶、輸入版本、建立時間、更新時間與可保存到何時。若結果已被清除,查詢回覆也要讓客戶端知道,不能永遠停在「處理中」。
冪等鍵不能只是「先查再做」
如果先查 requestId 是否有結果,沒有才執行,結束後保存,仍然無法避免同時重送。兩個同時到達的請求可能都查不到,接著同時呼叫模型或工具。這是競爭條件,即使後來只留下一份結果,重複費用或外部寫入已經發生。
需要的是原子地取得「這個請求由誰負責執行」的權利。概念上的儲存介面可以是:
type Reservation =
| { kind: "owner"; jobId: string }
| { kind: "existing"; jobId: string }
| { kind: "conflict" };
interface IdempotencyStore {
reserveOrRead(input: {
scope: string; key: string; bodyHash: string;
}): Promise<Reservation>;
}
reserveOrRead 必須由資料庫唯一限制與交易,或等價的原子機制實作;函式名稱本身不會提供保障。scope 應涵蓋租戶與操作,bodyHash 綁定正規化後的請求內容。同一個鍵卻帶不同參數,應明確拒絕衝突,不能把舊結果當成新請求的回覆。
也要處理取得執行權後的中斷。若 worker 可能接手逾期工作,需要租約與工作版本,避免舊 worker 回來又寫入結果。外部副作用更需要下游的冪等支援或可查詢狀態;只在自己的資料庫中避免重複處理,無法保證第三方只執行一次。
驗證契約,要包含會讓客戶端誤判的情境
測試應從前端看得到的回覆開始:同步成功時取得結果;非同步接受時只取得任務資訊;輸入失敗不啟動模型;政策拒絕保留固定分類;供應商故障不冒充完成。等待核准則另外檢查三件事:等待中不顯示完成、拒絕後不執行原工具,以及重複核准不重複寫入。驗證 JSON 結構也要驗證 HTTP 狀態與查詢權限,TypeScript 型別不能檢查網路上收到的任意資料。
冪等測試則同時送出多個相同鍵的請求,檢查只有一個執行者;再用相同鍵、不同 body,確認被判為衝突。插入程序中斷、交接失敗、佇列重送與結果保存失敗,確認任務能恢復,或進入明確的失敗狀態。不要只測連續送兩次的順利情境。
版本相容性也包括狀態與錯誤碼。新增欄位通常較容易維持相容,但新增狀態可能使列舉判斷失效,改變 retryable 會改變客戶端行為。保存一組舊版客戶端的請求與回覆測試,才能檢查改動是否仍符合承諾。
API 的完成條件應能由客戶端判斷。只要「接受」與「完成」仍被混用,後端成功回覆再多次,使用者也不會知道工作到底做了沒有。
延伸閱讀
- RFC 9110:202 Accepted:已接受處理與已完成工作的區別。
- OpenAI Agents SDK:Results and state:最終輸出、等待核准與可接續狀態。
- OpenAI Agents SDK:Guardrails and human review:驗證與核准的執行邊界。
- Claude Agent SDK:Structured outputs:結構化結果與格式失敗情境。
- Codex SDK:本機程式開發工作的 thread 介面。
SDK 文件參考日期:2026-10-07。
系列導覽
上一篇:多步驟任務的效能。下一篇:Agent 的觀測與故障定位。也可以從產品架構開始。