[控场AI]
· 5 分钟阅读· 2,670 字

LangGraph代理成本控制:recursion_limit数步数,美元限额才管钱

LangGraph代理成本控制:recursion_limit数步数,美元限额才管钱

recursion_limit只控步数不控成本,需在网关层实施美元/分钟限额并解决锁存与run隔离问题。

文章指出 LangGraph 的 `recursion_limit` 仅按步数终止图执行,但单步成本因上下文长度和模型不同可能相差百倍,无法有效控制美元支出。作者提出在 API 网关层实施美元/分钟限额,并踩过两个坑:一是限额必须“锁存”,触发后在设定时间内持续拒绝请求(返回 429 和 Retry-After),避免窗口重置后循环重启;二是必须为每次图调用分配唯一 run ID,实现按运行隔离的限额,防止一个失控运行拖垮所有用户。在 LangChain 落地时,还需捕获 `openai.RateLimitError` 并优雅返回“stopped: spend limit”,同时可结合运行总预算实现双重控制。

当你的LangGraph代理陷入循环,recursion_limit只能按步数叫停,但一步的代价可能天差地别。一位开发者在Reddit上分享了他的解决方案:用网关上的美元/分钟限额来兜底,并解决了两个实际部署中的陷阱。

reddit source: recursion_limit counts steps, not dollars. How I cap what a looping LangGraph agent can spend per mi

为什么recursion_limit不够用

recursion_limit会在图执行N步后停止,这听起来像是安全网。问题在于,步数的成本并不均匀。原文指出:在前沿模型上,一次带有100k token上下文的调用,成本可能超过五十次小调用。因此,同样是25步,账单可能是0.50美元,也可能是50美元。

这意味着,如果你只依赖步数限制,就无法回答“这个代理每分钟花了多少钱”这个关键问题。对于生产环境,尤其是面向多用户的代理应用,成本失控的风险是实实在在的。

什么是 recursion_limit?
在 LangGraph 中,图由节点和边构成,执行时按边逐步推进。recursion_limit 是图执行器的一个配置参数,用于限制从起始节点到终止节点之间允许的最大步数(即节点执行次数)。当执行步数达到该限制时,LangGraph 会抛出 GraphRecursionError,从而强制终止图运行,防止无限循环。默认值通常为 25。

这个参数的设计初衷是防止图陷入死循环,而非控制成本。它只关心“走了多少步”,完全不感知每一步实际消耗了多少 token 或美元。因此,对于成本敏感的生产场景,recursion_limit 只能作为兜底的安全网,不能替代真正的预算控制机制。

网关上的美元/分钟限额

思路很直接:在API网关层实施按分钟计算的美元限额。每次LLM调用都经过网关,网关累计成本,当某次调用导致成本超过阈值(例如X美元/分钟),就拒绝后续请求。

但这位开发者踩过两个坑,并给出了应对方案。

陷阱一:限额必须“锁存”

第一个问题是窗口重置。如果你只是简单地在每分钟开始时重置计数器,那么代理在超限被阻止后,等一分钟窗口刷新,下一次调用又会放行,循环继续。

所以限额必须锁存:一旦触发,在设定时间内(默认5分钟)所有调用都被拒绝,即使速率已经降下来。网关应返回429状态码,并带上Retry-After头,告诉客户端何时可以重试。

陷阱二:一个密钥,多个图

第二个问题更隐蔽。如果所有图调用都共用同一个API密钥,那么密钥级别的限额触发会停掉所有用户的运行,而不仅仅是那个陷入循环的。

解决方案是给每次调用一个独立的窗口。原文给出了代码示例:

run = {"X-Cost-Run-ID": str(uuid.uuid4())}
llm = ChatOpenAI(model="gpt-4o", base_url=GATEWAY_URL, api_key=KEY, default_headers=run)

这样,每次图调用都使用唯一的run ID,网关可以按run ID分别计算美元/分钟, runaway的调用被停止,其他调用继续运行。

什么是 API 网关?
API 网关是位于客户端和后端服务之间的中间层,负责请求路由、认证、限流、监控等功能。在 LLM 应用中,网关通常被用来统一代理对 OpenAI、Anthropic 等模型提供商的调用,从而集中实施访问控制、成本追踪和速率限制。

将美元/分钟限额实现在网关层,而不是在应用代码中,有几个好处:一是所有调用(无论来自哪个图、哪个用户)都经过同一入口,便于统一计量;二是网关可以独立于应用逻辑进行策略调整,无需修改代码;三是网关天然具备熔断和拒绝请求的能力。常见的开源 LLM 网关包括 LiteLLM、Portkey 等,它们提供了成本累计和预算限制的内置支持。

什么是 Retry-After 头?
Retry-After 是 HTTP 协议中的一个响应头,用于在服务器返回 429(Too Many Requests)或 503(Service Unavailable)时,告知客户端建议等待多长时间后再重试。它的值可以是一个秒数(如 Retry-After: 300 表示 5 分钟后重试),也可以是一个 HTTP 日期时间戳。

在本文的网关方案中,当美元/分钟限额被触发并锁存时,网关返回 429 状态码并附带 Retry-After 头,客户端(即 LangChain 的 ChatOpenAI)可以据此决定是否重试以及等待多久。然而,由于 ChatOpenAI 默认会对 429 进行有限次重试,开发者仍需在节点内捕获 RateLimitError 并做优雅降级,避免无意义的重试消耗。

LangChain的两个实现细节

在LangChain/LangGraph中落地这个方案,有两点需要注意。

首先,429错误会以openai.RateLimitError的形式抛出。ChatOpenAI默认会对429重试两次,但当熔断持续数分钟时,重试毫无意义。你应该在节点内捕获这个错误,返回一个优雅的“stopped: spend limit”回答,而不是让异常杀死整个图。

其次,速率限制和总额限制是两回事。如果你还想控制“这次运行总共最多花1美元”,需要同时发送运行预算(X-Cost-Run-Budget)。两者结合使用:每分钟限额防止突发失控,总预算防止长期累积超支。

你的方案是什么?

原文作者最后抛出了问题:你现在用什么?是基于token计数的回调、网关,还是仅仅依赖recursion_limit然后祈祷?

这个问题值得每个部署代理的开发者思考。成本控制不是可选项,尤其是当代理可能自主循环时。从社区讨论看,网关方案正在成为共识,但具体实现中的细节——锁存、run隔离、错误处理——往往决定成败。

如果你正在使用LangGraph构建生产应用,不妨检查一下:你的代理失控时,账单会失控吗?

#LangGraph#recursion_limit#成本控制#API网关#LangChain#美元限额#代理循环
分享:

相关推荐