:用 FallbackModel 構(gòu)建跨模型故障轉(zhuǎn)移的可靠 Agent)
ADK Python 模型容錯實戰(zhàn)用 FallbackModel 構(gòu)建跨模型故障轉(zhuǎn)移的可靠 Agent【免費下載鏈接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.項目地址: https://gitcode.com/GitHub_Trending/ad/adk-pythonFallbackModel是 ADKAgent Development KitPython 提供的一個BaseLlm包裝器它持有一組有序的模型列表當(dāng)主模型調(diào)用失敗如限流 429、服務(wù)不可用 503時自動把同一請求轉(zhuǎn)交給下一個模型讓 LLM 提供商的故障不再傳導(dǎo)為整個 Agent 的中斷。本文以官方指南 docs/guides/models/fallback_model/index.md 為核心結(jié)合 源碼實現(xiàn) 與 單元測試完整講解其配置方式、故障轉(zhuǎn)移判定規(guī)則、請求回滾機(jī)制、流式與 Live 連接的邊界行為以及已知限制幫助你在生產(chǎn)環(huán)境把提供商壞了變成換個模型繼續(xù)跑。為什么需要 FallbackModel把提供商的壞消息擋在調(diào)用鏈之外LLM 提供商限流rate-limit和宕機(jī)是常態(tài)。沒有恢復(fù)路徑時一個 429 或 503 會從模型調(diào)用處拋出穿透整個 Flow直接終結(jié)一次 invocation——提供商的糟糕一分鐘就成了 Agent 的一次中斷。FallbackModel給失敗一個去處它持有多個模型按順序依次嘗試返回第一個成功的響應(yīng)。關(guān)鍵設(shè)計在于它自身就是一個BaseLlm見 base_llm.py因此 ADK 其余部分無需任何感知LlmAgent.model直接接受它Flow 照常通過generate_content_async調(diào)用它真正服務(wù)請求的那個委托模型delegate會以其名字出現(xiàn)在請求和 trace 上。從設(shè)計哲學(xué)看它是刻意窄化的它不做單模型重試也不按成本或任務(wù)路由——模型失敗就直接放棄換下一個。相鄰問題重試、路由由模型層已有的機(jī)制解決下文 與其他容錯層次的分工 一節(jié)會說明它們?nèi)绾胃魉酒渎殹?焖偕鲜謨煞N配置方式方式一直接給模型名字列表把想要的模型和后備模型放進(jìn)modelsfrom google.adk.agents import LlmAgent from google.adk.models import FallbackModel agent LlmAgent( namereliable_agent, modelFallbackModel(models[gemini-3.1-pro-preview, gemini-3.5-flash]), instructionYou are a helpful assistant., )如果gemini-3.1-pro-preview返回 429同一個請求會被轉(zhuǎn)給gemini-3.5-flashAgent 看到的是一個正常響應(yīng)。方式二混入模型實例給后備模型獨立配置條目也可以是模型實例這正是給后備模型配獨立參數(shù)的途徑from google.adk.models import FallbackModel from google.adk.models.google_llm import Gemini from google.genai import types FallbackModel( models[ gemini-3.1-pro-preview, Gemini( modelgemini-3.5-flash, retry_optionstypes.HttpRetryOptions(attempts3), ), ], )在這個例子里后備模型Gemini自帶retry_options意味著輪到它時genai SDK 會在 HTTP 層先按自己的重試策略嘗試再決定是否把錯誤交回給FallbackModel。工作原理從解析到回滾的完整鏈路延遲解析與緩存models的每個條目都會被解析為一個BaseLlm實例原樣使用字符串通過LLMRegistry.new_llm解析一次并緩存見 registry.py。解析被推遲到首次使用時與LlmAgent.model的做法一致因此構(gòu)造FallbackModel永遠(yuǎn)不會觸發(fā)提供商包的導(dǎo)入——給一個Claude后備并不會拉入anthropic包除非真的走到那個后備。代價是拼錯的后備模型名要到第一次真正需要后備時才會報錯而不是在定義 Agent 時。這一點在源碼_delegate方法src/google/adk/models/_fallback_model.py#L325-L331中清晰可見測試 test_names_are_not_resolved_at_construction 也專門驗證了構(gòu)造時不解析、未知名字延遲暴露的行為。模型名重定向委托者是誰請求就指向誰委托模型的名稱會在調(diào)用前寫入LlmRequest.model源碼見 src/google/adk/models/_fallback_model.py#L386因為模型是從請求上讀取名字而不是從自身讀取。沒有這一步后備模型會被塞進(jìn)主模型的名字從而調(diào)用錯誤的模型。測試 test_request_model_points_at_the_delegate 驗證了主模型失敗后請求最終保留的是實際服務(wù)者的名字backup。請求就地編輯與失敗回滾模型在發(fā)送前會就地修改請求追加用戶輪次、預(yù)處理工具Live 連接還會把語音配置、系統(tǒng)指令、工具和 HTTP 選項寫上去。因此一個模型失敗后必須先把它的改動回滾再嘗試下一個模型否則后備模型會繼承調(diào)用者從未要求的設(shè)置。更隱蔽的問題是模型只在自己擁有某些配置時才寫它們?nèi)艉髠淠P蜎]有自己的語音配置它就會用主模型的聲音說話。回滾對兩種路徑普通輪次和 Live 連接都生效。其快照實現(xiàn)是_RequestSnapshotsrc/google/adk/models/_fallback_model.py#L87-L130只捕獲四類內(nèi)容contents內(nèi)容列表configGenerateContentConfig生成配置live_connect_configLive 連接配置請求自身的簿記bookkeeping即_SNAPSHOT_PRIVATE指定的私有屬性之所以不能整份深拷貝請求是因為tools_dict持有 live 工具對象MCP 工具內(nèi)部還握有一個無法復(fù)制的threading.Lock。工具是模型只讀、從不編輯的注冊表因此被排除在快照之外。測試 test_falls_back_with_a_tool_that_cannot_be_copied 專門用帶鎖的工具驗證了這一點回滾不會嘗試復(fù)制它們委托看到的仍是同一個實例。成功模型保留自己的改動——這正是 trace 上展示的內(nèi)容測試 test_the_model_that_succeeds_keeps_its_edits 驗證。測試文件中還通過test_a_failed_model_does_not_leak_its_edits_to_the_next與test_every_private_attribute_is_accounted_for等用例把哪些字段被恢復(fù)、哪些被刻意跳過固化為回歸約束防止LlmRequest后續(xù)新增字段悄悄泄漏到下一次嘗試。什么樣的失敗才會觸發(fā)切換只有攜帶retriable_status_codes之一的狀態(tài)碼才會切換到下一個模型。狀態(tài)碼從提供商拋出的各種錯誤形態(tài)中提取_status_code實現(xiàn)見 src/google/adk/models/_fallback_model.py#L179-L203錯誤來源狀態(tài)碼讀取位置google.genai的APIErrorcode字段google-genai 把所有 4xx 折疊為ClientError、5xx 折疊為ServerError狀態(tài)碼在code上litellm / OpenAI / Anthropic 錯誤status_code字段httpx錯誤如ApigeeLlm拋出response.status_code沒有狀態(tài)碼的錯誤——連接重置、回調(diào)里的 bug——從未到達(dá)服務(wù)端不足以構(gòu)成換一家模型嘗試的理由因此原樣向外傳播測試 test_error_without_status_propagates。被刻意排除的帶狀態(tài)錯誤有些錯誤形態(tài)雖帶狀態(tài)碼卻被有意排除litellm 的誤報 500APIConnectionError和APIResponseValidationError都被 litellm 硬編碼為 status 500但前者從未到達(dá)服務(wù)后者說明響應(yīng)已到達(dá)卻在客戶端檢查失敗。兩者都被識別并當(dāng)作無狀態(tài)處理實現(xiàn)見 src/google/adk/models/_fallback_model.py#L141-L176。若按面值對待會導(dǎo)致把服務(wù)可能已計費并執(zhí)行過的提示詞重發(fā)一遍。測試 test_litellm_misreported_500_does_not_fall_back 驗證了這一點。408 不在默認(rèn)集合中與 ADK 的重試列表不同默認(rèn)集合刻意不含 408因為超時并不能說明請求是否已被處理。切換到另一個模型是比重試同一個模型更重的承諾代價可能是同一提示詞被付費執(zhí)行兩次。litellm 甚至把客戶端超時也報成 408。若某個提供商的 408 確定表示請求被丟棄可自行加回見下文配置。配置選項選項類型默認(rèn)值說明modelslist[str \| BaseLlm]必填按順序嘗試的模型列表第一項是主模型。retriable_status_codesfrozenset[int]{429, 500, 502, 503, 504}觸發(fā)切換到下一個模型的狀態(tài)碼集合。models 的約束models至少需要一個條目空列表在構(gòu)造時即被拒絕Pydantic 的min_length1見 src/google/adk/models/_fallback_model.py#L276測試 test_empty_models_is_rejected 驗證。第一項是主模型capabilities報告的是主模型的能力model屬性由主模型派生而來。model繼承自BaseLlm不能直接設(shè)置——直接傳model會被 Pydantic 校驗拒絕測試 test_setting_model_directly_is_rejected源碼_derive_model_name_from_primarysrc/google/adk/models/_fallback_model.py#L311-L323會拋出說明性錯誤因為直接給的名字只會被報告、不會真正選擇任何模型。要配置的就是models列表。收窄或放寬 retriable_status_codes默認(rèn)集合是ADK 重試的狀態(tài)碼集合減去 408——ADK 自身的重試集合見 evaluation/_retry_options_utils.py#L27-L34包含 408、429、500、502、503、504。可以收窄為只對限流做故障轉(zhuǎn)移或為某個用別的方式表達(dá)過載的提供商放寬from google.adk.models import FallbackModel FallbackModel( models[gemini-3.1-pro-preview, gemini-3.5-flash], retriable_status_codesFallbackModel.DEFAULT_STATUS_CODES | {529}, )DEFAULT_STATUS_CODES是公開的類屬性源碼定義見 src/google/adk/models/_fallback_model.py#L257-L263可直接取用并擴(kuò)展。測試 test_default_status_codes_is_reachable_from_the_class 與 test_default_status_codes_membership 固定了這一集合的內(nèi)容測試 test_a_timeout_does_not_fall_back_by_default 驗證 408 默認(rèn)不觸發(fā)切換。所有模型都失敗之后在 ADK 既有錯誤處理處兜底當(dāng)每個模型都失敗時最后一個提供商拋出的錯誤會原樣向外傳播源碼 src/google/adk/models/_fallback_model.py#L412-L416 的注釋說明這是為了讓LlmAgent.on_model_error_callback能接手。若想用一條回復(fù)而不是終結(jié)本次 invocation就在 ADK 本來就處理模型錯誤的地方處理它——LlmAgent.on_model_error_callback或等價的插件鉤子from google.adk.agents import LlmAgent from google.adk.agents.callback_context import CallbackContext from google.adk.models import FallbackModel from google.adk.models.llm_request import LlmRequest from google.adk.models.llm_response import LlmResponse from google.genai import types def on_model_error( callback_context: CallbackContext, llm_request: LlmRequest, error: Exception, ) - LlmResponse: return LlmResponse( contenttypes.Content( rolemodel, parts[types.Part(textEvery model is unavailable; try again.)], ) ) agent LlmAgent( namereliable_agent, modelFallbackModel(models[gemini-3.1-pro-preview, gemini-3.5-flash]), on_model_error_callbackon_model_error, )這個鉤子并非本類專屬因此它同樣覆蓋 FallbackModel 吸收不了的失敗不可重試的狀態(tài)碼以及輪次已經(jīng)開始流式輸出后的失敗。與其他容錯層次的分工FallbackModel刻意只做跨模型故障轉(zhuǎn)移相鄰問題由其他層次負(fù)責(zé)三層互不干擾1. LiteLLM 提供商的 fallback。如果所有想用的模型都能通過 LiteLLM 觸達(dá)LiteLlm本身就有此能力LiteLlm(model..., fallbacks[...])該列表被透傳給 litellm由提供商內(nèi)部完成失敗轉(zhuǎn)移。FallbackModel是跨模型類的方案——Gemini主模型配Claude后備或任何BaseLlm子類——且兩者可組合一個配置了fallbacks的LiteLlm實例可以作為這里的條目之一。倉庫樣例 contributing/samples/models/litellm_with_fallback_models/agent.py 展示了LiteLlm(modelgemini/gemini-2.5-pro, fallbacks[anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o])的用法并配合before_model_callback/after_model_callback觀察模型選擇的變化——注意該樣例用的是 LiteLLM 自帶 fallback而非FallbackModel類本身。2. 單模型重試。重試是獨立一層留在模型自己身上Gemini和ApigeeLlm接受retry_options由 genai SDK 在 HTTP 層應(yīng)用。FallbackModel對每個模型恰好嘗試一次這樣單個 429 不會被兩個互不知曉的層次重復(fù)重試。3. 服務(wù)端路由。第三層通過模型名觸達(dá)model-optimizer-*條目在 Vertex AI 上做服務(wù)端路由它本身可以作為這里的第一個條目后面再跟一個客戶端后備FallbackModel(models[model-optimizer-exp-04-09, gemini-3.5-flash])流式輸出什么時點之后不再切換流式對故障轉(zhuǎn)移施加了約束。一旦模型產(chǎn)出了該輪次的第一個響應(yīng)這一輪就歸它所有之后的失敗直接傳播而不是切換調(diào)用者已經(jīng)持有前面發(fā)出的 chunk啟動第二個模型會把兩個模型的輸出拼接進(jìn)同一輪次源碼注釋見 src/google/adk/models/_fallback_model.py#L404-L408。非流式調(diào)用只產(chǎn)出一次因此幾乎總是在那個時點之前失敗可以自由切換。測試 test_streaming_failure_after_first_chunk_does_not_fall_back 用先產(chǎn)出兩個 chunk 再拋 429的場景驗證了后備模型不會被叫來收尾。此外包裝器通過Aclosing傳遞提前放棄的語義若調(diào)用者提前停止消費回調(diào)拋異常、客戶端斷開委托模型的流——及其下的提供商連接——會被立即關(guān)閉而不是留給事件循環(huán)的終結(jié)器。測試 test_abandoning_the_stream_closes_the_delegate 驗證了這一點。Live 連接同一條規(guī)則在連接邊界上connect按順序嘗試每個模型產(chǎn)出第一個成功建立連接的那個連接尚未打開時沒有任何數(shù)據(jù)穿越連接把嘗試交給另一個模型不會損失任何東西因此可以故障轉(zhuǎn)移。失敗的嘗試在嘗試下一個模型前被回滾與輪次相同。連接已打開后會話歸該模型所有——后備模型無法恢復(fù)一個已在進(jìn)行的雙向會話——之后的失敗原樣到達(dá)調(diào)用者。收尾保證無論正常退出還是async with體拋異常已打開的連接都會被關(guān)閉AsyncExitStack實現(xiàn)見 src/google/adk/models/_fallback_model.py#L463。測試 test_connect_closes_the_connection_when_the_body_raises 驗證了異常路徑上的清理。Live 的就地編輯回滾同樣嚴(yán)格Gemini.connect會寫入speech_config、system_instruction、tools、thinking_config、safety_settings和http_options其中一些只在模型持有它們時才寫所以沒有回滾的話沒有自己語音的后備模型就會用主模型的聲音說話。測試 test_a_failed_connect_does_not_leak_its_edits_to_the_next 用_VoiceLlm驗證了失敗連接不泄漏語音配置。限制與邊界行為實驗性狀態(tài)。FallbackModel默認(rèn)開啟特性注冊見 src/google/adk/features/_feature_registry.py#L157-L159FALLBACK_MODEL為 EXPERIMENTAL 且 default_onTrue首次構(gòu)造時警告一次但 API 仍可能變化。設(shè)置環(huán)境變量ADK_DISABLE_FALLBACK_MODEL可關(guān)閉它此時構(gòu)造會直接拋錯。不可達(dá)不等于可切換。提供商不可達(dá)而非帶錯誤應(yīng)答不會觸發(fā)故障轉(zhuǎn)移——見上文什么樣的失敗才會觸發(fā)切換。后備模型只能救服務(wù)在線但拒絕干活的情況。Live 會話斷線回到所屬模型不故障轉(zhuǎn)移。會話恢復(fù)句柄只對簽發(fā)它的模型有意義若該模型持續(xù)宕機(jī)重連會持續(xù)失敗而不是悄悄啟動另一個模型的會話。所有者是針對 live flow 為本次運行構(gòu)建的請求記錄的因此跟隨的是會話而非模型名——兩個條目可以同名同一模型背后的兩個 key 或區(qū)域仍能被區(qū)分。相關(guān)測試包括 test_reconnect_follows_the_session_not_the_name、test_reconnect_works_for_two_entries_with_the_same_name。跨運行恢復(fù)的局限。通過RunConfig.session_resumption把句柄帶進(jìn)新的run 時該句柄屬于這個模型從未見過的請求唯一可依據(jù)的是 flow 寫上的名字——即 Agent 自己的名字。這樣的 run 被釘在主模型上首次連接沒有故障轉(zhuǎn)移如果會話實際由后備模型持有句柄會被交給從未簽發(fā)它的模型。兩個報告相同模型名的條目在這種情況下無法區(qū)分重連會拋錯而不是猜測_candidate_indexes的 ValueError 邏輯見 src/google/adk/models/_fallback_model.py#L567-L593。包裝會隱藏具體類型。live flow 只為 Vertex AI 上的Gemini設(shè)置session_resumption.transparent而FallbackModel不是Gemini因此該默認(rèn)值不會應(yīng)用。capabilities 始終來自主模型。即使后備模型服務(wù)了請求請求也是在任何調(diào)用之前構(gòu)建的即為主模型構(gòu)建的。因此請讓各條目在能力上盡量接近使同一請求能適配所有條目源碼 src/google/adk/models/_fallback_model.py#L340-L349。相關(guān)樣例官方指南指出目前尚無FallbackModel專屬樣例最接近的現(xiàn)有樣例是 contributing/samples/models/litellm_with_fallback_models/agent.py它使用的是 LiteLLM 自帶的提供商級 fallback 而非本類。若要親自驗證FallbackModel的行為可參照單元測試 tests/unittests/models/test_fallback_model.py——其中_FakeLlm、_rate_limited等測試樁完整覆蓋了主模型成功不動后備、429 切換、多模型逐級切換、非可重試狀態(tài)碼傳播、流式半途失敗不切換等關(guān)鍵路徑是理解本類語義最直接的活教材。【免費下載鏈接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.項目地址: https://gitcode.com/GitHub_Trending/ad/adk-python創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考