Agent反复调用工具烧钱,OpenRouter四道闸封住循环
OpenRouter 用四道闸应对 Agent 反复调用同一工具:识别重复调用、阻止微小改参绕过、限制单次运行步数与成本,并用 API 密钥额度兜住总支出。

- 失败重试会重发完整对话,推高单轮成本
- 重复检测可按连续次数阻止相同调用
- loopKey 可归一参数差异以识别变体
- 单次上限之外还需服务端密钥额度兜底
官方教程兼产品演示,测试由 OpenRouter 完成;文中数字为内部实验结果,未见第三方复测。
发票接口持续超时,Agent 却拿着同一张发票一次又一次重试;你看到调用次数不断上涨,它并没有主动停手。OpenRouter 的测试显示,这类循环不仅会重复消耗工具调用,每轮还会带上更长的对话历史,让单次调用越来越贵。它在 Agent SDK 里加入四道闸:识别并阻止重复调用、归一化参数、限制单次运行的步数与成本,再由 API 密钥额度守住总支出,目标是在账单失控前先截住循环。
Agent 为什么会越烧越贵?
每轮重试都会把旧错误一并发回模型,输入越来越长,单轮成本跟着抬升。Agent 越烧越贵,不只是因为工具失败后多跑了几次。
OpenRouter 的教程把原因指向一个循环:工具持续报错,模型收到的任务指令却仍要求完成任务,于是它把再次调用工具当成下一步。错误被写回对话后,Agent 本身不会自动统计尝试次数,除非开发者另加停止规则。
每轮输入都包含此前的失败信息,模型看得见服务仍然不可用,却可能继续重试;账单增加的并不只是本次工具操作,还有越来越长的上下文输入。
OpenRouter 在一次逐轮记录的演示中测到,输入从 131 个 token 增至 3,262 个,同一次演示的单轮成本接近四倍。token(模型处理文本时计量长度的单位)增长约 24 倍,单轮价格却接近四倍,说明它不能被直接理解成逐条等比例上涨。
另一个演示给发票查询工具设置了持续超时。循环持续到运行结束,共执行 41 次相同工具调用,费用为 0.0309 美元。41 次调用只花这么点钱,是因为那次设了步数上限;没设上限时它不会自己停。
那次运行设置了步数上限,所以循环才在最后停下。若没有步数上限,OpenRouter Agent SDK(让模型能够调用工具并连续行动的开发工具包)会持续运行,直到模型不再请求工具。换句话说,工具一直失败时,Agent 并没有一个天然终点。
第三次相同调用,它就被拦下
模型拿到上一次工具结果,又用相同工具、相同参数再要一遍——这就是它拦的循环。
OpenRouter 管这叫“厄运循环检测”(识别工具反复调用并中止运行)。默认规则分三步:连续出现 2 次相同调用时,系统先观察;第 3 次时阻止;连续达到 6 次时,终止整个运行。观察阶段不会立即打断模型,阻止阶段会拦下当前调用,终止阶段则结束这次任务。
什么才算“相同”?工具名称相同,参数也必须等价,而且模型得先拿到上一次执行结果,才有机会再次提出同一请求。若模型尚未看到结果便连续生成请求,不会按这种方式逐次累加。
多数循环没走到第三次。OpenRouter 在六个当前模型上各运行三次,共完成 18 次测试,开启检测后,有 16 次只执行了两次真实工具调用便结束。
整体调用量也随之下降:检测关闭时,18 次运行共调用工具 314 次,费用为 0.29 美元;开启后,工具调用总数降至 64 次,费用降至 0.05 美元。这里记录的是厂商自己的测试结果,目前没有第三方复测数据,不能直接外推到其他模型或不同任务。

