用进程内熔断拦住大模型失控账单
文章介绍在 Python 与 TypeScript 应用中实现零外部依赖的本地熔断器,通过实时 token 成本账本设置会话预算上限。结合滚动动作签名检测循环,阻止 Agent 因工具异常反复重试而持续消耗额度。
AI 深度解读
Agent 的一次异常重试,可能把代码错误变成 API 账单。原文用一个风险场景切入:携带 4,000+ token 上下文的任务,在 20 分钟内调用 50 次,开发者最后面对 $300+ 的意外支出。作者主张在应用进程内同时设置预算上限和重复动作检测。
这个方向适合做 Agent、RAG 和 LLM 后端的程序员参考,但标题里的「防止失控账单」说得偏满:示例能识别部分死循环,却没有真正实现请求发出前的费用硬限制。读懂这个差别,比直接复制代码更重要。
连续重复三次,应该在哪里拦住
作者把安全机制分成两部分:一份内存账本记录费用,一段动作检测逻辑识别重复调用。两者解决的问题不同。正常执行也可能消耗过多预算;卡住的 Agent 则可能在花完预算之前就该停下。
循环检测采用 SHA-256,把动作签名转换成哈希,再检查最近几次是否完全一致。默认参数 max_repeat_actions=3,即连续第三次出现相同动作时抛出 RuntimeError。
下面这段代码展示了检查应该放在哪里:
breaker = LoopBreaker(max_repeat_actions=3)
while agent.is_running:
tool_call = agent.get_next_action()
# 在执行前检查签名
breaker.check(f"{tool_call.name}:{tool_call.args}")
result = tool_call.execute()
要看的是 breaker.check(...):它在 tool_call.execute() 之前,因此能够阻止第三次相同工具动作执行。不过,如果 agent.get_next_action() 内部已经请求过模型,这次模型费用仍然可能已经产生。
作者说 Agent 常重复相同或近似的工具签名,但代码只判断完全相同且连续出现的动作。参数里多了时间戳,或者调用顺序变成 A、B、A、B,都不会触发这个条件。SHA-256 也不会让近似文本自动变成相同签名。
另一个实现细节是:检查时只取最近 N 项,action_hashes 却持续追加,没有限制列表长度。因此,这是一段简单的连续重复检测示例,还不是完整的有界滚动窗口。
根据 usage 记账,为什么还不算硬预算
费用计算采用常见公式:
总费用 = 输入 token ÷ 1,000,000 × 输入单价 + 输出 token ÷ 1,000,000 × 输出单价
作者建议读取 response.usage,按每百万 token 的费率记账。正文举过每个会话 $1.00 的上限,实际代码默认值则是 $2.00。
下面是原文的预算实现,它解决的是累计费用检查:
class BudgetGuard:
def __init__(self, max_budget_usd: float = 2.00):
self.max_budget_usd = max_budget_usd
self.total_spent_usd = 0.0
def record(self, input_tokens: int, output_tokens: int, input_rate_1m: float, output_rate_1m: float):
cost = (input_tokens / 1_000_000) * input_rate_1m + (output_tokens / 1_000_000) * output_rate_1m
if self.total_spent_usd + cost > self.max_budget_usd:
raise Exception(f"Budget exceeded! Attempted spend would reach ${self.total_spent_usd + cost:.4f} (Cap: ${self.max_budget_usd:.2f})")
self.total_spent_usd += cost
return cost
要注意 if self.total_spent_usd + cost > self.max_budget_usd:如果 cost 来自已经返回的 response.usage,检查发生时费用已经产生。它能让调用方停止后续请求,却不能撤销刚刚发生的支出。
而且,超限分支先抛异常,没有把这笔实际费用写进 total_spent_usd。如果上层捕获异常后继续执行,账本还会少记一笔。正文承诺的 BudgetExceededError 也没有出现在实现中,代码抛的是通用 Exception。
事后记账可以帮助止损,但不能保证费用绝不越过上限。
要实现更严格的限制,还需要请求前估算、预留预算,并在响应后结算;原文没有给出这些逻辑。并发请求如何共享额度、进程重启如何恢复账本,也没有说明。
我的判断
我赞同把运行时保护放进应用。作者认为 OpenAI、Anthropic、AWS 的账单邮件可能在预算耗尽数小时后才到,不能承担实时停止任务的职责。不过,原文没有提供通知延迟的实测数据,不能把这个时间描述当成所有平台的固定表现。
这套方案的价值在于接入直接、示例没有外部依赖。与单纯设置最大重试次数相比,它额外考虑了动作是否重复,以及不同调用的实际成本。但原文没有对照实验,也没有展示真实事故被成功阻断的记录,效果仍属于机制分析和作者经验。
文章后半段还有产品推广,需要与代码示例分开看。作者称免费计算器覆盖 13+ 个模型,列出 OpenAI GPT-6/4o、Claude Opus 5.5/Sonnet、Gemini 4/Flash,但没有给出对应价格表和核验依据。
收费的 LLM Token Guard 标价 $19,作者声称提供 OpenAI、Anthropic、Google 的 15+ 模型定价、Python 与 TypeScript / Next.js 实现、Slack/Discord webhook 告警、100% 通过的单元测试和商业许可证。这些都是产品声明,正文没有提供足够材料验证其「生产可用」程度。尤其标题提到 TypeScript,实际公开代码只有 Python。
能拿来做什么
- 在现有 Agent 执行器中,把动作检查放到工具执行之前,并为工具参数生成稳定签名;先确认正常业务是否允许连续重复,再决定是否沿用
max_repeat_actions=3。 - 在 Node 或 Python 的 LLM 请求封装层统一读取
response.usage、计算费用,并让超限异常结束任务;不要让通用重试逻辑把预算异常再次变成请求。 - 如果业务要求严格控制单次任务支出,就在请求前预留额度、请求后结算,并处理并发占用预算的问题;原文的
BudgetGuard.record()只能作为其中的记账起点。 - 评估收费包时,要求查看 TypeScript / Next.js 实现、定价更新方式和测试覆盖场景,重点检查交替循环、并发请求及超限后的真实费用是否被记录。