與模型重試的死循環(huán):工程化治理方案)
1. 為什么一個(gè)工具超時(shí)了重試的問(wèn)題能把人問(wèn)急眼那天面試官問(wèn)我的時(shí)候我第一反應(yīng)其實(shí)是松了一口氣——因?yàn)榍皫纵喍荚诹?Agent 的規(guī)劃能力和 ReAct 范式這些都是我平時(shí)寫(xiě)代碼踩坑踩出來(lái)的領(lǐng)域。結(jié)果問(wèn)題落到工具一直超時(shí)模型不停重試怎么辦時(shí)我居然一時(shí)不知道從哪說(shuō)起。原因很簡(jiǎn)單市面上絕大多數(shù) Agent 教程講的是怎么把任務(wù)拆解成工具調(diào)用怎么設(shè)計(jì) prompt 讓模型思考。但真正把這套東西跑到生產(chǎn)環(huán)境、遇到線上真實(shí)流量的人都知道Agent 最難的從來(lái)不是推理而是執(zhí)行。推理錯(cuò)了最多答非所問(wèn)執(zhí)行掛了整個(gè)任務(wù)就廢在中間態(tài)里用戶盯著轉(zhuǎn)圈日志刷出一片 TimeoutError你要應(yīng)對(duì)的重試邏輯比規(guī)劃邏輯復(fù)雜一個(gè)數(shù)量級(jí)。面試官想要的我想了很久才意識(shí)到并不是一個(gè)怎么調(diào) timeout 參數(shù)的答案。他真正想知道的是三件事你知不知道 Agent 執(zhí)行一個(gè)任務(wù)的完整生命周期是什么每一環(huán)拆開(kāi)有哪些狀態(tài)你遇到工具超時(shí)的時(shí)候是在模型層無(wú)腦重試還是在架構(gòu)層把重試變成一個(gè)有邊界、有策略、有退避的閉環(huán)你設(shè)計(jì)的 Agent 系統(tǒng)在極端情況工具全部不可用下是優(yōu)雅降級(jí)還是直接陷入死循環(huán)燒錢(qián)。這篇我就把這套完整的東西寫(xiě)出來(lái)。從 Agent 的執(zhí)行循環(huán)講起再把超時(shí)和重試這個(gè)我最熟悉的坑拆開(kāi)揉碎最后給一個(gè)我在生產(chǎn)環(huán)境里驗(yàn)證過(guò)的完整治理方案。2. 先建立共識(shí)Agent 的一次任務(wù)到底經(jīng)歷了哪些環(huán)節(jié)面試聊到這種話題最怕的就是兩個(gè)人各說(shuō)各話。你講的是失敗重試他問(wèn)的是執(zhí)行循環(huán)其實(shí)是一個(gè)東西的兩個(gè)切面。所以第一步得把 Agent 從收到自然語(yǔ)言任務(wù)到產(chǎn)出最終結(jié)果這個(gè)過(guò)程用工程視角拆開(kāi)來(lái)。2.1 一個(gè)標(biāo)準(zhǔn)執(zhí)行循環(huán)的狀態(tài)流轉(zhuǎn)我把 Agent 的一輪完整執(zhí)行理解為下面這個(gè)閉環(huán)任務(wù)接收 → 意圖解析 → 規(guī)劃拆解 → 工具選擇 → 工具調(diào)用 → 結(jié)果回填 → 上下文重組 → 收斂判斷 → 輸出完成這看起來(lái)很像教科書(shū)對(duì)吧但工程上真正需要盯的是每個(gè)節(jié)點(diǎn)之間的邊界條件。比如規(guī)劃拆解出來(lái)的子任務(wù)是不是真的能映射到某個(gè)已注冊(cè)工具工具返回的結(jié)果是結(jié)構(gòu)化 JSON 還是自由文本回填之后模型上下文有沒(méi)有爆掉收斂判斷是誰(shuí)來(lái)做、依據(jù)什么信號(hào)我拿一個(gè)具體場(chǎng)景舉例你讓 Agent 查一下某電商平臺(tái)最近三天某商品的評(píng)論情感傾向。這個(gè)任務(wù)在理想情況下的執(zhí)行路徑是這樣的模型把查評(píng)論和分析情感拆成兩個(gè)子任務(wù)第一個(gè)子任務(wù)調(diào)用fetch_comments(product_id, days3)工具返回的是原始評(píng)論列表可能是 500 條文本模型把結(jié)果摘要化然后調(diào)用sentiment_analyze(texts)工具返回情感分布模型判斷已經(jīng)有最終答案了停止循環(huán)輸出結(jié)果。但真實(shí)環(huán)境里第 2 步就可能出問(wèn)題商品 ID 是用戶口語(yǔ)化的名稱模型沒(méi)有正確映射或者評(píng)論接口需要分頁(yè)拉取Agent 一次只拿了一頁(yè)或者平臺(tái)方接口限流返回 429。每一步出問(wèn)題都可能導(dǎo)致 Agent 回到規(guī)劃環(huán)節(jié)重新走一圈。2.2 執(zhí)行循環(huán)的兩個(gè)關(guān)鍵狀態(tài)步進(jìn)與終止設(shè)計(jì)執(zhí)行循環(huán)的工程第一課是必須給循環(huán)加上硬邊界。這里有兩個(gè)基礎(chǔ)概念步進(jìn)StepAgent 完成一次模型推理 一次工具調(diào)用 結(jié)果觀察的過(guò)程算一個(gè) step。一個(gè)復(fù)雜任務(wù)通常在 5~15 個(gè) step 之間。超過(guò) 20 步的任務(wù)基本是規(guī)劃爛了應(yīng)該從規(guī)劃層解決而不是讓 Agent 硬跑。終止Termination什么時(shí)候停止循環(huán)正常情況是模型判斷已有足夠信息生成最終答案。但工程上必須同時(shí)具備三種強(qiáng)制終止手段最大步數(shù)限制如 max_steps15防止模型無(wú)限推理Token 預(yù)算限制防止模型一直在把工具結(jié)果重復(fù)寫(xiě)進(jìn)上下文導(dǎo)致成本爆炸外部中斷用戶取消、任務(wù)超時(shí)、資源回收這個(gè)后面重點(diǎn)展開(kāi)。我在生產(chǎn)項(xiàng)目里見(jiàn)過(guò)最多的翻車現(xiàn)場(chǎng)就是只寫(xiě)了正常終止條件沒(méi)加最大步數(shù)限制。一個(gè)本來(lái) 5 步能完成的簡(jiǎn)單查詢因?yàn)楣ぞ叻祷馗袷阶兓屇P头磸?fù)誤解一口氣跑了 40 多步還在繼續(xù)用戶早就等不及關(guān)掉頁(yè)面了。2.3 工具層的系統(tǒng)邊界接口契約是超時(shí)問(wèn)題的總根源工具超時(shí)這件事得往上游看。Agent 的每個(gè)工具調(diào)用本質(zhì)上是模型生成參數(shù) → 運(yùn)行時(shí)去調(diào)任意外部系統(tǒng) → 把結(jié)果拿回來(lái)。這段鏈路里的系統(tǒng)邊界多得驚人模型服務(wù)本身LLM 推理耗時(shí)長(zhǎng)上下文場(chǎng)景可能 30 秒才返回工具所依賴的外部 API第三方接口的響應(yīng)時(shí)間方差極大工具宿主進(jìn)程的資源調(diào)度你用的是進(jìn)程內(nèi)函數(shù)還是獨(dú)立微服務(wù)網(wǎng)絡(luò)層跨區(qū)調(diào)用、代理網(wǎng)關(guān)、DNS 解析數(shù)據(jù)層數(shù)據(jù)庫(kù)查詢慢、緩存穿透。任何一個(gè)環(huán)節(jié)抖動(dòng)表現(xiàn)到 Agent 層都是同一句話工具調(diào)用超時(shí)。但如果你不做分層診斷就會(huì)在模型層瞎重試重試半天發(fā)現(xiàn)是數(shù)據(jù)庫(kù)慢查詢那就非常尷尬了。這點(diǎn)我后面會(huì)有專門(mén)的排查鏈路分析。3. 執(zhí)行循環(huán)的核心機(jī)制模型怎么決策、工具結(jié)果如何回填、上下文不能爆這一節(jié)看起來(lái)是原理實(shí)則是為后面超時(shí)治理鋪墊的——你不懂執(zhí)行循環(huán)內(nèi)部的數(shù)據(jù)流就沒(méi)法判斷重試造成的影響面到底有多大。3.1 工具選擇模型是把工具列表當(dāng)作API 菜單來(lái)讀的模型在你給的 System Prompt 里看到的工具本質(zhì)是一份 JSON Schema 或自然語(yǔ)言描述列表。執(zhí)行循環(huán)里每次推理要過(guò)一遍這個(gè)列表挑出自認(rèn)為匹配的工具。這一步的工程關(guān)鍵是工具描述的信息量。你只知道工具能干什么還遠(yuǎn)遠(yuǎn)不夠還得寫(xiě)清楚這個(gè)工具的輸入邊界例如product_id必須是數(shù)字字符串不接受中文名這個(gè)工具的輸出格式例如返回的是 JSON 數(shù)組還是分頁(yè)對(duì)象這個(gè)工具的失敗特征例如超時(shí)會(huì)拋出TimeoutException限流會(huì)返回 429這個(gè)工具適用和不適用的場(chǎng)景例如不要用 get_weather 獲取歷史天氣用 get_historical_weather。為什么強(qiáng)調(diào)這個(gè)因?yàn)槲乙?jiàn)過(guò)太多 Agent 把參數(shù)傳錯(cuò)然后反復(fù)重試的案例。根因不是你重試策略不好而是工具說(shuō)明沒(méi)寫(xiě)清這個(gè)參數(shù)不接受啥模型第一次生成錯(cuò)了參數(shù)后面每次生成同樣的錯(cuò)參數(shù)重試一萬(wàn)次也是白搭。工具契約的清晰度決定了重試有效性的上限。3.2 結(jié)果回填模型怎么看到工具返回的內(nèi)容工具調(diào)用完成后執(zhí)行器通常會(huì)把結(jié)果拼進(jìn)當(dāng)前對(duì)話上下文讓模型在下一次推理時(shí)看到。這個(gè)回填機(jī)制有兩個(gè)大坑第一個(gè)坑是原始結(jié)果過(guò)重。你拉了一個(gè)評(píng)論列表工具返回 500 條原始文本每條 200 字那就是 10 萬(wàn) token。下一次模型推理時(shí)光觀察結(jié)果就消耗了巨量 token成本直線上升。更糟的是模型在長(zhǎng)上下文里抓重點(diǎn)的能力是下降的垃圾進(jìn)、垃圾出。正確做法是在回填前對(duì)工具結(jié)果做一次輕量級(jí)摘要或者只保留關(guān)鍵統(tǒng)計(jì)量。第二個(gè)坑是回填后上下文污染。工具返回的不是模型預(yù)期的結(jié)構(gòu)模型被迫在下一次推理里硬猜怎么處理猜錯(cuò)之后又發(fā)起一次新工具調(diào)用。這樣幾輪下來(lái)上下文里積壓了一堆失敗的中間輸出模型越跑越糊涂越跑越愛(ài)重試——這就是 Agent 陷入重試泥潭的經(jīng)典路徑之一。所以我推薦在結(jié)果回填時(shí)再加一個(gè)規(guī)范化層對(duì)話上下文里只出現(xiàn)三種形態(tài)成功的結(jié)果摘要、明確的失敗標(biāo)記、觸發(fā)重試的策略提示。讓模型永遠(yuǎn)面對(duì)干凈、有結(jié)構(gòu)的信息。3.3 收斂判斷模型說(shuō)我好了不一定是真的好Agent 在什么情況下會(huì)停止調(diào)用工具直接給答案一般是模型看到了足夠的證據(jù)然后它在最后的回復(fù)里表達(dá)基于工具結(jié)果結(jié)論是……。問(wèn)題在于模型對(duì)足夠的判斷是概率性的不是確定性的。有時(shí)候它拿到了部分結(jié)果就直接總結(jié)丟掉了關(guān)鍵數(shù)據(jù)有時(shí)候明明結(jié)果不夠它還強(qiáng)行給一個(gè)看似合理的答案。這可能是因?yàn)樯舷挛睦锏墓ぞ呓Y(jié)果被摘要得太多、關(guān)鍵信息被吞了。工程上的解法是在執(zhí)行器層面加一道結(jié)果完整性校驗(yàn)。例如任務(wù)要求返回產(chǎn)品列表那么最終輸出里必須包含產(chǎn)品名售價(jià)庫(kù)存三段字段模型輸出缺了庫(kù)存執(zhí)行器就把它打回重試。這種結(jié)構(gòu)校驗(yàn) 重試和工具超時(shí) 重試是兩種完全不同的重試語(yǔ)義前者是在糾正模型的回答質(zhì)量后者是在對(duì)抗系統(tǒng)抖動(dòng)。把這兩類問(wèn)題混為一談是 Agent 工程里的大忌諱。4. 工具超時(shí)不是設(shè)個(gè) timeout 參數(shù)這么簡(jiǎn)單前面把 Agent 執(zhí)行循環(huán)的骨架搭了一遍現(xiàn)在進(jìn)入這場(chǎng)面試我真正想講的深水區(qū)。工具超時(shí)表面上是個(gè)網(wǎng)絡(luò)請(qǐng)求多久算失敗的參數(shù)配置實(shí)際上是一個(gè)分布式系統(tǒng)的問(wèn)題誰(shuí)來(lái)計(jì)時(shí)、計(jì)幾層時(shí)、超時(shí)之后往哪個(gè)狀態(tài)遷。4.1 超時(shí)分層單位超時(shí)、循環(huán)超時(shí)、總預(yù)算超時(shí)我給 Agent 系統(tǒng)設(shè)計(jì)超時(shí)控制時(shí)永遠(yuǎn)分三層第一層單次工具調(diào)用超時(shí)Unit Timeout。這是最基礎(chǔ)的。一次 HTTP 請(qǐng)求、一次數(shù)據(jù)庫(kù)查詢、一次外部函數(shù)執(zhí)行超過(guò)指定時(shí)間就放棄。常規(guī)設(shè)置是 5~15 秒但要看工具類型。第三方 API 普遍設(shè) 10 秒內(nèi)部數(shù)據(jù)庫(kù)查詢可以設(shè) 5 秒如果工具是調(diào)用另一個(gè)大模型那 30 秒都不過(guò)分。第二層單輪循環(huán)超時(shí)Round Timeout。Agent 的一輪 模型推理 工具調(diào)用 結(jié)果回填。這一輪的總時(shí)間可能遠(yuǎn)超單次工具超時(shí)因?yàn)槟P屯评肀旧砭鸵〞r(shí)間。像 Claude 或 GPT 系列單次推理在長(zhǎng)上下文下可能要 20~40 秒。如果工具的響應(yīng)也要 5 秒一輪就是 45 秒。這一層的超時(shí)上限通常按單次工具超時(shí) × 預(yù)期最大工具調(diào)用次數(shù) 推理時(shí)間的余量來(lái)設(shè)。第三層總?cè)蝿?wù)超時(shí)Task Timeout。整個(gè) Agent 任務(wù)從開(kāi)始到結(jié)束最多允許跑多久。這層是最終保險(xiǎn)防止所有局部控制失效導(dǎo)致任務(wù)永遠(yuǎn)不結(jié)束???cè)蝿?wù)超時(shí)按業(yè)務(wù)場(chǎng)景設(shè)比如用戶可接受的等待時(shí)間可能只有 2~3 分鐘那任務(wù)超時(shí)就該設(shè) 2 分鐘而不是 10 分鐘。這三層超時(shí)的關(guān)系是逐層包含的。單位超時(shí)到了這輪工具調(diào)用算失敗循環(huán)超時(shí)到了這一整輪算失敗可能觸發(fā)任務(wù)降級(jí)總預(yù)算超時(shí)到了整個(gè)任務(wù)算失敗。三層的優(yōu)先級(jí)是總預(yù)算 循環(huán) 單次。4.2 超時(shí)狀態(tài)遷移失敗后進(jìn)入哪一步比失敗本身更重要工具超時(shí)之后Agent 運(yùn)行時(shí)需要做一個(gè)決策這個(gè)失敗是決定性的還是可重試的我的經(jīng)驗(yàn)是把失敗分成三類確定性失敗Deterministic Failure參數(shù)非法、權(quán)限不足、工具不存在。這類錯(cuò)誤重試一萬(wàn)次結(jié)果都一樣不能重試直接改狀態(tài)為該工具不可用可重試失敗Retryable Failure網(wǎng)絡(luò)抖動(dòng)、超時(shí)、限流 429、服務(wù)端 5xx。這類是臨時(shí)性故障可以重試未知失敗Unknown Failure返回了無(wú)法解析的內(nèi)容、狀態(tài)碼異常。這類最危險(xiǎn)我傾向于先重試一次如果第二次還是一樣轉(zhuǎn)入確定性失敗處理。關(guān)鍵點(diǎn)來(lái)了超時(shí)幾乎都屬于可重試失敗但如果重試次數(shù)耗盡仍未成功必須升級(jí)狀態(tài)不能一直停在可重試?yán)铩:芏?Agent 實(shí)現(xiàn)失敗就敗在這里同一輪的循環(huán)里不斷重復(fù)調(diào)用同一個(gè)超時(shí)工具導(dǎo)致整個(gè)系統(tǒng)變成一臺(tái)重試機(jī)器。正確做法是維護(hù)一個(gè)工具狀態(tài)表記錄每個(gè)工具在本次會(huì)話里的失敗次數(shù)與失敗類型超過(guò)閾值就標(biāo)記為本次任務(wù)不可用。4.3 超時(shí)后的補(bǔ)償動(dòng)作切備用工具、降級(jí)策略、上下文安撫超時(shí)不只是一個(gè)timeout 報(bào)錯(cuò)然后重試的動(dòng)作。成熟的 Agent 在工具超時(shí)后應(yīng)該立刻執(zhí)行一系列補(bǔ)償邏輯查找備用工具。比如主工具的天氣查詢掛了如果系統(tǒng)里還有一個(gè)備用的天氣接口應(yīng)該讓模型去選用它而不是死磕原工具。這個(gè)我得說(shuō)清楚備用工具的切換不能只靠 prompt 提示最好是運(yùn)行時(shí)通過(guò)工具注冊(cè)表主動(dòng)給模型推薦替補(bǔ)選項(xiàng)轉(zhuǎn)換子任務(wù)。超時(shí)的子任務(wù)降級(jí)為從另一個(gè)數(shù)據(jù)源獲取近似數(shù)據(jù)。比如實(shí)時(shí)股票數(shù)據(jù)拿不到切換為延遲 15 分鐘的行情數(shù)據(jù)源重規(guī)劃。如果工具連續(xù)失敗達(dá)到閾值整個(gè)任務(wù)的規(guī)劃就不應(yīng)該繼續(xù)基于這個(gè)工具展開(kāi)。此時(shí)要把失敗信息注入到規(guī)劃器讓模型重新拆解任務(wù)繞過(guò)不可用工具。這個(gè)策略的本質(zhì)是超時(shí)是對(duì)當(dāng)前路徑的否定不是對(duì)整個(gè)任務(wù)的否定。一次超時(shí)的成本已經(jīng)付出了你要讓這次失敗產(chǎn)生最大信息量讓系統(tǒng)學(xué)會(huì)繞路而不是原地踏步。5. 模型不停重試這個(gè)坑的根因往往不在模型身上面試官問(wèn)題的后半句是模型不停重試怎么辦。這里我要先潑一盆冷水你把問(wèn)題歸因到模型想重試方向就錯(cuò)了。模型本身沒(méi)有重試的意愿是執(zhí)行器在編排層把模型看到一個(gè)失敗結(jié)果后再發(fā)起同樣調(diào)用這件事變成了循環(huán)。5.1 重試風(fēng)暴是怎么形成的我調(diào)試過(guò)幾起Agent 瘋狂重試直到任務(wù)預(yù)算耗盡的事故。復(fù)盤(pán)下來(lái)形成路徑幾乎都是同一條某工具超時(shí)了執(zhí)行器把工具調(diào)用失敗 報(bào)錯(cuò)信息回填給模型模型不理解這個(gè)錯(cuò)誤因?yàn)閳?bào)錯(cuò)文本是堆棧或內(nèi)部代碼不是面向 Agent 的說(shuō)明于是它判斷可能參數(shù)不對(duì)或再試一次也許好了模型基于錯(cuò)誤的認(rèn)知重新生成參數(shù)再次調(diào)用工具再超時(shí)陷入循環(huán)。這個(gè)循環(huán)里模型每一步看起來(lái)都在推理但信息的增量是零。失敗原因沒(méi)有變成模型可理解的語(yǔ)義錯(cuò)誤信息沒(méi)有引導(dǎo)模型換策略它只是在機(jī)械地重復(fù)同一個(gè)動(dòng)作直到達(dá)到 max_steps 或 token 預(yù)算邊界。所以模型不停重試的真正根因是執(zhí)行器給模型的反饋信號(hào)不夠可行動(dòng)actionable。5.2 反饋信號(hào)工程讓模型知道下一步該做什么我在執(zhí)行器里維護(hù)了一套失敗反饋模板嚴(yán)格按照下面的格式回填給模型工具調(diào)用失敗。工具名get_product_info 失敗類型TIMEOUT 錯(cuò)誤詳情請(qǐng)求在 10 秒內(nèi)未得到響應(yīng) 建議動(dòng)作之一改用工具 query_product_mysql備用數(shù)據(jù)源 建議動(dòng)作之二確認(rèn)參數(shù) product_id 不是過(guò)期失效的商品編號(hào) 不建議動(dòng)作不要對(duì)同一工具同一參數(shù)再次發(fā)起調(diào)用為什么建議動(dòng)作要寫(xiě)到這種粒度因?yàn)槟P驮陂L(zhǎng)上下文中對(duì)下一步做什么的判斷高度依賴提示的明確性。如果你只是給一個(gè)TimeoutError模型大概率理解不了這個(gè)錯(cuò)誤意味著什么、該換什么方式。但你給了改用備用工具 ×××它就知道該怎么做。這個(gè)模板不是萬(wàn)能的但它在我的實(shí)踐中把超時(shí)→重試死循環(huán)的發(fā)生率砍掉了至少 70%。剩下的 30% 得靠重試策略兜底。5.3 重試策略退避、上限、抖動(dòng)即使反饋信號(hào)做得好也一定會(huì)發(fā)生該工具就是臨時(shí)抽風(fēng)重試一次能成的情況。所以重試還是要做但要做成有紀(jì)律的重試。我的重試參數(shù)組合如下最大重試次數(shù)默認(rèn) 2 次最多不超過(guò) 3 次。一個(gè)工具連續(xù)超時(shí) 3 次基本可以斷定它短時(shí)間內(nèi)不可用繼續(xù)重試只是浪費(fèi)時(shí)間和 token指數(shù)退避第一次重試等待 1 秒第二次 2 秒第三次 4 秒給下游系統(tǒng)恢復(fù)留時(shí)間抖動(dòng)Jitter在退避時(shí)間上疊加 ±20% 的隨機(jī)偏移。這個(gè)主要是防止多個(gè) Agent 實(shí)例同時(shí)重試同一個(gè)下游服務(wù)造成重試風(fēng)暴把服務(wù)打崩重試預(yù)算共享整個(gè) Agent 任務(wù)的總重試次數(shù)也要設(shè)上限。例如整個(gè)任務(wù)最多允許 5 次工具重試用完了就只能走降級(jí)路徑。我在實(shí)際項(xiàng)目中會(huì)把重試策略做成一個(gè)可配置的 JSON 下發(fā)到運(yùn)行時(shí)。這樣不同優(yōu)先級(jí)的任務(wù)可以有不同的重試預(yù)算比如付費(fèi)用戶的任務(wù)允許更多重試免費(fèi)的只給一次機(jī)會(huì)。5.4 上下文里的失敗記憶防止同一錯(cuò)誤反復(fù)觸發(fā)還有一個(gè)非常細(xì)微但影響很大的點(diǎn)模型的上下文里積累的失敗歷史會(huì)影響它后續(xù)的決策。同一個(gè)工具失敗兩次兩次的報(bào)錯(cuò)都出現(xiàn)在上下文里模型可能會(huì)因?yàn)樾畔⑦^(guò)載而對(duì)后續(xù)所有工具調(diào)用都變得保守或者激進(jìn)而發(fā)生偏移。我的做法是把失敗記錄壓縮成一行摘要用新的摘要覆蓋舊的。例如第一次失敗后上下文寫(xiě)入[失敗] get_product_info 超時(shí) 1 次10秒無(wú)響應(yīng)第二次失敗后不是追加一行而是更新這一行為[失敗] get_product_info 超時(shí) 2 次10秒無(wú)響應(yīng)已標(biāo)記不可用為什么這樣做因?yàn)槟P烷喿x上下文時(shí)重復(fù)出現(xiàn)的同一類信息會(huì)干擾它對(duì)當(dāng)前狀態(tài)的判斷。壓縮失敗歷史讓上下文始終保有最近一次、最關(guān)鍵的狀態(tài)就夠了。這個(gè)技巧聽(tīng)起來(lái)小但對(duì)模型在后續(xù)輪次的規(guī)劃質(zhì)量影響極大。6. 完整排查鏈路一個(gè)線上 Agent 瘋狂重試事故的全過(guò)程復(fù)盤(pán)光講理論不夠我把一個(gè)真實(shí)事故的排查過(guò)程寫(xiě)出來(lái)這個(gè)鏈路你可以直接拿去當(dāng)排查手冊(cè)參考。6.1 事故現(xiàn)場(chǎng)任務(wù)卡在重試漩渦里出不來(lái)那是一個(gè)電商導(dǎo)購(gòu) Agent用戶問(wèn)幫我找一下這個(gè)鏈接里提到的保溫杯看看哪個(gè)平臺(tái)最便宜。當(dāng)時(shí)線上日志顯示同一個(gè)工具search_sku_by_link在 3 分鐘內(nèi)被調(diào)用了 23 次每一次結(jié)果都是超時(shí)。任務(wù)最后因?yàn)榭傤A(yù)算耗盡被強(qiáng)制終止用戶看到的反饋是抱歉當(dāng)前服務(wù)不穩(wěn)定。我接手時(shí)的第一反應(yīng)不是去看重試參數(shù)而是看這個(gè)工具本身。先確認(rèn)一件事它是不是一個(gè)需要外部依賴的工具一查這個(gè)工具內(nèi)部封裝的是第三方比價(jià)平臺(tái)的 API而那個(gè)平臺(tái)恰好有單 IP 訪問(wèn)頻率限制。前幾次超時(shí)是因?yàn)槠脚_(tái)方限流后面幾次是因?yàn)?Agent 的重試壓力進(jìn)一步觸發(fā)了更嚴(yán)格的限流——我們的重試親手把故障從偶發(fā)超時(shí)升級(jí)成了持續(xù)拒絕。6.2 逐層隔離從工具實(shí)現(xiàn)到模型行為再到重試策略這是一個(gè)三層問(wèn)題必須逐層排查第一層工具實(shí)現(xiàn)。search_sku_by_link里是什么超時(shí)邏輯發(fā)現(xiàn)它的 HTTP 客戶端用的是默認(rèn)連接超時(shí)60 秒但業(yè)務(wù)側(cè)的單次調(diào)用超時(shí)設(shè)的是 10 秒。這意味著每次調(diào)用里Agent 框架層在 10 秒時(shí)判定失敗實(shí)際上游 HTTP 請(qǐng)求還掛在后臺(tái)跑著。多次失敗請(qǐng)求疊加占滿了連接池。這里就直接定位到第一個(gè)根因框架超時(shí)和工具內(nèi)部超時(shí)不一致導(dǎo)致框架層失敗但資源層仍在消耗。第二層模型行為。模型每次收到失敗反饋后反饋文本是Error: upstream request timeout。這個(gè)文本太抽象了模型不知道上游超時(shí)意味著什么也不知道換成別的工具可以完成同樣的比價(jià)目標(biāo)。它唯一的動(dòng)作就是換個(gè)參數(shù)再試但在鏈接解析這種任務(wù)上參數(shù)本來(lái)就固定換參數(shù)毫無(wú)意義。這定位到第二個(gè)根因失敗反饋缺少備選方案指引。第三層重試策略。查配置發(fā)現(xiàn)重試次數(shù)設(shè)置成了 5 次退避間隔固定 500ms沒(méi)有抖動(dòng)。限流類錯(cuò)誤的退避需要更長(zhǎng)固定短退避會(huì)讓重試全部撞在限流窗口里。這定位到第三個(gè)根因重試策略沒(méi)有按錯(cuò)誤類型區(qū)分退避策略。6.3 修復(fù)方案三級(jí)聯(lián)動(dòng)一鍋端我把修復(fù)拆成三個(gè)動(dòng)作修正工具內(nèi)部超時(shí)search_sku_by_link的 HTTP 客戶端超時(shí)改為 8 秒比框架層 10 秒短并且為連接池設(shè)置最大并發(fā)數(shù)。這樣框架層判定失敗時(shí)不會(huì)留下掛著的僵尸請(qǐng)求改進(jìn)失敗反饋在反饋里增加備用工具選項(xiàng)比如search_sku_by_name并注明該工具不走第三方比價(jià)平臺(tái)可直接查商家?guī)熘卦嚥呗跃?xì)化把限流類錯(cuò)誤429的退避調(diào)整為 5 秒起步指數(shù)遞增把超時(shí)類錯(cuò)誤的退避調(diào)整為 1 秒起步加上 50% 抖動(dòng)兩個(gè)類型的最大重試次數(shù)都降到 2 次。超過(guò)閾值后標(biāo)記工具不可用讓模型切換到備用工具。修復(fù)上線后的效果同一場(chǎng)景下的任務(wù)成功率從 54% 提升到了 92%重試調(diào)用次數(shù)從 23 次降到了 1~3 次。后面我又把框架超時(shí) vs 工具內(nèi)部超時(shí)的一致性檢查做成了自動(dòng)化巡檢防止新工具上線時(shí)再次出現(xiàn)同樣問(wèn)題。7. 把 Agent 的失敗處理從重試死循環(huán)升級(jí)為優(yōu)雅降級(jí)面試到最后我對(duì)工具一直超時(shí)模型不停重試這個(gè)問(wèn)題的最終答案其實(shí)歸結(jié)為一句話你要讓 Agent 具備優(yōu)雅失敗的能力而不是追求永不失敗。這個(gè)理念落實(shí)到工程上就是三個(gè)原則。7.1 把失敗當(dāng)成正常業(yè)務(wù)路徑來(lái)設(shè)計(jì)在我熟悉的接口設(shè)計(jì)里一個(gè)外部服務(wù)不可用是常態(tài)而不是異常。Agent 系統(tǒng)也是一樣。我建議把工具超時(shí)、重試耗盡、切換備用、任務(wù)降級(jí)這整條鏈路的體驗(yàn)做得和順利完成任務(wù)一樣細(xì)致。具體到代碼層面就是給每條執(zhí)行路徑都準(zhǔn)備可觀測(cè)的狀態(tài)超時(shí)算一種狀態(tài)重試算一種狀態(tài)降級(jí)算一種狀態(tài)完成算一種狀態(tài)。每一種狀態(tài)都要有對(duì)應(yīng)的日志、指標(biāo)和用戶反饋。沒(méi)有狀態(tài)的 Agent 執(zhí)行鏈路本質(zhì)上是個(gè)盲盒。7.2 用超時(shí)預(yù)算替代無(wú)限重試我在新項(xiàng)目里推行了一個(gè)任務(wù)超時(shí)預(yù)算的概念目前看效果極好每個(gè) Agent 任務(wù)有一個(gè)總時(shí)間預(yù)算比如 120 秒和總重試次數(shù)預(yù)算比如 5 次工具調(diào)用消耗時(shí)間重試消耗預(yù)算預(yù)算剩余量會(huì)展示給模型模型對(duì)是否還要重試的判斷會(huì)因此變得謹(jǐn)慎而不再那么冒進(jìn)預(yù)算耗盡后執(zhí)行器主動(dòng)轉(zhuǎn)向降級(jí)方案比如返回部分結(jié)果、引導(dǎo)用戶手動(dòng)操作、轉(zhuǎn)人工客服。這個(gè)概念我自己總結(jié)一句話給 Agent 一切讓它選擇。7.3 降級(jí)方案庫(kù)每個(gè)核心工具都準(zhǔn)備 Plan B最后是實(shí)操建議。每個(gè) Agent 項(xiàng)目里凡是核心工具都必須準(zhǔn)備降級(jí)方案。降級(jí)不一定是功能完全一致的備份可以是數(shù)據(jù)精度降級(jí)、數(shù)據(jù)時(shí)效降級(jí)、用戶交互方式降級(jí)。比如你有一個(gè)實(shí)時(shí)天氣查詢工具超時(shí)后可以降級(jí)到離線天氣表昨天緩存的數(shù)據(jù)如果離線數(shù)據(jù)也沒(méi)有就降級(jí)到籠統(tǒng)建議讓用戶在界面里看溫度區(qū)間如果這也不行就告訴用戶天氣服務(wù)暫不可用請(qǐng)稍后再試。每一層降級(jí)都有一個(gè)成本與體驗(yàn)的權(quán)衡。我實(shí)際見(jiàn)過(guò)太多項(xiàng)目失敗的時(shí)候只會(huì)把原始報(bào)錯(cuò)拋給用戶那不是降級(jí)是甩鍋。降級(jí)的目標(biāo)是讓用戶在無(wú)法獲得完整服務(wù)時(shí)依然獲得一個(gè)有意義的結(jié)果。8. 最后再聊一個(gè)執(zhí)行層面的小技巧把工具的報(bào)錯(cuò)翻譯給模型聽(tīng)這個(gè)技巧非常小但對(duì)模型不停重試的治理幫助特別大單獨(dú)拿出來(lái)分享一下。工具返回的錯(cuò)誤信息通常是為開(kāi)發(fā)者設(shè)計(jì)的不是為模型設(shè)計(jì)的。比如dial tcp: lookup api.example.com: i/o timeout這個(gè)東西對(duì)模型來(lái)說(shuō)就是一團(tuán)亂碼。模型看到亂碼第一反應(yīng)就是我沒(méi)理解錯(cuò)誤我需要重試。這才是重試的起點(diǎn)。我在執(zhí)行器里加了一個(gè)錯(cuò)誤翻譯層原理很簡(jiǎn)單維護(hù)一張錯(cuò)誤碼映射表把常見(jiàn)工具報(bào)錯(cuò)翻譯成模型能理解的短句子格式是失敗類型 失敗原因 建議動(dòng)作 不建議動(dòng)作。比如原始錯(cuò)誤dial tcp: lookup api.example.com: i/o timeout 翻譯后網(wǎng)絡(luò)連接超時(shí)暫時(shí)無(wú)法訪問(wèn)外部 API。建議 3 秒后重試或改用緩存工具 query_cache。不建議連續(xù)發(fā)起超過(guò) 2 次請(qǐng)求。這套翻譯層跑起來(lái)之后模型放棄當(dāng)前路徑、轉(zhuǎn)向替代方案的概率大幅提高。甚至在一些極端場(chǎng)景模型會(huì)主動(dòng)跟用戶說(shuō)主數(shù)據(jù)源暫時(shí)不可用我從緩存里給你找了一份昨天的數(shù)據(jù)請(qǐng)你參考。這比我設(shè)計(jì)的所有重試策略都管用。因?yàn)檫@個(gè)小小的翻譯層本質(zhì)上是在幫模型降低決策過(guò)程中的不確定性。模型不是不愿意換路而是不知道怎么換路。你把路標(biāo)畫(huà)清楚了它自然就繞過(guò)去了。這次面試之后我一直在想面試官那個(gè)問(wèn)題真正值錢(qián)的不是答案本身而是它逼著我從模型推理的魔法視角切換到系統(tǒng)工程視角。一個(gè) Agent 能穩(wěn)定地完成一次任務(wù)靠的不只是聰明的模型更是執(zhí)行器對(duì)每一步失敗的處理——怎么診斷、怎么反饋、怎么重試、怎么放棄。把這套功夫練好了你手上的 Agent 項(xiàng)目就不會(huì)總在線上燒錢(qián)轉(zhuǎn)圈了。