参数只改一点,循环检测就会失效吗?
会。逐字比对参数,模型只需轻微改写就能制造出“不同请求”。更稳妥的办法是先归一化参数,再识别它们是否属于同一次调用。
部分模型会改变工具参数,再发起看似不同的请求。检测器若逐字比对,就可能把这些请求当成新调用,让循环继续执行。检测器要比的是参数指向的目标,不是字面写法。
loopKey(把调用归一为同一身份的标记)正是为这类情况设计的。开发者可指定需要忽略的参数差异;系统按规则归一化后,再识别重复调用。模型即使微调参数,也难以靠换一种写法逃过检测,前提是开发者把该忽略的差异配进 loopKey。
归一化还要结合调用所在的轮次。同一轮中的五次相同调用只计作一次重复,不会被当成五段独立工作。检测关注的是同一轮内的重复行为,而非简单累加调用次数。

第三道闸:给单次运行踩刹车
单次运行要同时设步数上限和成本上限:前者限制循环能走多远,后者限制这次最多花多少钱。
OpenRouter Agent SDK(用于构建多轮工具调用的开发包)把停止条件写在 stopWhen(达到条件时结束本次运行) 里,步数和成本两个条件可以一起交给它。stepCountIs(按运行步数停止) 管的是循环能走多远,maxCost(按累计费用停止) 管的是这次最多烧掉多少钱。两道限制分别兜住两类失控:调用次数很多但单次便宜,或调用次数不算多却因模型、工具和上下文变贵。
步数上限并非精确的“工具调用计数闸”。当 stepCountIs(40) 到达上限时,模型此前已经发出的额外工具调用仍可能继续执行一次。因此,开发者不能把“停止”理解成后续请求必然不会发生;工具侧仍需按可能多出一次调用来设计。
如果完全不设置 stopWhen,Agent SDK 不会替开发者猜测何时算“够了”。它会持续运行,直到模型不再请求工具。工具持续失败、模型反复重试或参数不断变化,都可能让结束条件迟迟不出现。
这套限制按每次运行生效,适用于不同原因触发的循环,但不约束所有运行叠加后的总支出。当前 OpenRouter Agent SDK 版本为 0.11.0,开发者需要确认依赖版本,并分别设置步数与成本条件,而不是只押注其中一道限制。
代码失效后,谁来兜住总账单?
当 Agent 的内部止损逻辑失效,OpenRouter 仍会按 API 密钥的总花费执行额度限制。请求一旦越过额度,服务端会让它失败并返回 402,超额请求直接失败,账单停在那里。
前 3 道防护都由 Agent 内部执行:重复调用检测负责识别异常,参数归一化用于识别换皮后的重复,步数与成本限制负责约束单次运行。Agent 代码一旦出错,这些判断可能停摆,账单却仍由使用同一把 API 密钥的请求累积。
密钥额度在服务端执行,开发者不需要依赖 Agent 自觉停下,也不需要假设每一段本地代码都正确工作。代码失效能影响 Agent 怎样调用工具,却不能解除服务端已经设定的支出上限。
| 防护位置 | 约束对象 | 代码失效后的结果 |
|---|---|---|
| Agent 内部 | 重复调用、单次运行的步数与成本 | 相关判断可能无法执行 |
| OpenRouter 服务端 | API 密钥累计花费 | 超额请求仍会失败 |
前 3 道闸管的是运行过程,密钥额度管的是账户总账,单次运行成本上限不能当成总账单兜底。
额度触发的反馈也可以定位。请求超出密钥额度后会返回 402(表示请求因额度限制被拒绝),返回信息中的 limit_source 为 openrouter_key_limit。这个字段指明拒绝来自密钥额度,而不是 Agent 内部的重复检测或单次运行限制,便于开发者查清账单为何被截住。
回到开场那个画面:当发票接口再次超时,Agent 准备发起下一次重试时,这四道闸会先于失控账单出现。
如果 Agent 必须先完成一张发票查询才能结束任务,哪道闸该先触发?
本文基于OpenRouter:Announcements(RSS)原文撰写(2026-11-12)。厂商公布的数字(跑分、降幅等)均为官方口径,除注明外尚未经第三方独立复测。
术语小典 · 本篇出现的人话注解
深度、研究解读、工具教程的全文和全部播客,都在会员区。