Repository navigation
fix(proxy,quota): 解耦客观物理配额耗尽与自适应熔断,增加单请求重试账号排除防反复锤击 - #3845
Conversation
1. 客观物理配额与投机熔断解耦: - 修复 mark_rate_limited_async_with_mode 与 fast 将 !config.enabled 置顶导致客观零配额/到期时间戳信号被丢弃的缺陷; - 无论自适应熔断开关是否启用,一旦上游返回 429 配额耗尽,坚定将目标模型及基名配额标记为 0% 并锁定到期时间; - 熔断关闭时 is_rate_limited 正确路由至 is_quota_exhausted_or_explicit_limited。 2. 重试循环账号排除与强制轮换: - 将 TokenManager::get_token_filtered 暴露为 pub,支持 excluded_accounts; - 在 claude、gemini、openai 请求处理器的重试循环中引入 attempted_accounts,发生账号级故障换号时排除当前请求已尝试账号,消除 10 次重试反复锤击同一耗尽账号的调度失忆症。 3. 前端配置对齐与单测覆盖: - 修正 Settings.tsx 中 circuit_breaker 初始状态漂移; - 补充 test_hard_quota_depleted_locks_and_rotates_even_with_circuit_breaker_disabled 与 test_get_token_filtered_excludes_attempted_accounts_in_retry_loop 端到端测试。 Ref lbjlaq#3838, lbjlaq#3840 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
已合并 ✅感谢 @cubelikeplayDaniel 的这份修复。分析得很扎实,尤其是把「物理配额事实」和「自适应熔断」这两个不同性质的信号区分开的思路,是本轮最有价值的架构洞察。 已 squash 合并进 这份 PR 补上了我们遗漏的三个真实缺陷我们上一轮(
结论:不重复,是互补。 我们解决了"识别",PR 解决了"生效"。 合并前的兼容性验证因为两边改动了同一批文件( 1. 代码合并 无真实冲突,只是上下文行号漂移。 2. 判据复用关系 本 PR 直接复用了我们新增的 3. 四条关键路径的等价逻辑推演(全部 PASS)
其中第三条是我们最关心的:非配额耗尽场景在熔断关闭时行为完全不变,没有引入回归。 4. 构建门禁
一处值得肯定的实现细节
let base_name = strip_tier_suffix(&m_lower);
if base_name != m_lower {
token.model_quotas.insert(base_name, 0);
}我们的原版只置了三处 key(norm / 原名 / lower),你这个补充让 再次感谢。这类"客观事实 vs 自适应策略"的解耦思路,后续如果在其他信号上还有类似问题,欢迎继续提。 |
…3845, Ref #3838, #3840) 发布 beta.5,包含 PR #3845 的熔断解耦与重试去重修复。 - 版本号同步至 5.0.3-beta.5(package.json / Cargo.toml / Cargo.lock / tauri.conf.json / Cask / Settings.tsx) - CHANGELOG.md 与 CHANGELOG_EN.md 补充本次更新: - 解耦客观物理配额耗尽与自适应熔断(PR #3845, Thanks to @cubelikeplayDaniel) - 重试循环引入 attempted_accounts 排除机制,杜绝反复锤击同一耗尽账号 - 熔断关闭时不再旁路实时 429 锁定 - 修正前端 circuit_breaker 默认值为 enabled: true - beta.4 的 Release 因 GitHub API 瞬时 500 失败(publish-release 步骤,其余构建作业均成功), 已删除悬空 tag,直接以 beta.5 交付 Co-Authored-By: JeikCode <code@jeikcode.top>
发布 v5.0.3 正式版,将 5.0.3 预发布线(beta.0 ~ beta.5)转正,功能与 beta 线完全一致。 - 配额归因治理:网关内部限流判据收敛至 policy::is_internal_gateway_exhaustion 单一权威源(根除占全量 5xx 92.6% 的 503 归因污染),新增 403 封禁专用归因 account_forbidden (Fixes #3838) - 配额耗尽与自适应熔断彻底解耦,重试循环引入 attempted_accounts 账号排除,配额过滤前置于优先级收敛以消除 0 配额高优账号饥饿全池死锁 (Fixes #3841, PR #3845) - 蹬额度模式重构为最高调度优先级竞争,保住 Prompt Cache 并自动外溢并发流量 (PR #3840) - 超时契约解除 135s/160s 硬截断,首字 180s / 多模态 270s / 全局预算统一下沉 pipeline;Codex / Legacy 流式收敛至统一生命周期 (Fixes #3816, #3821) - 自定义映射确立最高优先级、旧版模型硬下线与先映射后判定、号池自适应暗路由与 X-Upstream-Model 统一收敛 (Fixes #2065, Ref #3764) - 多模态坏图 400 就地自愈、保鲜滑窗默认开启、Responses 日志脱敏与视觉 Token 估算 (Fixes #3758, #3824, #3756) - 提示词清洗分轨保护用户正文、CTF 策略剥离与 Gzip 入站解压 (Fixes #3764, #3781) - GCP Vertex AI 企业账号、IDE 2.6.0+ 三级双写切号、账号删除一致性、Web/Docker OAuth 与更新设置修复 (Fixes #3823, #3829, #3806, #3802, #3754) - Linux 远程桌面保活、macOS Mini 视窗主线程调度、界面缩放;签名缓存写入放大治理与 Token 双视角 + TTFT 统计 (Fixes #3752, #3757, #3744, PR #3765, Fixes #3810) - 回填 beta 分支缺失的 v5.0.0 正式版日志条目,保持 main 与 beta 内容一致 Co-Authored-By: JeikCode <code@jeikcode.top>
lbjlaq#3845, lbjlaq#3852) 1. [P0-1 Weekly 硬配额优先契约] - 在 `ProxyToken` 引入 `model_quota_windows: HashMap<String, Vec<QuotaWindowStatus>>`,保存各配额桶详细窗口快照; - 重构 `is_model_quota_depleted_expired`:当账号周硬配额耗尽 (0%) 且未越过周重置时,严禁仅因短期 5h 重置误触发自愈; 2. [P0-2 模型族重置时钟隔离] - 提取 `resolve_model_reset_time` 与 `resolve_model_group_key`,按 Argon / 3P / Gemini 独立维护重置时间映射; - 当模型族映射非空时,未匹配模型严禁回退至全局最早重置时间,根除跨模型重置时间污染; 3. [P1-1 ~ P1-3 状态同步与多桶收敛] - 修正 `sync_zero_quota_circuit_breaker` 中的 `all_buckets_positive`,缺失剩余比例时不默认判为正额度; - `load_single_account` 与 `fetch_and_lock_with_realtime_quota` 全状态同步 `model_reset_times`、`model_weekly_reset_times` 与 `model_quota_windows`; - 多桶同窗口取最早截止时间戳 (`.min(ts)`),动态从 `quota.models` 提取模型名称; 4. [P1-4 准入资格与权重排序解耦] - 拆分 `has_candidate_quota_admission`(布尔准入资格)与 `get_candidate_quota`(排序权重计算); - 自愈到期账号赋予保守正向 sentinel (20),参与调度竞争的同时绝不抢占健康高配额 (80%-100%) 账号; 5. [P1-5 & P1-6 精准解限与确定性单测] - 账号重载与正配额快照恢复时调用 `reconcile_all_quota_recovery_for_account`,仅清除 `QuotaExhausted` 并保留 429 `RateLimitExceeded`; - 增加 `test_weekly_hard_quota_depleted_blocks_self_healing_until_weekly_reset`、`test_model_family_isolation_prevents_argon_reset_contaminating_gemini` 等全套确定性单元测试。 Co-Authored-By: @jeikl <jeikl@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lbjlaq#3840, lbjlaq#3845, lbjlaq#3852, lbjlaq#3854, lbjlaq#3857) 发布 v5.0.4-beta.1,包含: - 分级配额契约与自愈架构重构:落实周硬配额绝对优先契约、模型族隔离与安全自愈 (PR lbjlaq#3852, Fixes lbjlaq#3840, lbjlaq#3845) - Docker 容器健康检查超时修复与 Tini Init 进程泄漏治理 (PR lbjlaq#3854, Fixes lbjlaq#3850) - 上传带宽语义与预算对齐,根治大请求体 15s 误杀 (PR lbjlaq#3857, Ref lbjlaq#3763, Ref lbjlaq#3821) - 账号封禁识别与无配额门禁卡死修复 (commit 03c83b1, Ref lbjlaq#3525, lbjlaq#3630, lbjlaq#3631, lbjlaq#3840) - 版本号全量同步至 5.0.4-beta.1(package.json / package-lock.json / Cargo.toml / Cargo.lock / tauri.conf.json / Cask / Settings.tsx) Co-Authored-By: @jeikl <jeikl@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lbjlaq#3840, lbjlaq#3845, lbjlaq#3852, lbjlaq#3854, lbjlaq#3857, lbjlaq#3864, lbjlaq#3866) 发布 v5.0.4-beta.1,包含: - 分级配额契约与自愈架构重构:落实周硬配额绝对优先契约、模型族隔离与安全自愈 (PR lbjlaq#3852, Fixes lbjlaq#3840, lbjlaq#3845) - Docker 容器健康检查超时修复与 Tini Init 进程泄漏治理 (PR lbjlaq#3854, Fixes lbjlaq#3850) - 上传带宽语义与预算对齐,根治大请求体 15s 误杀 (PR lbjlaq#3857, Ref lbjlaq#3763, Ref lbjlaq#3821) - 账号封禁识别与无配额门禁卡死修复 (commit 03c83b1, Ref lbjlaq#3525, lbjlaq#3630, lbjlaq#3631, lbjlaq#3840) - 文本内嵌 Data URL 视觉自愈与超大 Token 计费治理 (PR lbjlaq#3864, Ref lbjlaq#3830) - Responses 协议工具调用与回执相邻性对齐:根治 Claude 400 Prefill 报错 (PR lbjlaq#3866) - 版本号全量同步至 5.0.4-beta.1(package.json / package-lock.json / Cargo.toml / Cargo.lock / tauri.conf.json / Cask / Settings.tsx) Co-Authored-By: @jeikl <jeikl@users.noreply.github.com> Co-Authored-By: @mclinyeshi <mclinyeshi@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lbjlaq#3845, lbjlaq#3852) 1. [P0-1 Weekly 硬配额优先契约] - 在 `ProxyToken` 引入 `model_quota_windows: HashMap<String, Vec<QuotaWindowStatus>>`,保存各配额桶详细窗口快照; - 重构 `is_model_quota_depleted_expired`:当账号周硬配额耗尽 (0%) 且未越过周重置时,严禁仅因短期 5h 重置误触发自愈; 2. [P0-2 模型族重置时钟隔离] - 提取 `resolve_model_reset_time` 与 `resolve_model_group_key`,按 Argon / 3P / Gemini 独立维护重置时间映射; - 当模型族映射非空时,未匹配模型严禁回退至全局最早重置时间,根除跨模型重置时间污染; 3. [P1-1 ~ P1-3 状态同步与多桶收敛] - 修正 `sync_zero_quota_circuit_breaker` 中的 `all_buckets_positive`,缺失剩余比例时不默认判为正额度; - `load_single_account` 与 `fetch_and_lock_with_realtime_quota` 全状态同步 `model_reset_times`、`model_weekly_reset_times` 与 `model_quota_windows`; - 多桶同窗口取最早截止时间戳 (`.min(ts)`),动态从 `quota.models` 提取模型名称; 4. [P1-4 准入资格与权重排序解耦] - 拆分 `has_candidate_quota_admission`(布尔准入资格)与 `get_candidate_quota`(排序权重计算); - 自愈到期账号赋予保守正向 sentinel (20),参与调度竞争的同时绝不抢占健康高配额 (80%-100%) 账号; 5. [P1-5 & P1-6 精准解限与确定性单测] - 账号重载与正配额快照恢复时调用 `reconcile_all_quota_recovery_for_account`,仅清除 `QuotaExhausted` 并保留 429 `RateLimitExceeded`; - 增加 `test_weekly_hard_quota_depleted_blocks_self_healing_until_weekly_reset`、`test_model_family_isolation_prevents_argon_reset_contaminating_gemini` 等全套确定性单元测试。 Co-Authored-By: @jeikl <jeikl@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lbjlaq#3840, lbjlaq#3845, lbjlaq#3852, lbjlaq#3854, lbjlaq#3857, lbjlaq#3864, lbjlaq#3866, lbjlaq#3867) 发布 v5.0.4-beta.1,包含: - 分级配额契约与自愈架构重构:落实周硬配额绝对优先契约、模型族隔离与安全自愈 (PR lbjlaq#3852, Fixes lbjlaq#3840, lbjlaq#3845) - Docker 容器健康检查超时修复与 Tini Init 进程泄漏治理 (PR lbjlaq#3854, Fixes lbjlaq#3850) - 上传带宽语义与预算对齐,根治大请求体 15s 误杀 (PR lbjlaq#3857, Ref lbjlaq#3763, Ref lbjlaq#3821) - 账号封禁识别与无配额门禁卡死修复 (commit 03c83b1, Ref lbjlaq#3525, lbjlaq#3630, lbjlaq#3631, lbjlaq#3840) - 文本内嵌 Data URL 视觉自愈与超大 Token 计费治理 (PR lbjlaq#3864, Ref lbjlaq#3830) - Responses 协议工具调用与回执相邻性对齐:根治 Claude 400 与 Gemini 缓存跳变 (PR lbjlaq#3866, PR lbjlaq#3867) - 版本号全量同步至 5.0.4-beta.1(package.json / package-lock.json / Cargo.toml / Cargo.lock / tauri.conf.json / Cask / Settings.tsx) Co-Authored-By: @jeikl <jeikl@users.noreply.github.com> Co-Authored-By: @mclinyeshi <mclinyeshi@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
变更类型
基于的分支与版本号
基于
origin/beta(v5.0.3-beta.2,HEAD39ef36cf)当前问题、对用户使用体验会产生什么问题或影响
在实测与高频使用场景中,用户报告「一直在 429,网关的熔断为什么没生效?连续 10 次重试都打在同一个已耗尽账号上」:
Settings.tsx中的初始默认值circuit_breaker: { enabled: false }会将熔断器持久化关闭。在token_manager.rs的mark_rate_limited_async_with_mode与mark_rate_limited_fast中,代码直接将if !config.enabled { return; }置顶于函数入口,位于is_quota_depleted_signal、mark_model_quota_depleted与fetch_and_lock_with_realtime_quota之前。导致当 Google 上游返回明确的客观硬配额耗尽信号(Individual quota reached/QUOTA_EXHAUSTED/ 携带重置时间戳)时,该信号被彻底丢弃,账号在内存中的配额从未被置为 0%,也未写入限流锁定,调度器完全感知不到该账号已耗尽;claude.rs、gemini.rs、openai.rs处理器的多轮重试循环中,虽然 429 会设置force_rotate = true,但调用的是无参数的token_manager.get_token(...),内部传入空的excluded_accounts = HashSet::new()。P2C 算法在随后所有重试轮次中无从知晓该账号刚刚发生过 429 报错,加之耗尽账号的历史周配额或基础权重可能高于其他备用账号,导致 10 次重试连续全部击中同一个耗尽账号;is_rate_limited在!config.enabled时只读取周配额硬快照,旁路了记录于limits中的实时 429 到期时间戳锁定。解决方案
is_quota_depleted_signal与mark_model_quota_depleted提取到熔断开关检查之前;确立不变量:“账号已被上游告知配额耗尽是客观物理事实,绝不受自适应熔断退避开关的影响”;strip_tier_suffix)配额标记为 0%,并精确锁定到期时间;rate_limit.rs引入is_quota_exhausted_or_explicit_limited,确保即使熔断器关闭,调度器依然拦截处于QuotaExhausted状态的账号。attempted_accounts排除机制:TokenManager::get_token_filtered提升为pub;claude.rs、gemini.rs、openai.rs重试循环中跟踪当前请求已尝试失败的账号集(attempted_accounts),并在触发账号级故障换号(should_rotate_account == true)时强制排除,确保重试时平滑且确定性地轮换到池中下一个健康账号;全池遍历后立即快速失败,避免漫长无效空转。Settings.tsx中的初始默认状态为circuit_breaker: { enabled: true, backoff_steps: [60, 300, 1800, 7200] },杜绝保存配置导致熔断器意外关闭。test_hard_quota_depleted_locks_and_rotates_even_with_circuit_breaker_disabled单测,覆盖熔断器关闭时上游 429 配额耗尽的准确锁定与轮换逃逸(Red-Green 闭环验证);test_get_token_filtered_excludes_attempted_accounts_in_retry_loop单测,验证多轮重试排除机制。提交清单(组内独立、可单独回退)
538d2255每项提交引起的变化和影响面
自检(与 CI 相同的命令)
cd src-tauri && cargo fmt -- --checkcd src-tauri && cargo clippy --all-targets --all-featuresnpm run buildcargo test test_hard_quota_depleted通过 (1 passed)cargo test test_get_token_filtered_excludes通过 (1 passed)署名
cubelikeplayDaniel
Ref #3838, #3840
🤖 Generated with Claude Code