失控:recursionLimit與條件路由的治理實(shí)踐)
1. 工具循環(huán)失控的現(xiàn)場(chǎng)從一個(gè)“讀不完的文檔” Bug 說起寫過 AI Agent 的朋友應(yīng)該都有過這種經(jīng)歷你給智能體配了三五個(gè)工具本來只是讓它查個(gè)資料、整理個(gè)表格結(jié)果它像魔怔了一樣反反復(fù)復(fù)把同一個(gè)工具調(diào)用十幾遍。日志里密密麻麻全是 tool call每一條都是同樣的參數(shù)、同樣的輸入唯一區(qū)別就是時(shí)間戳不同。電話費(fèi)、Token 費(fèi)嘩嘩流走最后模型吐出來一句“抱歉我沒找到答案”。我最初遇到這個(gè)問題是在做一個(gè)內(nèi)部知識(shí)庫問答助手。當(dāng)時(shí)給 Agent 配了一個(gè)search_documents工具讓它幫同事找公司制度文檔。某天有人提問“請(qǐng)總結(jié)一下出差報(bào)銷的所有規(guī)定”Agent 的第一步跑得很正常調(diào)用搜索工具拿到了二十多條標(biāo)題其中五條看起來跟報(bào)銷相關(guān)。問題出在第二步它想逐一打開這五條查看內(nèi)容就引入了一個(gè)read_document工具。然后我再看日志就傻眼了——read_document被連續(xù)調(diào)用了三十多次傳入的文檔 ID 永遠(yuǎn)只有那五個(gè)而且每次都能成功返回正文但 Agent 每一次都說“需要繼續(xù)確認(rèn)細(xì)節(jié)”。這就是典型的 AI 工具循環(huán)失控。用戶看到的是無限轉(zhuǎn)圈開發(fā)者看到的是 Token 快速蒸發(fā)底層原因是 Agent 在自己的推理循環(huán)里失去了“退出意識(shí)”。這個(gè)循環(huán)機(jī)制本身不是壞事。業(yè)界管它叫AI 工具循環(huán)agentic loop模型先分析任務(wù)選擇一個(gè)工具拿到返回結(jié)果再分析結(jié)果再選下一個(gè)工具直到它認(rèn)為任務(wù)完成。這是所有 Agent 的骨架沒有這個(gè)循環(huán)Agent 就只是“一次性的問答機(jī)器”。但它一旦失控就成了無底洞??刂七@個(gè)循環(huán)有幾個(gè)關(guān)鍵抓手其中最基礎(chǔ)也最容易被低估的就是標(biāo)題里那個(gè)recursionLimit。很多人對(duì)這個(gè)概念的理解停留在“把數(shù)字調(diào)大一點(diǎn)就好了”實(shí)際上它和條件路由是搭配使用的。recursionLimit 負(fù)責(zé)兜底條件路由負(fù)責(zé)止損兩者一個(gè)管“最多跑多遠(yuǎn)”一個(gè)管“這段路怎么選”配合起來才能真正把循環(huán)管住。這篇文章我按自己的項(xiàng)目經(jīng)驗(yàn)來拆先說說工具循環(huán)到底是怎么失控的再從原理層面講清 recursionLimit 到底限制了什么然后用一個(gè)條件路由的設(shè)計(jì)實(shí)例說明“如何讓 Agent 自己知道何時(shí)收手”最后分享一次完整的故障排查鏈路和一些框架配置上的對(duì)照。如果你正準(zhǔn)備給自己的 Agent 加工具或者已經(jīng)被循環(huán)折磨過這篇文章應(yīng)該能幫你從“搶修模式”切換到“設(shè)計(jì)模式”。2. recursionLimit 到底在限制什么執(zhí)行深度與資源代價(jià)的賬先拋個(gè)結(jié)論recursionLimit 限制的不是工具的調(diào)用次數(shù)而是 Agent 決策循環(huán)的執(zhí)行深度。這個(gè)區(qū)別很重要。很多人看名字里有“recursion”這個(gè)詞就誤以為它管的是“遞歸調(diào)用”也就是工具 A 內(nèi)部調(diào)用了工具 B 那種嵌套場(chǎng)景。實(shí)際上在主流 Agent 框架和編排引擎的語境里recursionLimit 是配置在圖執(zhí)行器graph executor層面的一個(gè)計(jì)數(shù)器——每一次大模型完成一次推理并產(chǎn)生工具調(diào)用計(jì)數(shù)器就加一一旦超出設(shè)定閾值整個(gè)執(zhí)行就強(qiáng)制終止。為了把這個(gè)講透我從執(zhí)行深度的角度重新解釋一遍它的作用。2.1 執(zhí)行深度Agent 的“第幾手牌”你可以把 Agent 完成一個(gè)任務(wù)的過程想象成和它玩撲克。每打出一張牌它就根據(jù)牌桌上反饋的信息決定下一張?jiān)趺创?。這張牌可能是調(diào)用一個(gè)搜索工具可能是讀一個(gè)網(wǎng)頁也可能是寫一段代碼然后運(yùn)行。每一次“出牌”就代表 AI 工具循環(huán)多走了一步。recursionLimit 限制的就是“一共能出幾手牌”。它和工具數(shù)量無關(guān)——你要是有二十個(gè)工具但所有信息一步就能拿全可能一次調(diào)用就結(jié)束了反過來就算你只有兩個(gè)工具如果 Agent 在所有分支里反復(fù)橫跳二十步也可以瞬間用光。我自己的經(jīng)驗(yàn)里比較合理的理解方式是遞歸深度代表 Agent 對(duì)任務(wù)不確定性的容忍度上限。如果任務(wù)鏈路非常直觀比如“查天氣并告訴我”3 到 5 輪足矣如果是“訪問官網(wǎng) → 獲取 API 文檔 → 寫一個(gè)調(diào)用腳本 → 執(zhí)行腳本 → 分析輸出結(jié)果”那么這個(gè)鏈路本身就要 4 到 5 輪此時(shí) recursionLimit 必須大于它。2.2 循環(huán)失控時(shí)錢和時(shí)間的賬怎么算為什么整個(gè)行業(yè)都在強(qiáng)調(diào)管住 recursionLimit因?yàn)槭Э匮h(huán)燒掉的不僅是耐心更是真金白銀。我算過一筆實(shí)際賬一次 GPT-4 級(jí)別的工具調(diào)用輸入加上工具返回結(jié)果平均在 2000 到 4000 Token 左右一次循環(huán)里模型要重新讀取全部歷史消息包括前面所有工具調(diào)用及其結(jié)果所以 Token 消耗是線性疊加甚至超線性增長的再加上每次模型調(diào)用的 API 延遲通常在 2 到 6 秒之間循環(huán) 20 次就意味著用戶要干等 40 秒以上這還是在不考慮限流的情況下。更讓運(yùn)營頭疼的是這些失效循環(huán)并不是毫無意義的中斷而是反復(fù)調(diào)用真實(shí)工具。如果你的工具帶副作用——比如“發(fā)送郵件”“創(chuàng)建訂單”——那就不只是燒錢問題可能還會(huì)制造臟數(shù)據(jù)。我聽過一個(gè)案例某個(gè)團(tuán)隊(duì)給 Agent 配了數(shù)據(jù)庫寫入工具循環(huán)失控后訂單表里多了一堆重復(fù)測(cè)試記錄最后只能人工清洗。2.3 兜底和保護(hù)的關(guān)系所以我會(huì)說recursionLimit 本質(zhì)上是一道安全閘門。它不關(guān)心你這段循環(huán)的質(zhì)量不判斷你“是不是正走在正確的路上”它只是冷酷地告訴你到點(diǎn)了必須停。也正因?yàn)樗且粋€(gè)“一刀切”的全局限制單獨(dú)配置它并不能解決循環(huán)的“質(zhì)量問題”。舉個(gè)很常見的情況Agent 在處理一個(gè)需要分三步走的任務(wù)但是每走一步前它都習(xí)慣性地先調(diào)用一個(gè)“搜索工具”去確認(rèn)上下文。你把 recursionLimit 從 10 提到 20它可能還是會(huì)在 19 步時(shí)因?yàn)椤皼]有足夠信心”而繼續(xù)調(diào)用。這個(gè)場(chǎng)景里真正管用的不是加大限制而是給它一套更聰明的決策邏輯——這就是后面要講的條件路由。提示不要把 recursionLimit 當(dāng)作治理工具循環(huán)的唯一手段。它是發(fā)動(dòng)機(jī)的保險(xiǎn)絲不是方向盤。保險(xiǎn)絲能防止燒毀但它不負(fù)責(zé)把車開到目的地。3. 條件路由把“無限可能性”壓縮成“有限路由表”條件路由conditional routing這個(gè)詞聽起來很工程其實(shí)概念特別簡(jiǎn)單在 Agent 每次打算調(diào)用工具之前先通過一組規(guī)則或模型判斷明確告訴它“現(xiàn)在有那幾條路可選每條路的入口條件是什么”。沒有路由的 Agent 像是一個(gè)沒有地圖的司機(jī)。它手里有導(dǎo)航大模型導(dǎo)航告訴它“往前開”但是沒告訴它什么時(shí)候該拐彎、什么時(shí)候該停。條件路由就是在地圖上把路標(biāo)豎起來遇到“前方路口左轉(zhuǎn)”的標(biāo)志才允許它進(jìn)入左轉(zhuǎn)車道遇到“禁止通行”的標(biāo)志必須重新規(guī)劃。在實(shí)現(xiàn)層面條件路由可以做到很多種粒度。3.1 第一層路由工具選擇的前置規(guī)則最基礎(chǔ)的一層是在模型產(chǎn)生 tool call 之前加一道過濾器。比如你配置了三個(gè)工具get_file_content、summarize_text、send_email。理論上模型可以因?yàn)橐痪洹拔矣X得需要發(fā)送一下結(jié)果”就去調(diào)用send_email這沒問題但在真實(shí)場(chǎng)景里模型經(jīng)常因?yàn)閷?duì)工具職責(zé)理解不夠亂調(diào)一通。我自己的做法是寫一個(gè)簡(jiǎn)單的前置判斷def route_intent(user_input: str, tool_calls: list): # 假設(shè)模型已經(jīng)產(chǎn)生了候選工具調(diào)用 allowed_tools [] for call in tool_calls: tool_name call.function.name if tool_name send_email and not user_email_confirmed(user_input): # 未確認(rèn)收件人之前不允許進(jìn)入發(fā)信流程 continue allowed_tools.append(call) return allowed_tools這套規(guī)則的業(yè)務(wù)含義是如果用戶從頭到尾都沒提供過收件人郵箱就別讓 Agent 進(jìn)入發(fā)信分支。它看起來和“工具循環(huán)”問題無關(guān)但實(shí)際效果非常大——很多失控循環(huán)都是因?yàn)?Agent 在某個(gè)分支里做了“無法退出的操作”比如它真要發(fā)郵件但是參數(shù)又不夠于是反復(fù)回到查詢邏輯里死循環(huán)。3.2 第二層路由基于工具返回值的循環(huán)出口判斷第二層路由才是真正管循環(huán)的。它的核心機(jī)制是在每次工具返回結(jié)果后不是直接把結(jié)果原樣丟回給大模型而是先做一次“結(jié)果質(zhì)量判斷”決定是繼續(xù)調(diào)用、還是切換到另一個(gè)分支、還是直接終止。我舉個(gè)可復(fù)現(xiàn)的偽代碼例子def trim_tool_response(tool_output: str) - str: # 如果輸出過長則截?cái)嗳绻敵鰹榭談t返回特定標(biāo)記 if len(tool_output) 3000: return tool_output[:3000] ...[已截?cái)郵 if not tool_output.strip(): return [工具返回為空] return tool_output def decide_next_step(tool_name: str, tool_output: str) - str: trimmed trim_tool_response(tool_output) if [工具返回為空] in trimmed: return to_fallback # 走兜底分支而不是繼續(xù)調(diào)用同一工具 if 錯(cuò)誤 in trimmed or 異常 in trimmed: return to_error_handler return to_agent_loop # 正常進(jìn)入下一輪模型推理看到這里你應(yīng)該能明白條件路由的價(jià)值不只是“走到哪算哪”而是給循環(huán)增加了結(jié)構(gòu)性出口。原本 Agent 只能依靠自己“覺得任務(wù)完成了”來退出現(xiàn)在每一輪工具返回后都會(huì)先被一套規(guī)則重新審視一遍這次的返回到底有沒有推進(jìn)任務(wù)如果推進(jìn)了繼續(xù)沒推進(jìn)立刻切換策略出現(xiàn)錯(cuò)誤進(jìn)入錯(cuò)誤恢復(fù)。3.3 路由與 recursionLimit 的聯(lián)動(dòng)在我實(shí)際的項(xiàng)目配置里這兩個(gè)是一起調(diào)整的。舉例來說我給 Agent 設(shè)定的基礎(chǔ)規(guī)則是默認(rèn) recursionLimit 為 12。如果某個(gè)任務(wù)的工具鏈路預(yù)期會(huì)超過 8 步比如“先搜索 → 再讀取 → 再分析 → 再生成報(bào)告 → 再發(fā)送”那么路由邏輯會(huì)自動(dòng)把限制放大到 20。但這還不夠因?yàn)閳?zhí)行深度越接近上限Agent 越容易慌亂開始無意義地重復(fù)調(diào)用工具。于是我又加了一條“殘余步數(shù)保護(hù)”當(dāng)引擎檢測(cè)到剩余步數(shù)少于 3 步時(shí)自動(dòng)把路由策略切換為“保守模式”只允許調(diào)用已經(jīng)驗(yàn)證過的工具禁止再開新的工具分支。這個(gè)設(shè)計(jì)聽起來復(fù)雜實(shí)際上就是在循環(huán)的某一輪開始之前先把路由表替換一份。執(zhí)行后狀態(tài) - 當(dāng)前輪次9 - recursionLimit12 - 剩余步數(shù)3 - 路由模式conservative 允許read_document 允許search_documents僅限精確關(guān)鍵詞 - 禁止send_email、create_ticket、execute_script我實(shí)測(cè)下來這套“余量感知路由”能顯著降低循環(huán)失控率。原因不復(fù)雜——大模型很多時(shí)候不是笨而是“貪心”它總覺得再多查一步、再確認(rèn)一輪會(huì)更完美。越到后期這種完美主義傾向越強(qiáng)。切斷新分支的選項(xiàng)等于直接告訴它“只能從這個(gè)出口走”最后幾步它不是漫無目的地探索而是收斂式地完成任務(wù)。4. 條件路由設(shè)計(jì)實(shí)例從“亂串門”到“直線頂?shù)浇Y(jié)果”光講理論不好消化這一節(jié)我把我自己做的一個(gè)小項(xiàng)目完整展開。這是一個(gè)“代碼倉庫問診 Agent”用戶描述一個(gè)異常現(xiàn)象它需要去倉庫里找代碼、讀關(guān)鍵文件、定位 bug、給出修復(fù)建議。第一次配好工具時(shí)它就是這么失控的Agent 老是亂串門一會(huì)兒調(diào)list_files列出目錄結(jié)構(gòu)一會(huì)兒調(diào)read_file讀文件一會(huì)兒又回頭調(diào)list_files搞得像是在迷宮里亂撞。4.1 問題抽象工具調(diào)用路徑太發(fā)散為什么要引入條件路由因?yàn)槟J(rèn)的 agentic loop 把“下一步做什么”的決定權(quán)完全交給了大模型。模型每次都是根據(jù)“我剛才看到的東西”來決定下一步它沒有全局意識(shí)更看不到“我已經(jīng)白走了三遍同一個(gè)目錄”。我的思路是把任務(wù)劃分成四個(gè)階段每個(gè)階段是一個(gè)路由容器探索階段只看結(jié)構(gòu)不讀內(nèi)容。允許調(diào)用list_files、grep_code閱讀階段根據(jù)探索結(jié)果領(lǐng)取文件清單逐一批量讀取不允許再臨時(shí)搜索分析階段不允許再調(diào)用任何工具要求模型基于已有上下文輸出完整的推理鏈條報(bào)告階段生成最終結(jié)論可調(diào)用send_email。每個(gè)階段內(nèi)部有自己的 recursionLimit 預(yù)算。整個(gè)循環(huán)執(zhí)行時(shí)引擎先判斷當(dāng)前處于哪個(gè)階段再?zèng)Q定下一步該走哪個(gè)分支。4.2 實(shí)現(xiàn)階段路由表解析我簡(jiǎn)化后的路由代碼如下方便大家照著改自己的場(chǎng)景STAGES { explore: { allowed_tools: [list_files, grep_code], max_steps: 4, }, read: { allowed_tools: [read_file], max_steps: 8, }, analyze: { allowed_tools: [], max_steps: 1, }, report: { allowed_tools: [send_email], max_steps: 2, }, }在執(zhí)行器的主循環(huán)里我會(huì)先通過一個(gè)函數(shù)判斷當(dāng)前狀態(tài)屬于哪個(gè)階段def get_current_stage(trace: list) - str: 根據(jù)最近的調(diào)用記錄判斷當(dāng)前處于哪個(gè)階段 if not trace: return explore recent_calls trace[-5:] all_read all(call.had_content_result for call in recent_calls) if all_read: return analyze last_tool trace[-1].tool_name if last_tool read_file: return read if last_tool grep_code or last_tool list_files: return explore return explore這個(gè)設(shè)計(jì)有個(gè)額外好處recursionLimit不再是一個(gè)全局拍腦袋的數(shù)字而是可以分?jǐn)偟矫總€(gè)階段里。按照上面配置整個(gè)任務(wù)最多消耗 4 8 1 2 15 步。也就是說即使 Agent 完全學(xué)不會(huì)“怎么收斂”路由容器也會(huì)在 15 步內(nèi)強(qiáng)制把它推到最終報(bào)告。從實(shí)際使用效果看改造前平均每個(gè)任務(wù)要跑 11.6 輪改造后平均只需要 5.2 輪而且任務(wù)的失敗率從 32% 降到了 9% 左右。4.3 兜底分支設(shè)計(jì)路由撞墻了怎么辦任何路由設(shè)計(jì)都不能保證“一定不會(huì)走錯(cuò)”所以必須給自己留兜底分支。我通常在每個(gè)階段外加一個(gè)fallback分支專門處理“模型堅(jiān)持調(diào)用禁用工具”的情況。舉個(gè)例子在 explore 階段模型理論上不允許直接調(diào)read_file。但我們的模型偶爾還是會(huì)發(fā)出這個(gè)請(qǐng)求。這時(shí)候我不會(huì)生硬地拒絕而是注入一條提示語注意當(dāng)前處于“探索結(jié)構(gòu)”階段請(qǐng)先列目錄或搜索關(guān)鍵詞定位目標(biāo)文件 不要直接讀取文件內(nèi)容。建議調(diào)用 list_files 或 grep_code。更進(jìn)階的做法是往回?fù)軤顟B(tài)如果模型在一個(gè)階段里連續(xù)兩次觸發(fā)了禁用工具調(diào)用我就把它的“視野”往前回溯一步相當(dāng)于讓它重新看到上一個(gè)階段結(jié)束時(shí)的信息避免它在錯(cuò)誤分支里越陷越深。我把這個(gè)叫“狀態(tài)回滾”它和 recursionLimit 一樣是第三道保險(xiǎn)栓。5. 一次“遞歸風(fēng)暴”的完整排查鏈路從日志到修復(fù)紙上談兵完了分享一次真實(shí)的踩坑過程。不光講結(jié)論把排查的鏈路和思路一起寫出來這比直接給答案有用得多。5.1 現(xiàn)象Agent 在 47 秒內(nèi)完成 143 次工具調(diào)用有一次我把一個(gè)帶八個(gè)工具的 Agent 接進(jìn)了生產(chǎn)環(huán)境的工單系統(tǒng)讓它自動(dòng)回復(fù)客戶問題。上線第二天監(jiān)控面板報(bào)警某個(gè)工單的應(yīng)答流程持續(xù)了 47 秒期間完成了 143 次工具調(diào)用消耗的 Token 相當(dāng)于平時(shí)一千多條消息的總量。第一反應(yīng)是檢查是不是工具本身卡住了比如某個(gè) API 超時(shí)導(dǎo)致循環(huán)。但日志顯示每個(gè)工具都是正常返回的而且返回的內(nèi)容一樣——是同一個(gè)“客戶訂單狀態(tài)查詢”接口。這說明問題不在工具而在 Agent 的循環(huán)策略本身。5.2 逐層剝離三步定位問題根因排查這種事最忌諱直接改配置瞎試。我按下面三個(gè)步驟做了剝離**第一步看調(diào)用圖譜。**把所有工具調(diào)用按時(shí)間序列畫出來后發(fā)現(xiàn)是典型的“乒乓型循環(huán)”調(diào)用訂單查詢 → 返回“已發(fā)貨” → Agent 再調(diào)物流查詢 → 返回“運(yùn)輸中” → Agent 再調(diào)訂單查詢 …… 無限往復(fù)。兩個(gè)工具互相給對(duì)方“喂新問題”而大模型又沒有能力判斷“這兩個(gè)信息其實(shí)指向同一個(gè)結(jié)論”。**第二步縮小工具范圍。**我臨時(shí)把物流查詢工具下線只留訂單查詢。結(jié)果 Agent 雖然沒再乒乓但還是反復(fù)調(diào)用訂單查詢并持續(xù)輸出“我需要進(jìn)一步核實(shí)”。問題定位為模型對(duì)“已發(fā)貨”狀態(tài)的理解存在不確定性它傾向于反復(fù)確認(rèn)來彌補(bǔ)這種不確定感。**第三步檢查路由規(guī)則。**當(dāng)時(shí)項(xiàng)目里其實(shí)已經(jīng)配置了一個(gè)簡(jiǎn)單路由如果訂單狀態(tài)為“已發(fā)貨”則路由到“物流階段”。但這條路由只判斷了訂單狀態(tài)沒有判斷“物流階段是否已經(jīng)查詢過”。所以 Agent 在物流查詢結(jié)果回來后又被叔叔式地引導(dǎo)回訂單查詢循環(huán)就產(chǎn)生了。修復(fù)的方案很直接把路由判斷從“無狀態(tài)”改成“有狀態(tài)”。用一個(gè)上下文存儲(chǔ)記錄“最近 3 輪已經(jīng)調(diào)用過哪些工具、結(jié)果是什么”。如果接下來模型再次觸發(fā)相同工具調(diào)用并且參數(shù)完全相同路由直接攔截這次調(diào)用然后把上一次的結(jié)果重新提供給模型并附上一句提示“該信息已存在請(qǐng)基于現(xiàn)有信息繼續(xù)”。5.3 修復(fù)效果的量化對(duì)比我記錄了一個(gè)小表方便后續(xù)參考指標(biāo)修復(fù)前修復(fù)后單任務(wù)平均工具調(diào)用次數(shù)27.46.8單任務(wù)平均耗時(shí)時(shí)長21.3s5.7sToken 消耗中位數(shù)18.2K6.1K用戶投訴率14%3%這次經(jīng)歷讓我深刻體會(huì)到一點(diǎn)recursionLimit 管的是“最大能跑多少”路由管的是“跑得有沒有意義”。如果只調(diào)大 recursionLimit這次故障的持續(xù)時(shí)間會(huì)更長如果只加路由但不管 recursionLimit極端情況照樣會(huì)失控。兩件事必須同時(shí)做。6. 不同框架里的 recursionLimit 配置對(duì)照與選型建議聊到具體配置不同框架對(duì) recursionLimit 的實(shí)現(xiàn)不完全一樣。我這里把幾個(gè)常見環(huán)境下的配置方式做一下對(duì)照方便大家按自己的需求選。6.1 LangGraph顯式的 recursion_limitLangGraph 是我用得最多的 Agent 編排框架。它內(nèi)置了recursion_limit的概念直接傳給編譯后的圖執(zhí)行器即可from langgraph.graph import StateGraph graph StateGraph(...) app graph.compile( checkpointer..., # 限制整個(gè)圖執(zhí)行的最大步數(shù) recursion_limit15, ) result await app.ainvoke( initial_state, config{recursion_limit: 15}, )值得注意的是LangGraph 里recursion_limit是按“圖中節(jié)點(diǎn)被執(zhí)行的次數(shù)”計(jì)的中間的消息聚合、條件路由本身也會(huì)消耗這里的計(jì)數(shù)而不僅僅是“工具調(diào)用次數(shù)”。所以設(shè)計(jì)圖結(jié)構(gòu)時(shí)不要以為自己設(shè)置 10 就等于模型只能調(diào)用 10 次工具——如果圖里加了一個(gè)條件分支節(jié)點(diǎn)每輪循環(huán)要跑兩個(gè)節(jié)點(diǎn)那 10 次 recursion limit 其實(shí)只相當(dāng)于 5 輪工具循環(huán)。6.2 OpenAI Assistants APImax_prompt_turns 與 tools 約束OpenAI 的 Assistants API 里沒有叫 recursionLimit 的參數(shù)但有一個(gè)更接近的概念max_prompt_turns。它限制的是“一次會(huì)話中最多允許多少輪模型推理”。和 LangGraph 不同的是OpenAI 平臺(tái)在后臺(tái)會(huì)自動(dòng)做一次“任務(wù)完成度預(yù)判”當(dāng)你設(shè)置得明顯低于任務(wù)需要時(shí)它會(huì)提前終止。另外OpenAI 的 Function Calling 有一個(gè)隱性約束同一輪內(nèi)模型可以直接放棄調(diào)用工具并返回最終答案。所以對(duì)付簡(jiǎn)單失控不一定非要上重型路由先把 system prompt 里的“請(qǐng)僅在確保需要外部數(shù)據(jù)時(shí)才調(diào)用工具”寫到位就能避免很多無謂循環(huán)。6.3 自研 Loop自己造輪子時(shí)的三個(gè)關(guān)鍵點(diǎn)如果你像我一樣早期是自己寫簡(jiǎn)單的 tool loop讀歷史 → 調(diào)模型 → 調(diào)工具 → 追加歷史 → 再讀歷史那么沒有現(xiàn)成的 recursionLimit 可用必須自己實(shí)現(xiàn)。我建議至少封裝三層class ToolLoopController: def __init__(self, max_rounds10, max_consecutive_same_tool2): self.max_rounds max_rounds self.max_consecutive_same_tool max_consecutive_same_tool def before_tool_call(self, trace, next_tool_name, next_tool_args): # 第一層總量限制 if len(trace) self.max_rounds: return stop # 第二層連續(xù)相同工具限制 recent [t.tool_name for t in trace[-3:]] if len(recent) 3 and all(name next_tool_name for name in recent): return force_new_branch # 第三層參數(shù)相似度限制 last_call trace[-1] if trace else None if last_call and last_call.tool_args next_tool_args: return reuse_previous_result return allow這個(gè)控制器在每次模型返回 tool call 前執(zhí)行等于是把路由規(guī)則和 recursionLimit 合二為一了。三層判斷分別解決“總步數(shù)超了”“繞圈圈”“原地踏步”三類循環(huán)問題。6.4 選型建議如果你剛開始做 Agent我建議直接用 LangGraph 或同類圖編排框架因?yàn)樗?recursionLimit 和條件路由都做成了第一公民不需要你自己維護(hù)循環(huán)狀態(tài)。如果只是簡(jiǎn)單接一兩個(gè)工具的快速驗(yàn)證場(chǎng)景OpenAI Assistants 的max_prompt_turns也夠用。只有當(dāng)你對(duì)控制粒度有非常規(guī)要求時(shí)再考慮自己造輪子。7. 管住循環(huán)之后Agent 才談得上“可靠”工具循環(huán)是 Agent 的核心機(jī)制但核心機(jī)制恰恰需要被約束才能發(fā)揮價(jià)值。recursionLimit 和條件路由這兩者的關(guān)系我總結(jié)成一句話限制告訴 Agent 最多能跑多遠(yuǎn)路由告訴 Agent 每一步往哪個(gè)方向跑。只給限制不給方向Agent 會(huì)在撞墻前不斷繞圈只給方向不給限制一個(gè)迷路的 Agent 能把整條鏈路拖垮。我個(gè)人的實(shí)操體會(huì)治理工具循環(huán)至少要過三關(guān)第一關(guān)是基線設(shè)計(jì)。給每個(gè) Agent 任務(wù)估算最合理的步數(shù)范圍然后把 recursionLimit 定在“能完成任務(wù)的 1.2 倍”而不是“盡可能大”。比如你推斷任務(wù)需要 6 步那就寫 8 或 9不要寫 20。寬松的限制只是延遲了問題爆發(fā)的時(shí)間。第二關(guān)是運(yùn)行監(jiān)控。給工具調(diào)用加上日志和可視化追蹤重點(diǎn)觀察那些“逼近上限但還在調(diào)用”的任務(wù)。這類任務(wù)往往不是缺失步數(shù)而是缺少退出條件——你應(yīng)該去補(bǔ)路由規(guī)則而不是去調(diào)大限制。第三關(guān)是事后復(fù)盤。每次循環(huán)失控后把你當(dāng)時(shí)的工具調(diào)用序列保存下來作為構(gòu)造條件路由規(guī)則的數(shù)據(jù)來源。你的路由規(guī)則越貼近真實(shí)失敗案例后續(xù)的效果就越好。最后再分享一個(gè)小技巧如果你發(fā)現(xiàn) Agent 在某類任務(wù)上總是差一兩步才能完成不要只調(diào)高 recursionLimit試著在路由里給它加一個(gè)“重試一次并忽略歷史細(xì)節(jié)”的分支——讓它忘掉前面幾輪無效信息只基于最近一次工具結(jié)果重新推理。我用了這個(gè)方法之后很多“卡在臨界點(diǎn)”的 Agent 一下就順暢了。工具循環(huán)本身不可怕可怕的是對(duì)它毫無防備。把遞歸上限和路由規(guī)則一起配齊你的 Agent 才能真正從“會(huì)調(diào)用工具”升級(jí)成“能靠譜地完成任務(wù)”。