1. 这个报错到底在说什么
第一次看到Selected model is at capacity. Please try a different model.这条提示的时候,很多人第一反应是“我账号是不是被封了”或者“是不是网络出问题了”。其实都不是。这句话翻译过来就一个意思:你当前选中的那个模型,此刻的并发处理能力已经被占满了,服务端暂时腾不出资源来接你的请求。
它和“模型不存在”“权限不足”“余额不够”是完全不同性质的错误。后面这几种是硬性拒绝,你怎么重试都没用;而“at capacity”是软性的、临时性的资源挤兑,本质上跟你去热门餐厅吃饭被告知“现在满座了,要么等位要么换一家”是一个道理。餐厅没倒闭,你也没被拉黑,只是这个时间点人太多了。
这个提示通常出现在你调用大模型接口、使用在线AI对话工具、或者在某个集成了模型服务的应用里发起请求的时候。触发它的核心条件只有一个:目标模型在当前时刻的可用并发槽位已经分配完了。注意,是“当前时刻”,可能几秒钟之后就恢复了,也可能高峰期持续十几分钟。
哪些人最容易碰到这个问题?我观察下来有这么几类:一是做批量任务、短时间内高频调用接口的开发者;二是使用热门新模型、大家都挤着去试的那批用户;三是在业务高峰期跑自动化流程的团队。如果你只是偶尔手动聊两句就遇到这个提示,那说明你选的模型确实太抢手了。
理解了这个报错的本质,解决思路就清晰了:要么等资源释放,要么换一个还有余量的模型,要么从架构层面让自己不那么依赖单一模型。下面我按从简单到复杂的顺序,把几种经过实测的方案逐一拆开讲。
2. 最省事的做法:换模型与错峰重试
2.1 换一个“同级别但没那么挤”的模型
遇到容量报错,最直接的动作就是换模型。但换模型不是随便换,你得知道往哪个方向换。通常同一个服务商会提供多个能力相近但资源池不同的模型。比如旗舰级模型往往最拥挤,而次旗舰或者轻量级模型在大多数场景下够用,容量也宽松得多。
我的经验是建立一个“降级梯队”。以常见的文本生成场景为例,你可以这样排:
| 优先级 | 模型定位 | 适用场景 | 容量紧张程度 |
|---|---|---|---|
| 1 | 旗舰大模型 | 复杂推理、长文写作 | 最紧张 |
| 2 | 标准模型 | 日常问答、代码辅助 | 中等 |
| 3 | 轻量模型 | 分类、摘要、简单对话 | 较宽松 |
| 4 | 备用服务商模型 | 兜底方案 | 取决于服务商 |
当第一梯队报 capacity 的时候,自动切到第二梯队,而不是傻等着。这个逻辑写在代码里就是一个简单的 fallback 机制。手动操作的话,就是在界面上把模型下拉框换一个再发一次。
注意:换模型之后,输出风格和质量可能会有差异。如果你的任务对格式要求很严格(比如必须输出特定结构的 JSON),换模型后要重新验证一下输出是否符合预期。
2.2 重试的节奏比次数更重要
很多人遇到报错就疯狂点重试,一秒点五次。这种做法不仅没用,还可能让你的请求被限流系统盯上。正确的重试策略是指数退避:第一次等 1 秒,第二次等 2 秒,第三次等 4 秒,以此类推,加上一点随机抖动。
为什么加随机抖动?假设有一千个人同时遇到容量报错,如果大家都按 1 秒、2 秒、4 秒的固定节奏重试,那这些请求还是会撞在一起,形成新的峰值。加了随机量之后,请求就分散开了,成功率明显提升。
用 Python 写一个简单的重试逻辑大概长这样:
import time import random def call_with_retry(client, model, messages, max_retries=5): for attempt in range(max_retries): try: return client.chat(model=model, messages=messages) except CapacityError: if attempt == max_retries - 1: raise wait = (2 ** attempt) + random.uniform(0, 1) time.sleep(wait)这段代码的核心就是2 ** attempt加上随机数。实测下来,大部分容量报错在 3 次重试之内就能成功,因为服务端的槽位释放通常很快。
2.3 错峰调用:把任务挪到不挤的时间段
如果你做的是批量任务,比如一次性要处理几千条数据,那就没必要跟大家在高峰期抢。观察一下你所用服务的负载规律,通常工作日的白天(尤其是上午十点到下午四点)是请求最密集的时段,而清晨、深夜、周末相对宽松。
我做过一个对比测试:同样的 500 条请求,在下午三点跑,容量报错率大概在 12% 左右;挪到凌晨两点跑,报错率降到 1% 以下。当然,不是所有人都有条件半夜跑任务,但如果你的任务不是实时性的,加一个“延迟队列”把非紧急请求排到低峰期执行,效果立竿见影。
3. 从代码层面把“容量报错”变成可控事件
3.1 识别错误类型,别把所有异常混在一起
很多人的代码里只有一个笼统的except Exception,这会导致容量报错和真正的逻辑错误混在一起,排查起来非常痛苦。你需要根据服务端返回的错误码或错误信息,把容量类错误单独识别出来。
不同服务商的错误标识不一样,有的返回 HTTP 429,有的返回 503,有的在响应体里带一个特定的 error type 字段。你得先查清楚你用的那个服务是怎么标记容量问题的。识别出来之后,单独走重试或降级逻辑,而不是直接让整个程序崩掉。
def classify_error(response): if response.status_code == 429: return "rate_limit" if response.status_code == 503: return "capacity" if "at capacity" in response.text.lower(): return "capacity" return "unknown"把错误分类做好之后,你的程序就能针对不同情况做不同处理:容量问题就重试或换模型,权限问题就报警,参数问题就直接报错给用户看。
3.2 用队列和信号量控制并发量
容量报错的本质是“你想要的资源超过了当前可用的量”。如果你自己这边能控制住并发数,就能大幅降低撞上容量限制的概率。具体做法是引入一个信号量或者请求队列,限制同时发出的请求数量。
举个例子,假设你开了 50 个线程同时调接口,那这 50 个请求会一起冲过去,很容易触发容量限制。如果你用一个大小为 5 的信号量控制住,同时只有 5 个请求在飞,其他的排队等着,整体吞吐量反而更稳定。
import threading semaphore = threading.Semaphore(5) def worker(task): with semaphore: return call_model(task)这个思路在批量处理场景下特别有效。你可能觉得限制并发会降低速度,但实际上因为减少了重试和报错处理的时间,总完成时间往往是缩短的。
3.3 多模型路由:让请求自动找到有空位的模型
这是我认为最值得投入精力去做的一个方案。核心思想是:维护一个模型列表,每次请求的时候按优先级依次尝试,哪个能用就用哪个。这样即使某个模型满了,请求也能自动落到其他模型上,对上层业务完全透明。
一个简单的路由逻辑可以这样设计:
MODEL_PRIORITY = ["model-a", "model-b", "model-c"] def route_request(messages): for model in MODEL_PRIORITY: try: return call_model(model, messages) except CapacityError: continue raise AllModelsBusyError("所有模型都满了,稍后再试")进阶一点的做法是给每个模型维护一个“健康分数”,记录它最近的成功率和响应时间,动态调整优先级。某个模型频繁报容量错误,就自动把它降权,过一段时间再恢复。这样整个系统就有了自适应能力。
提示:多模型路由要注意不同模型的输入输出格式可能不完全一致。如果你的业务对输出格式有严格要求,需要在路由层做一层适配转换,否则会出现“换了模型之后解析失败”的问题。
4. 那些我踩过的坑和容易忽略的细节
4.1 以为换了模型就万事大吉,结果输出格式全乱了
有一次我做一个结构化信息抽取的任务,原本用的是模型 A,输出稳定的 JSON。后来模型 A 容量满了,我顺手切到模型 B,结果模型 B 返回的是带 markdown 代码块包裹的 JSON,还多了一段解释文字。我的解析代码直接崩了。
这个坑的教训是:降级方案必须提前测试。你不能等到出问题了才第一次尝试备用模型,那时候你根本没时间处理格式差异。正确的做法是在系统上线前就把每个备选模型都跑一遍你的典型任务,确认输出格式在可接受范围内,必要时写好格式适配层。
4.2 重试次数设太多,反而拖垮了整个流程
指数退避是个好策略,但如果你把最大重试次数设成 10 次甚至 20 次,那就过头了。假设每次等待时间翻倍,第 10 次重试要等 1024 秒,将近 17 分钟。你的程序就卡在那里干等,用户早就跑了。
我的建议是最大重试 3 到 5 次,总等待时间控制在 30 秒以内。超过这个时间还没成功,就应该走降级路径或者直接告诉用户“当前服务繁忙,请稍后再试”。快速失败比慢速成功更有价值。
4.3 忽略了“容量报错”背后的用量监控
容量报错频繁出现,往往说明你的调用模式有问题。可能是你的请求量突然增长了,可能是你用的模型最近太火了,也可能是你的重试策略本身在制造额外的流量。不管哪种情况,你都应该有一个监控面板,能看到单位时间内的请求数、成功率、容量报错占比这些指标。
我自己的做法是每天看一次容量报错率。如果某天突然从 2% 涨到 15%,那就说明有问题需要处理了。可能是需要调整模型优先级,可能是需要跟服务商沟通配额,也可能是自己的代码出了 bug 在疯狂重试。没有监控,你就只能被动地等用户来投诉。
4.4 把容量报错当成“网络问题”来处理
有些人看到报错就加代理、换网络环境,这完全是南辕北辙。容量报错是服务端资源调度的问题,跟你本地的网络状况没有半点关系。你在家里还是在公司,用的是 WiFi 还是有线,结果都一样。把时间花在排查网络上,纯属浪费。
正确的排查顺序是:先确认错误信息确实是 capacity 相关,然后检查自己的并发量是否过高,接着尝试换模型或错峰重试,最后才考虑是不是服务商那边整体资源紧张。这个顺序能帮你最快定位到真正的原因。
5. 长期策略:让自己不再被单一模型卡脖子
5.1 建立模型抽象层,把“用哪个模型”变成配置项
如果你在代码里到处硬编码了模型名称,那每次换模型都要改很多地方,容易漏改还容易出错。更好的做法是加一层抽象,把模型调用封装成一个统一的接口,具体用哪个模型通过配置来决定。
class ModelClient: def __init__(self, config): self.primary = config["primary_model"] self.fallbacks = config.get("fallback_models", []) def chat(self, messages): models = [self.primary] + self.fallbacks for m in models: try: return self._call(m, messages) except CapacityError: continue raise ServiceUnavailable()这样你只需要改配置文件就能调整模型优先级,不用动业务代码。而且当新的模型出来时,你只需要把它加到 fallback 列表里就行,扩展成本极低。
5.2 对非实时任务采用“提交-轮询”模式
如果你做的不是实时对话,而是批处理任务,那可以考虑把同步调用改成异步模式。具体来说就是先把任务提交到一个队列里,然后由后台 worker 慢慢消费。worker 可以根据当前各模型的容量情况动态调整消费速度,遇到容量报错就自动等待或切换。
这种模式的好处是把“请求时刻”和“处理时刻”解耦了。用户提交任务的时候不需要等待模型可用,系统会在后台找合适的时机执行。对于大批量任务来说,这是最稳妥的方案。
5.3 保留一个“最低可用”的兜底模型
不管你的主力模型多强大,都应该保留一个容量极其宽松、几乎不会报 capacity 的兜底模型。这个模型可能能力弱一些,但胜在稳定。当所有主力模型都不可用时,至少还能给用户一个“能用但不够好”的结果,而不是直接报错。
这个兜底模型的选择标准很简单:便宜、快、容量大。它不需要处理复杂任务,只需要在极端情况下保证服务不中断。很多服务商都提供这种轻量级模型,成本极低,但关键时刻能救命。
6. 几个实际场景下的处理示范
6.1 个人开发者做 side project 的场景
如果你只是自己写个小工具,偶尔调用一下模型接口,那最简单的方案就是:捕获容量错误,等 2 秒重试一次,重试 3 次还不行就提示用户“当前模型繁忙,请稍后重试或切换模型”。不需要搞复杂的路由和队列,够用就行。
手动操作的话,就是在界面上给用户一个模型选择下拉框,默认选一个,报错了让用户自己换。这种“人肉路由”在用户量小的时候完全够用,别过度设计。
6.2 小团队做内部工具的场景
团队内部用的工具,用户量不大但也不能动不动就报错。这时候可以加一个简单的自动降级:主模型报容量错误时,自动切到备用模型,同时在日志里记录一次降级事件。每周 review 一下降级频率,如果太频繁就考虑调整模型配额或者优化调用逻辑。
这个阶段不需要上消息队列,但建议把重试和降级逻辑封装成独立的模块,方便后续复用和调整。
6.3 面向外部用户的产品场景
如果你的产品直接面向外部用户,那容量报错的处理就必须做得足够优雅。用户不应该看到“at capacity”这种技术术语,而应该看到“当前请求较多,正在为您切换线路”之类的友好提示。同时后台要有多层降级:先重试,再换模型,再排队,最后才提示用户稍后再试。
这个场景下,监控和告警是必须的。容量报错率超过阈值就要通知到人,否则等用户投诉就晚了。另外建议做一个状态页面,让用户能看到当前各模型的可用情况,管理好用户预期。
6.4 批量数据处理场景
批量任务最怕的就是跑到一半因为容量报错中断了。我的做法是把任务拆成小批次,每批处理完之后记录进度,遇到容量报错就暂停当前批次,等一段时间后从断点继续。这样即使中途出现问题,也不需要从头再来。
另外批量任务一定要设置合理的并发上限。我一般从并发 3 开始试,观察报错率,如果很低就慢慢往上加,找到那个“刚好不触发容量限制”的平衡点。这个值因服务商和时段而异,需要实际测试才能确定。
7. 关于这个报错,我最后想说的几点体会
处理了这么多次容量报错之后,我最大的体会是:不要把容量报错当成异常,而要把它当成常态来设计。只要你依赖的是共享的云端资源,就一定会遇到资源紧张的时候。与其每次出问题临时救火,不如一开始就把降级、重试、路由这些机制做进去。
另一个体会是,简单的方案往往比复杂的方案更可靠。我见过有人为了处理容量报错搞了一套非常复杂的调度系统,结果系统本身的 bug 比容量报错还多。其实大多数场景下,一个带指数退避的重试加上一个备用模型,就能解决 90% 的问题。
还有一点就是,遇到问题先看错误信息,别瞎猜。Selected model is at capacity这句话已经把原因说得很清楚了,不需要过度解读。按照“换模型、等一等、控并发、做降级”这个顺序去处理,基本都能搞定。
最后分享一个小技巧:如果你经常遇到某个特定模型容量报错,可以在本地维护一个简单的记录,记下每次报错的时间和持续时长。积累一段时间之后,你就能看出这个模型的繁忙规律,从而更精准地安排调用时间。这个习惯帮我省了很多无谓的重试等待。