產品 Agent 的模型層:選擇 SDK、處理重試與備援
Codex SDK、OpenAI Agents SDK 與 Claude Agent SDK 不是同一種模型介面。先確認 Agent 執行環境與狀態責任,再設計能力相容、有限重試和跨供應商備援。
本文目錄
產品 Agent 的模型層不只負責送出請求,還要選擇執行環境、處理重試與備援。一次任務可能包含多次模型呼叫;切換供應商時,不能只換 API 網址,還要確認工具契約、對話狀態與資料邊界是否仍然成立。
接了備援,請求仍可能一直等
一個 API 先呼叫供應商 A,逾時後重試 A,最後再切到 B。如果每次嘗試都重新拿到完整逾時額度,服務端可能還在等,客戶端卻早已放棄。SDK 自己有重試、請求分派層又加重試時,實際請求數也可能比你設定的上限更多。
另一個問題更難察覺:A 能讀圖片,B 只能處理文字。A 失敗後把相同任務送到 B,即使 B 回傳成功,也沒有完成原始需求。備援路徑必須保留任務契約,不能只追求「有字可以回」。
確認能力與執行環境
統一介面之前,先列出不能消失的差異
供應商介面可以統一請求識別碼、成功結果與錯誤分類,但能力、格式限制、模型可容納的內容長度與資料處理條件仍要保留。文字聊天、結構化輸出與圖片輸入是不同需求;支援其中一項,不代表其他項也相容。
先從允許使用的供應商中篩選能力,再依服務健康、等待時間與成本偏好排序。涉及資料地區或保留條件的限制,也應在這一步排除不符候選,不能讓一個低成本分數抵銷硬性條件。
應用層可以用統一的請求分派器處理選擇與備援。可重用的部分是控制流程,不是「所有模型可以互換」的假設。供應商成功回傳後,仍要驗證結果符合本次 schema 與必需欄位。
先選執行環境,再談替換模型
Codex SDK、OpenAI Agents SDK 與 Claude Agent SDK 都能讓應用程式控制 Agent,但它們管理的工作不同。產品抽象層應該先決定自己要的是「一次模型生成」還是「能接續的多步任務」,再接上對應的執行環境。
| 選項 | 適合交給它的工作 | 產品仍要管理的部分 |
|---|---|---|
| Codex SDK | 控制本機 Codex 的程式開發任務與 thread | 工作目錄、執行權限、產品工作狀態與資料存取 |
| OpenAI Agents SDK | 在自己的程式裡執行 Agent 迴圈、工具與交接 | 部署、資料儲存、工具實作與核准決策 |
| Claude Agent SDK | 在自己的程序中使用 Claude Code 的 Agent 執行環境 | 產品權限、工作隔離、session 保存與任務生命週期 |
這張表描述的是架構責任,不是品質或成本排名。三種 SDK 也不能當成相同介面的不同網址。Codex SDK、OpenAI Agents SDK、Claude Agent SDK
OpenAI Agents SDK 可在 Agent 或執行流程選定模型,非 OpenAI 模型則要核對所用語言 SDK 的 provider 或 adapter 介面。Claude Agent SDK 的 model 與 fallbackModel 能設定主模型與備用模型,但不能把它解讀成跨 OpenAI、Anthropic 的通用切換器。OpenAI:Models and providers、Claude:Configuration
需要跨供應商備援的產品,可以把路由放在應用層,但要明確決定切換發生在哪個邊界。一次尚未執行工具的模型呼叫較容易重新嘗試;一個已寫入資料、正在等待核准的 Agent 任務,就不能直接用另一個 SDK 從頭重跑。先保存已完成動作與可接續狀態,才能避免把備援變成重複操作。
模擬 429、逾時與全部不可用。重試與退避也要占用同一份時間預算,不會無限延長。
由供應商 B 回應。失敗、退避與成功合計 340 ms,沒有超過 1000 ms 總預算。
- 回應供應商
- B
- 耗時 / 預算
- 340 / 1000 ms
- 請求嘗試
- 3
- 01供應商 A停止
429,仍未取得結果;重試上限已到,改看下一個相容供應商。
- 02供應商 B完成
能力相容,取得結果。
- 03供應商 C略過
已取得結果,未呼叫。
圖中的資料只用來說明流程,不是實際服務的測試結果。 A / B / C 是虛構供應商,能力、回應時間、退避和故障均手動指定;不是實際供應商比較,也未模擬串流、計費、完整斷路器生命週期或 Retry-After 標頭。
選擇健康、速率限制、逾時或全部失效的情境,再切換聊天、結構化輸出與視覺能力。先看候選如何被排除,再調整總時間預算與 A 的額外重試上限,觀察動態路徑、等待長條與結果摘要。備援 B、C 各最多嘗試一次,能力不符就略過,全部共用同一份時間預算。
把預算縮小時,即使還有備援供應商,也可能沒有足夠時間再嘗試。提高重試上限,則可能先把時間花在同一個供應商,而不是讓下一個供應商有機會執行。圖解中的毫秒是為了讓這兩個因素容易比較,不是實務上建議的 timeout。
控制重試與備援
每一次嘗試都要消耗同一份預算
端到端截止時間從收到請求時開始計算,包含路由、連線、回應、退避與備援。可以讓每次嘗試另有較短上限,但它不能超過剩餘時間。下列示意把次數與單次上限放在供應商契約中;A 的 attemptLimit 是初次加額外重試,B、C 則各設一次:
type Failure = { kind: 'transient' | 'rate-limit' | 'invalid' };
type Provider = {
name: string;
capabilities: Set<string>;
attemptLimit: number;
attemptTimeoutMs: number;
call: (signal: AbortSignal) => Promise<string>;
};
async function route(
providers: Provider[], required: string[], budgetMs: number,
classify: (error: unknown) => Failure,
) {
const deadline = performance.now() + budgetMs;
const compatible = providers.filter(p =>
required.every(capability => p.capabilities.has(capability)),
);
for (const provider of compatible) {
for (let attempt = 0; attempt < provider.attemptLimit; attempt += 1) {
if (performance.now() >= deadline) break;
const remaining = Math.max(1, Math.floor(deadline - performance.now()));
const timeout = Math.min(provider.attemptTimeoutMs, remaining);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
return await provider.call(controller.signal);
} catch (error) {
const failure = classify(error);
if (failure.kind === 'invalid') throw error;
// 正式版本須依錯誤類型與剩餘時間,決定退避或換供應商。
} finally {
clearTimeout(timer);
}
}
}
throw new Error('NO_COMPATIBLE_RESULT_WITHIN_DEADLINE');
}
這個示意假設 adapter 會遵守取消訊號,也未實作退避與健康狀態。setTimeout 和 AbortController 不能強迫任意 Promise 停止;若底層忽略訊號,呼叫仍可能超出期限。正式 adapter 要把取消傳到網路層,也要判斷晚到結果是否仍可採用,不能在客戶端已離線後繼續累積工作。
invalid 表示請求本身不成立,例如 schema 錯誤。相同輸入重送通常不會修好這個問題。速率限制則要讀供應商提供的等待資訊,若已超出剩餘預算,就停止或使用相容備援;不是把每一種失敗都立即重送。
重試、切換與斷路保護處理不同尺度
重試處理一次請求的暫時失敗,切換把這次任務交給另一個相容候選,斷路保護(Circuit Breaker)則跨越多次請求,暫停打向持續失敗的服務。Closed 允許正常呼叫;Open 先略過;冷卻後進入 Half-open,只用受限的探測判斷是否可以恢復。
半開狀態必須限制同時探測數量,否則大量請求會在冷卻結束時一起打回故障服務。失敗也要分類:使用者資料錯誤不應算成供應商健康問題,部分速率限制可能只影響某個帳戶或模型,不能一律把整家供應商封鎖。
圖解展示單次任務在故障下的路由與時間消耗,沒有模擬跨請求斷路保護狀態。上線前要另測狀態轉移、同時發出多個探測請求與恢復門檻,避免只測「A 失敗後選 B」就宣布備援完成。
有限重試之外還要有退避與隨機抖動,讓不同請求不要同時重送。AWS 的工程說明指出,重試可能放大故障服務的負載;有副作用的操作也不能只因逾時就假設尚未執行。Timeouts, retries, and backoff with jitter
切換會改變結果,也會改變成本
同一份 prompt 在另一家供應商可能產生不同格式與內容。回應應記錄實際使用的 adapter、是否降級與驗證結果,而不是沿用第一次選到的供應商名稱。若任務要求結構化結果,驗證失敗應進入明確錯誤流程,不能默默回傳純文字代替。
失敗請求也可能已消耗 token。多供應商同時發送再取第一個結果,與依序 fallback 是不同策略,不能用相同成本模型比較。即使取消其他請求,也不代表遠端計算與計費一定停止;是否有這種保證需要查對應契約。
串流開始後更不能把 B 的答案直接接在 A 的半句後面。可以終止並顯示不完整,或以一個新的回應識別碼重新生成;選哪一種都要讓客戶端知道發生了什麼。
用故障注入驗證你真正設定的上限
用可控制的假 adapter 模擬速率限制、永不完成、能力不符與格式錯誤,記錄每次嘗試的起訖、退避、取消與結果。檢查跨供應商的總時間,不只檢查單次 timeout;也檢查 SDK 與應用是否各自重試。
全部候選失效時,應回傳可辨識的不可用狀態,不能把快取或預設文字包裝成新生成結果。備援的完成條件,是在允許的時間、資料邊界與任務能力內取得可接受結果,或者清楚結束失敗。
這份驗證要固定時鐘與假供應商行為,才知道是哪個策略改變了結果。讓 A 的等待超過單次上限,但低於總預算,確認取消後仍能嘗試相容的 B;再讓 A 的重試消耗完整預算,確認 B 沒有被呼叫。能力不符的候選,即使是健康狀態也不應產生請求。
測試還要記錄「開始生成」「開始傳送」與「已完整送出」的差別。一個正常回傳的 adapter 不代表客戶端收到結果;一個失敗回應也不代表供應商沒有處理。這些狀態若共用同一個 success,重試成本與完成率就會互相混淆。
延伸閱讀
SDK 文件核對日期:2026-10-07。模型能力與部署限制仍須依實際選用版本檢查,本文不指定固定的最新模型或價格。
本篇屬於「在產品裡打造 Agentic 功能」系列。可以先看產品架構與 Agent 迴圈。
上一篇:對話記憶怎麼選。下一篇:工具選完,還要有執行計畫。