摘要: OpenRouter 在 2026 年 11 月 12 日發布教學,說明如何用四道防線擋住 AI agent 的無限迴圈,其中三道跑在 Agent SDK(@openrouter/agent 0.11.0)裡,第四道是 API key 的信用額度。他們在六個現行模型上實測:開啟 doom-loop 偵測後,18 次執行的工具呼叫總數從 314 次降到 64 次,花費從 $0.29 降到 $0.05。對任何把 agent 放上線的人來說,這是把「迴圈燒錢」從事後事故變成可設定的工程問題。
迴圈為什麼越轉越貴
Agent 迴圈的結束條件是模型不再要求呼叫工具、直接給出答案。當一個工具持續失敗,錯誤訊息回到對話裡,指示又要求完成任務,模型自然認為再試一次是正確的下一步——除非你的程式碼主動計數,否則迴圈裡沒有任何東西在算次數。
更麻煩的是成本結構:每一輪都重送整段對話,包括之前所有失敗紀錄。OpenRouter 逐輪記錄的一次執行中,input 從 131 tokens 漲到 3,262,單輪成本幾乎變成四倍。他們的估算是,換成更貴的模型、多個 session 同時跑,一個跑整晚的迴圈可以累積到數千美元。
他們為了重現這個場景,寫了一個唯一的工具必定逾時失敗的 billing agent。沒開防線時,Mistral Large 4 連續打了 41 次完全相同的 fetch_invoice("INV-2291"),最後只因為 stepCountIs(40) 的上限才停下,花了 $0.0309——而且模型每一輪都知道服務在逾時,還是照打。
偵測怎麼運作:指紋、階梯、攔截
doomLoop: true 之後,SDK 對每次工具呼叫做指紋:以工具名稱加上正規化後的參數計算,JSON key 順序不同也算同一通。關鍵細節是,只有當模型已經看過上一次的結果、又再要求同樣的呼叫,才算重複——同一輪裡打五次相同的工具只算一次。
隨著連續次數(streak)上升,預設階梯是:streak 2 觀察、streak 3 攔截(block)、streak 6 終止(stop)。被攔截的呼叫不會真的執行,SDK 直接回一個 tool error,明確告訴模型「再試也不會有不同結果」。大多數執行裡,模型讀完就寫出最終答案;如果還繼續要求,stop 就終止整個 run,reason 記為 doom_loop。getDoomLoopVerdict() 只在偵測終止 run 時回傳訊息,作者建議把它當作告警訊號。
閾值可以調低。用 doomLoop: { ladder: { observe: 2, block: 3, stop: 4 } },一次 GPT-6 Luna 執行在 streak 4 就結束,只花 $0.0002。階梯還有兩個預設關閉的檔位:steer 會提示模型它在重複,escalate 則把下一輪交給你指定的更強模型。
模型會繞過偵測
實測暴露了一個真實限制:預設的比對要求參數完全相同,被 block 之後,有些模型會微調參數換來新指紋。Mistral Large 4 在偵測開啟的九次額外執行裡有五次這樣做,其中三次靠交替使用 INV-2291 和 inv-2291 跑到 40 步上限;GPT-6 Luna 則用尾隨空格和換行達到同樣效果。諷刺的是,模型是在照 block 訊息的指示「換個方法」——只有一個工具時,唯一能換的就是輸入。
解法是在工具上設 loopKey,告訴偵測器輸入中哪個部分才是這通呼叫的身分:
loopKey: ({ invoice_id }) => invoice_id.replace(/\D/g, ''),
// "inv-2291 " 和 "INV-2291" 從此算同一通
每種寫法都映射到 2291,streak 持續累積,重複持續被攔。加上 loopKey 後,六次執行有五次在兩次真實呼叫後停止;第六次模型改查另一張發票 INV-0000,streak 正確重置。作者的原則是:正規化大小寫、空白、前綴——一切不影響工具實際抓哪筆記錄的東西。輪詢任務狀態這類本來就該重複的工具,則設 loopKey: false。
偵測之外:兩道兜底防線
偵測只抓重複。模型若每輪都嘗試新東西,永遠不會撞上指紋。所以第三道防線是 stopWhen 的硬上限:
stopWhen: [stepCountIs(15), maxCost(0.01), () => failuresInARow >= 5],
maxCost 累加每個回應回傳的 usage.cost,算的是實際帳單金額;失敗計數器不看參數,即使沒有 loopKey 也抓得到第二步那種換參數的繞法。作者用自己正常 run 的數據(Mistral Large 4 上四到六輪、$0.0018 到 $0.0053)來設上限,15 步和 $0.01 留了餘裕又不放行 40 輪的 run。兩個上限之間,成本上限更重要,因為後面的輪次比前面貴。
這些都跑在你的 agent process 裡。如果那段程式碼本身有 bug、忘了部署上限、或一百個 session 各自低於 maxCost,總量就沒人管。第四道防線是把信用額度設在 API key 上,由 OpenRouter 在他們那邊檢查——塞不下的請求會以 402 失敗,附上 limit_source: "openrouter_key_limit"。值得注意的是,這篇教學的收集文本在第四步中途截斷,額度機制的完整細節在現有材料裡看不到,這裡只轉述已確認的部分。這種「在你的程式碼之外、runtime 層多加一道不受 agent 影響的防線」思路,跟先前寫過的 Docker Sandboxes 用 microVM 給 AI 編碼代理圈出安全區是同一個方向:信任邊界要放在你自己程式碼的外面。
你該檢查什麼
先誠實面對材料的限制:這些數據來自 OpenRouter 自己的測試 agent(一個必定逾時的計費工具),六個模型各跑三次。它沒有告訴我們偵測在真實生產 agent、更多樣的工具組、或這六個模型之外的表現如何,也沒有任何生產事故的成本數據。Grok 4.7 在同一個輸入上分別打了 41、12、41 次,這個變異本身就是提醒:一次乾淨的測試不證明你的 agent 不會迴圈。
落地的順序,照他們的層次走比較穩:先打開 doomLoop: true 看觀察層的日誌,再根據工具語意寫好 loopKey 的正規化規則,最後從正常 run 的步數和成本分佈推導 stopWhen 上限,並在 key 上設信用額度作為最後一道閘。如果你的迴圈不是跑在 Agent SDK 上,同一套守衛在任何迴圈裡都裝得下——按工具名和正規化參數計數、第三次重複時直接回 tool result、成本或步數過線就停;他們用 chat completions API 寫的 Python 版,三次執行都在四步內、低於 $0.002 停下了 Mistral Large 4。
