Skip to content

fix: limit: 0 的冷却改为递增退避,不再等 RetryDelay - #65

Closed
swdswd2401999-ops wants to merge 1 commit into
Mag1cFall:mainfrom
swdswd2401999-ops:fix/zero-limit-backoff
Closed

swdswd2401999-ops wants to merge 1 commit into
Mag1cFall:mainfrom
swdswd2401999-ops:fix/zero-limit-backoff

Conversation

@swdswd2401999-ops

Copy link
Copy Markdown

问题

zeroQuotaLimit() 已经把「所有额度项都报 limit: 0」的 429 标成 kind = "无配额",说明代码识别出这不是额度用完,而是账号没有该模型的访问权限。

但 until 仍然取自 RetryDelay:

if zeroQuotaLimit(rpcError.Message) {
    kind = "无配额"
}
escalate := false
if rpcError.RetryDelay > 0 {
    until = now.Add(rpcError.RetryDelay)   // ← 19 小时
}

于是每次都是:冷却 19 小时 → 到点重试 → 又是 limit: 0 → 再等 19 小时。

既然已经判定「不是额度问题」,就不该沿用「额度日重置」的时间。

实测依据

从生产日志里提取全部 9 条 retry in,加上报错时刻后:

报错时刻(UTC)         retry in              到期(UTC)
2026-10-18 12:06:33  11h53m26.446913736s   2026-10-19 00:00:00
2026-10-18 13:38:43  10h21m16.964939969s   2026-10-19 00:00:00
2026-10-19 00:46:43  23h13m16.562325023s   2026-10-20 00:00:00
2026-10-19 04:49:51  19h10m8.803526341s    2026-10-20 00:00:00
2026-10-19 04:49:56  19h10m3.100469026s    2026-10-20 00:00:00

9/9 条精确指向 UTC 零点(±10 秒),也就是说上游给的 retry in 本身就是「到下一个额度日」。

但次日同一模型同一账号仍然是 limit: 0:

gemini-4-argon  build  limit: 0  →  冷却到 00:00  →  到点重试  →  又是 limit: 0

因为这是权限问题,不是额度问题 —— 等多久都不会变。

改动

kind == "无配额" 时提前返回,Escalate: true 让 MarkQuotaCooldownIfGeneration 走已有的 planLimitCooldown 分支:

if kind == "无配额" {
    return QuotaCooldown{Until: now.Add(planLimitBackoff[0]), Global: global,
        Kind: kind, Reason: kind + ": " + err.Error(), Escalate: true}, true
}

planLimitBackoff 已经是 1m → 5m → 15m → 30m → 1h,planLimitCooldown 会读上一次的 Level 递增,不用新增任何逻辑。

取舍

改前 改后
冷却时长 19 小时 1 分钟起,最长 1 小时
权限若开通 最多 19 小时才发现 1 小时内发现
无效探测 每天 1 次 每小时最多 1 次

代价是权限长期不开通时每小时多一次失败探测。实测这类失败约 60ms(ProxyUnaryCall 直接被 429 拒绝),不占并发槽,也远小于「每天干等 19 小时」。

影响面

只改 无配额 分支。limit: 100 这类真额度用完仍按 RetryDelay 恢复(TestRealQuotaStillUsesRetryDelay 覆盖)。

limit 里有任一非零值时不判为无配额(TestMixedLimitIsNotZeroQuota),没有 limit 字段时也不误判(TestNoLimitTokenIsNotZeroQuota)。

测试

新增 internal/aistudio/quota_zerolimit_test.go,8 个用例:

TestZeroQuotaUsesBackoff           limit:0 → 1 分钟,不是 19 小时
TestRealQuotaStillUsesRetryDelay   limit:100 → 仍按 retry in
TestMixedLimitIsNotZeroQuota       limit 有非零值时不误判
TestNoLimitTokenIsNotZeroQuota     没有 limit 字段时不误判
TestBackoffEscalates               1m → 5m → 15m,封顶 1h
TestBackoffKeepsActiveCooldown     冷却未结束时不重置
TestZeroQuotaKeepsGlobalFlag       global 标记保留
TestNon429Ignored                  非 429 不产生冷却
$ go test ./internal/aistudio/ -run "ZeroQuota|Backoff|RealQuota"
ok  github.com/Mag1cFall/AIStudio2API/internal/aistudio

$ go vet ./internal/aistudio/
(无输出)

$ go test ./...
ok  internal/aistudio  ok  internal/api  ok  internal/app
ok  internal/camoufoxnative  ok  internal/chromeauth  ok  internal/config
ok  internal/proxydial  ok  internal/requestdb  ok  internal/setup

线上验证

在生产实例上换成自编译二进制后:

第 1 次 limit:0  →  runtime-state.json  cooldowns["build:gemini-4-argon"]
                    剩余 0.9 分钟   level=1
第 2 次 limit:0  →  剩余 4.7 分钟   level=2

改前同一字段是 剩余 12.6h level=None。

备注

关于 gemini-4-argon 在 Build 通道的 limit: 0,issue #59 里提到这是 Google 曾把前端配置错、让 free 用户也能用 Ultra 专属模型。这个改动不试图绕过权限限制 —— 只是让「已知无权限」的冷却时长与它的真实恢复方式匹配。

zeroQuotaLimit() 已把这类 429 标成「无配额」,说明代码识别出它不是
额度用完而是账号没有该模型的访问权限。但 until 仍取自 RetryDelay,
于是每次都是:冷却 19 小时 → 到点重试 → 又是 limit: 0 → 再等 19 小时。

实测依据:
- 9/9 条 retry in 精确指向 UTC 零点(如 04:49:51 + 19h10m8s = 次日 00:00:00)
- 次日同一模型同一账号仍是 limit: 0,等多久都不恢复
- 只有 4%(7/156)的 argon 请求受影响,因为系统会自动回退 playground

改为走已有的 planLimitBackoff(1m → 5m → 15m → 30m → 1h):
- 权限若真开通,一小时内就能发现,而不是等 19 小时
- 若没开通,代价是每小时一次失败探测(约 60ms,不占并发槽)
- 复用 planLimitCooldown 的 Level 递增逻辑,不改动其他分支

测试:internal/aistudio/quota_zerolimit_test.go 覆盖 8 种情形
(limit:0 / limit:100 / 混合 / 无 limit 字段 / 递增 / 封顶 / 冷却未过期 / global 标记)
@Mag1cFall Mag1cFall closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants