-
Notifications
You must be signed in to change notification settings - Fork 995
Description
当前现状
一、现状诊断
1.1 三类桶的行为差异
| 桶类型 | 额度格式 | 调用后立即扣减? | 可信度 | 可作为账本? |
|---|---|---|---|---|
| grok-3 | totalTokens/remainingTokens + low/high |
✅ 第 1 次就扣 | 🟢 高 | ✅ 是 |
| grok-4 | totalTokens/remainingTokens + low/high |
✅ 第 1 次就扣 | 🟢 高 | ✅ 是 |
| grok-4-1-thinking-1129 | totalQueries/remainingQueries |
❌ 连续 8 次仍 8/8 |
🔴 低 | ❌ 仅探针 |
| grok-420 | totalQueries/remainingQueries |
❌ 连续 8 次仍 8/8 |
🔴 低 | ❌ 仅探针 |
1.2 模型到真实扣费桶映射
graph LR
subgraph "grok-3 桶 (80 tokens)"
G3[grok-3]
G3T[grok-3-thinking]
G3M[grok-3-mini]
end
subgraph "grok-3 HIGH(图片/视频)"
GIF[grok-imagine-1.0-fast]
GI[grok-imagine-1.0]
GIV[grok-imagine-1.0-video]
end
subgraph "grok-4 桶 (40 tokens)"
G4[grok-4]
G4T[grok-4-thinking]
end
subgraph "混合扣费(同时扣 grok-3 + grok-4)"
G41F[grok-4.1-fast]
G41E[grok-4.1-expert]
end
subgraph "独立上游"
GIE[grok-imagine-1.0-edit]
end
subgraph "不可观测扣费"
G41M[grok-4.1-mini]
G41TH[grok-4.1-thinking]
G420[grok-4.20-beta]
end
GIF -->|"-4 tokens, -1 high"| G3
GI -->|"-4 tokens, -1 high"| G3
GIV -->|"-4 tokens, -1 high"| G3
G41F -->|"-1 token"| G3
G41F -->|"-1 token"| G4
G41E -->|"-4 tokens, -1 high"| G3
G41E -->|"-4 tokens, -1 high"| G4
GIE -->|"独立,无可观测桶"| GIE
1.3 每次调用的真实消耗量
| 模型 | 扣费桶 | remainingTokens | low | high |
|---|---|---|---|---|
| grok-3 | grok-3 | -1 | -1 | 0 |
| grok-3-thinking | grok-3 | -1 | -1 | 0 |
| grok-3-mini | grok-3 | -1 | -1 | -1 |
| grok-4 | grok-4 | -4 | -4 | -1 |
| grok-4-thinking | grok-4 | -4 | -4 | -1 |
| grok-4.1-fast | grok-3 + grok-4 | 各 -1 | 各 -1 | 0 |
| grok-4.1-expert | grok-3 + grok-4 | 各 -4 | 各 -4 | 各 -1 |
| grok-imagine-1.0 | grok-3 | -4 | -4 | -1 |
| grok-imagine-1.0-fast | grok-3 | -4 | -4 | -1 |
| grok-imagine-1.0-video | grok-3 | -4 | -4 | -1 |
| grok-imagine-1.0-edit | 独立 (imagine-image-edit) |
无可观测扣减 | - | - |
二、核心问题
❌ 问题 1:单一 quota 无法反映真实额度
当前所有模型共用一个 quota 字段,但实测显示 grok-3 有 80 tokens、grok-4 只有 40 tokens,且扣减速率完全不同。
❌ 问题 2:上游 429 无法区分来源
Caution
所有模型的 429 响应 完全相同:{"error":{"code":8,"message":"Too many requests","details":[]}},无 retry-after,无 x-ratelimit-* 头。无法靠响应体区分是 grok-3 打满、grok-4 打满、还是 4.1/420 限流。
❌ 问题 3:grok-4.1/420 查询桶不可信
grok-4-1-thinking-1129 和 grok-420 的 /rest/rate-limits 始终返回 8/8,但实际请求在第 9 次或首次就可能 429。
期望改进
分层优化方案
拆分桶结构
token_state:
grok3: # 真实账本
remaining_tokens: int # 总额度 80
low_remaining: int # lowEffort 剩余
high_remaining: int # highEffort 剩余
synced_at: datetime # 上次同步时间
grok4: # 真实账本
remaining_tokens: int # 总额度 40
low_remaining: int
high_remaining: int
synced_at: datetime
probes: # 仅探针,不作为扣费依据
grok41_queries: int # grok-4-1-thinking-1129 查询桶
grok420_queries: int # grok-420 查询桶
synced_at: datetime
model_cooldowns: # 模型级冷却计时器
"grok-4.1-mini": until_ts
"grok-4.1-thinking": until_ts
"grok-4.20-beta": until_ts
"grok-imagine-1.0-edit": until_ts # 独立上游,探测型冷却
请求前预检规则
┌─────────────────────────────────────────────────────┐
│ 收到请求 (model_alias) │
└────────────────────────┬────────────────────────────┘
▼
┌──── 模型分类 ────┐
│ │
┌─────────┴─┐ ┌────────┐ ┌┴──────────┐ ┌──────────┐
│ grok-3 系 │ │grok-4系│ │ 4.1混合扣费│ │ 不可观测 │
│ 3/3t/3m │ │ 4/4t │ │ fast/exp │ │mini/th/420│
└─────┬─────┘ └───┬────┘ └─────┬─────┘ └─────┬────┘
▼ ▼ ▼ ▼
检查 grok3 桶 检查 grok4 桶 检查 grok3 检查冷却
tokens>0? tokens>0? AND grok4 桶 until_ts 过期?
low>=1? low>=1? 都 >0? 最近成功过?
high>=1?
各模型具体预检阈值:
| 模型 | grok-3 tokens | grok-3 low | grok-3 high | grok-4 tokens | grok-4 low | grok-4 high | 冷却检查 |
|---|---|---|---|---|---|---|---|
| grok-3 | ≥1 | ≥1 | - | - | - | - | - |
| grok-3-thinking | ≥1 | ≥1 | - | - | - | - | - |
| grok-3-mini | ≥1 | ≥1 | ≥1 | - | - | - | - |
| grok-4 | - | - | - | ≥1 | ≥1 | ≥1 | - |
| grok-4-thinking | - | - | - | ≥1 | ≥1 | ≥1 | - |
| grok-4.1-fast | ≥1 | ≥1 | - | ≥1 | ≥1 | - | - |
| grok-4.1-expert | ≥4 | ≥4 | ≥1 | ≥4 | ≥4 | ≥1 | - |
| grok-4.1-mini | - | - | - | - | - | - | ✅ |
| grok-4.1-thinking | - | - | - | - | - | - | ✅ |
| grok-4.20-beta | - | - | - | - | - | - | ✅ |
成功后本地扣费
- grok-3 系:
tokens -= 1, low -= 1(grok-3-mini 额外high -= 1) - grok-4 系:
tokens -= 4, low -= 4, high -= 1 - grok-4.1-fast:
grok3.tokens -= 1, grok3.low -= 1+grok4.tokens -= 1, grok4.low -= 1 - grok-4.1-expert:
grok3.tokens -= 4, grok3.low -= 4, grok3.high -= 1+grok4.tokens -= 4, grok4.low -= 4, grok4.high -= 1 - grok-4.1-mini / thinking / 4.20-beta:❌ 不做本地扣费
429 处理策略
| 请求模型 | 429 时的动作 |
|---|---|
| grok-3 / 3-thinking / 3-mini | 标记 grok-3 桶冷却 |
| grok-4 / 4-thinking | 标记 grok-4 桶冷却 |
| grok-4.1-fast / expert | 优先做 token + model_alias 冷却;同时观察 grok-3/grok-4 桶变化 |
| grok-4.1-mini | 仅 token + "grok-4.1-mini" 冷却 |
| grok-4.1-thinking | 仅 token + "grok-4.1-thinking" 冷却 |
| grok-4.20-beta | 仅 token + "grok-4.20-beta" 冷却 |
Important
对 grok-4.1-mini/thinking/4.20-beta 的 429,绝不能做 token 全局失效或查询桶减一,只能做 token + model 维度的冷却。
冷却时长建议
| 场景 | 冷却时长 |
|---|---|
| grok-3/grok-4 桶打满 | 取「窗口剩余时间」,兜底 120 秒 |
| grok-4.1-mini 连续 429 | 60 秒短冷却,再试一次仍 429 则翻倍至 300 秒 |
| grok-4.1-thinking 首次 429 | 300 秒,较长冷却 |
| grok-4.20-beta 首次 429 | 300 秒,实测等 3 分钟仍 429 |
结构化日志
每次请求记录以下字段:
{
"ts": "2026-03-13T06:00:00Z",
"token_tail": "x0tea9SY",
"requested_model": "grok-4.1-fast",
"reported_model": "grok-3",
"status": 200,
"grok3_before": {"tokens": 60, "low": 60, "high": 15},
"grok3_after": {"tokens": 59, "low": 59, "high": 15},
"grok4_before": {"tokens": 21, "low": 21, "high": 5},
"grok4_after": {"tokens": 20, "low": 20, "high": 5},
"applied_cooldown_scope": null
}Token 选择算法改造
选 token 时:
1. 根据 requested_model 确定需要检查的桶类型
2. 过滤掉正在冷却的 token
3. 按桶剩余量排序,优先选余量最大的 token
4. 对于 grok-4.1-fast/expert,需要同时满足 grok-3 + grok-4 两个桶
低频校准任务
- 每 5 分钟 对活跃 token 调用
/rest/rate-limits同步 grok-3 / grok-4 真实桶 - 不要高频刷新 grok-4.1 / grok-420 查询桶(无实际意义)
- 同步后校正本地账本的漂移
持续观测
- 对 grok-4.1-mini 持续积累样本:是否存在隐性扣费桶?什么条件下恢复可用?
- 对 grok-4.20-beta 观测窗口重置周期
- 记录
reported_model与requested_model的偏差,为后续映射优化积累数据
预期收益
| 指标 | 优化前 | 优化后 |
|---|---|---|
| grok-3/4 额度利用率 | 因单一 quota 被错误标记不可用而浪费 | 分桶后精确到 ±1 token |
| grok-4.1 误判率 | 查询桶 8/8 误以为可用 → 429 |
冷却机制拦截,减少无效请求 |
| 429 后恢复速度 | 全局冷却,所有模型不可用 | 按桶/按模型冷却,互不影响 |
| 混合模型调度 | 不知道 fast/expert 同时消耗两个桶 | 预检同时卡两个桶,避免打空 |
Warning
- grok-4.1-mini 的「前 8 次成功,第 9 次 429」行为可能是隐性速率限制而非额度耗尽,冷却后可能恢复
- grok-4.1-fast 响应
reported_model=grok-3但同时扣 grok-4 桶,说明上游存在多步内部调用链,未来扣费行为可能变化 - 方案基于单次实测快照,上游策略随时可能调整,建议保留结构化日志持续验证
前端额度显示优化
现状问题
当前前端 (token.html + token.js) 的额度展示存在以下问题:
| 位置 | 现状 | 问题 |
|---|---|---|
| 统计区 | 只有一个 Chat 剩余 数字(所有 token 的 quota 求和) |
无法区分 grok-3 和 grok-4 的剩余量 |
| 表格列 | 只有一列 额度,显示单一 item.quota 数字 |
无法看出哪个桶已满、哪个桶还有余量 |
| 编辑弹窗 | 只有一个 额度 输入框 |
管理员无法分别调整不同桶的额度 |
| 后端模型 | TokenInfo.quota 只有一个 int 字段 |
所有桶压缩成一个数字 |
后端数据结构改造
TokenInfo 模型扩展
class BucketQuota(BaseModel):
"""单桶额度"""
remaining_tokens: int = 0
total_tokens: int = 0
low_remaining: int = 0
high_remaining: int = 0
class TokenInfo(BaseModel):
# ... 现有字段保留 ...
quota: int = BASIC__DEFAULT_QUOTA # 保留兼容,作为 grok-3 桶剩余
# 新增分桶字段
grok3_quota: Optional[BucketQuota] = None
grok4_quota: Optional[BucketQuota] = None
grok41_queries: Optional[int] = None # grok-4-1 查询桶(探针)
grok420_queries: Optional[int] = None # grok-420 查询桶(探针)
# 模型级冷却
model_cooldowns: Dict[str, int] = {} # model_alias -> until_tsAPI 响应示例
{
"token": "sso...",
"status": "active",
"quota": 49,
"grok3_quota": {
"remaining_tokens": 49,
"total_tokens": 80,
"low_remaining": 49,
"high_remaining": 12
},
"grok4_quota": {
"remaining_tokens": 9,
"total_tokens": 40,
"low_remaining": 9,
"high_remaining": 2
},
"grok41_queries": 8,
"grok420_queries": 8,
"model_cooldowns": {
"grok-4.1-mini": 1710312000000
}
}统计区改造
改为 3 行 × 4 列 = 12 卡片:
Row 1: Token 统计(保留不变)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Token 总数 │ │ Token 正常 │ │ Token 限流 │ │ Token 失效 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Row 2: Chat 桶额度
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Grok-3 剩余 │ │ Grok-4 剩余 │ │ Grok-4.1 │ │ 总调用次数 │
│ 980/1600 │ │ 180/800 │ │ 探针 8/8 │ │ 1,234 │
│ ██████░░░░ │ │ ██░░░░░░░░ │ │ ⚠️ 仅参考 │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Row 3: Image / Video / Edit
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Image 剩余 │ │ Video 剩余 │ │ Image Edit │ │ (空/预留) │
│ 240/320 │ │ 240/320 │ │ 探测型冷却 │ │ │
│ = G3 high汇总│ │ = G3 high汇总│ │ ⚠️ 独立上游 │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Important
- Image 剩余 = 所有 active token 的
grok3_quota.high_remaining汇总(因为grok-imagine-1.0/grok-imagine-1.0-fast走 grok-3 HIGH,每次-1 high) - Video 剩余 = 同 Image,因为
grok-imagine-1.0-video同样走 grok-3 HIGH - Image Edit =
imagine-image-edit为独立上游(model.py 中grok_model="imagine-image-edit"),无可观测的真实桶。需要像grok-4.1-mini一样做模型级探测型冷却
对应 HTML 改造 (token.html):
<!-- Row 2: Chat 桶额度 -->
<div class="stat-card">
<div class="stat-value" id="stat-grok3-quota">-</div>
<div class="stat-label">Grok-3 剩余</div>
<div class="quota-bar" id="stat-grok3-bar"></div>
</div>
<div class="stat-card">
<div class="stat-value text-blue-600" id="stat-grok4-quota">-</div>
<div class="stat-label">Grok-4 剩余</div>
<div class="quota-bar" id="stat-grok4-bar"></div>
</div>
<div class="stat-card">
<div class="stat-value text-amber-500" id="stat-grok41-quota">-</div>
<div class="stat-label">Grok-4.1 探针</div>
<div class="stat-hint">⚠️ 仅参考</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-total-calls">-</div>
<div class="stat-label">总调用次数</div>
</div>
<!-- Row 3: Image / Video / Edit -->
<div class="stat-card">
<div class="stat-value text-purple-600" id="stat-image-quota">-</div>
<div class="stat-label">Image 剩余</div>
<div class="stat-hint">= Grok-3 high 桶</div>
</div>
<div class="stat-card">
<div class="stat-value text-indigo-600" id="stat-video-quota">-</div>
<div class="stat-label">Video 剩余</div>
<div class="stat-hint">= Grok-3 high 桶</div>
</div>
<div class="stat-card">
<div class="stat-value text-orange-500" id="stat-edit-quota">-</div>
<div class="stat-label">Image Edit</div>
<div class="stat-hint">⚠️ 独立上游</div>
</div>
<div class="stat-card"></div>表格额度列改造
从单一数字改为多桶进度条:
│ Token │ 类型 │ 状态 │ 额度 │ 备注 │ 操作 │
│ sso...9SY │ ssoBasic │ active │ G3: █████░░░ 49/80 │ - │ 🔄 ✏️ 🗑️│
│ │ │ │ G4: ██░░░░░░ 9/40 │ │ │
│ sso...CIQ │ ssoBasic │ active │ G3: ███████░ 60/80 │ - │ 🔄 ✏️ 🗑️│
│ │ │ │ G4: ████░░░░ 20/40 │ │ │
对应 JS 改造 (renderTable 中 tdQuota 部分):
// Quota (多桶展示)
const tdQuota = document.createElement('td');
tdQuota.className = 'text-left text-xs';
if (item.grok3_quota || item.grok4_quota) {
let html = '<div class="quota-multi">';
if (item.grok3_quota) {
const g3 = item.grok3_quota;
const g3pct = g3.total_tokens ? Math.round(g3.remaining_tokens / g3.total_tokens * 100) : 0;
html += `<div class="quota-row">
<span class="quota-label">G3</span>
<div class="quota-bar-mini">
<div class="quota-fill" style="width:${g3pct}%"></div>
</div>
<span class="quota-num">${g3.remaining_tokens}/${g3.total_tokens}</span>
</div>`;
}
if (item.grok4_quota) {
const g4 = item.grok4_quota;
const g4pct = g4.total_tokens ? Math.round(g4.remaining_tokens / g4.total_tokens * 100) : 0;
html += `<div class="quota-row">
<span class="quota-label">G4</span>
<div class="quota-bar-mini">
<div class="quota-fill quota-fill-blue" style="width:${g4pct}%"></div>
</div>
<span class="quota-num">${g4.remaining_tokens}/${g4.total_tokens}</span>
</div>`;
}
html += '</div>';
tdQuota.innerHTML = html;
} else {
// 降级:显示旧的单一 quota
tdQuota.className = 'text-center font-mono text-xs';
tdQuota.innerText = item.quota;
}新增 CSS 样式
/* token.css: 分桶额度展示 */
.quota-multi {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 140px;
}
.quota-row {
display: flex;
align-items: center;
gap: 6px;
}
.quota-label {
font-size: 10px;
font-weight: 600;
color: var(--accents-5);
width: 20px;
flex-shrink: 0;
}
.quota-bar-mini {
flex: 1;
height: 6px;
background: #f3f4f6;
border-radius: 3px;
overflow: hidden;
min-width: 60px;
}
.quota-fill {
height: 100%;
background: #10b981;
border-radius: 3px;
transition: width 0.3s ease;
}
.quota-fill-blue {
background: #3b82f6;
}
.quota-num {
font-size: 10px;
font-family: 'Geist Mono', monospace;
color: var(--accents-5);
min-width: 50px;
text-align: right;
}
/* 统计区进度条 */
.stat-card .quota-bar {
margin-top: 6px;
height: 4px;
background: #f3f4f6;
border-radius: 2px;
overflow: hidden;
}
.stat-card .quota-bar .fill {
height: 100%;
border-radius: 2px;
transition: width 0.3s ease;
}
.stat-hint {
font-size: 10px;
color: var(--accents-4);
margin-top: 4px;
}updateStats 逻辑改造
function updateStats(data) {
// ... 现有 count 逻辑保留 ...
// 分桶额度汇总
let grok3Total = 0, grok3Remaining = 0, grok3HighTotal = 0, grok3HighRemaining = 0;
let grok4Total = 0, grok4Remaining = 0;
let grok41Queries = 0;
let editCooldownCount = 0;
flatTokens.forEach(t => {
if (t.status !== 'active') return;
if (t.grok3_quota) {
grok3Total += t.grok3_quota.total_tokens;
grok3Remaining += t.grok3_quota.remaining_tokens;
grok3HighTotal += (t.grok3_quota.high_total || 0); // 需后端返回
grok3HighRemaining += t.grok3_quota.high_remaining;
}
if (t.grok4_quota) {
grok4Total += t.grok4_quota.total_tokens;
grok4Remaining += t.grok4_quota.remaining_tokens;
}
if (t.grok41_queries != null) {
grok41Queries += t.grok41_queries;
}
// Image Edit 冷却中计数
if (t.model_cooldowns && t.model_cooldowns['grok-imagine-1.0-edit']) {
const until = t.model_cooldowns['grok-imagine-1.0-edit'];
if (until > Date.now()) editCooldownCount++;
}
});
// Grok-3 统计
setText('stat-grok3-quota', `${grok3Remaining} / ${grok3Total}`);
setBar('stat-grok3-bar', grok3Remaining, grok3Total, '#10b981');
// Grok-4 统计
setText('stat-grok4-quota', `${grok4Remaining} / ${grok4Total}`);
setBar('stat-grok4-bar', grok4Remaining, grok4Total, '#3b82f6');
// Grok-4.1 探针
setText('stat-grok41-quota', `${grok41Queries} queries`);
// Image 剩余 = grok-3 high 桶汇总
// 因为 grok-imagine-1.0 / grok-imagine-1.0-fast 都走 grok-3 cost=HIGH
setText('stat-image-quota', `${grok3HighRemaining}`);
// Video 剩余 = 同 Image(grok-imagine-1.0-video 也走 grok-3 HIGH)
setText('stat-video-quota', `${grok3HighRemaining}`);
// Image Edit = 独立上游探测型
const activeForEdit = flatTokens.filter(t => t.status === 'active').length;
const editAvailable = activeForEdit - editCooldownCount;
setText('stat-edit-quota', editAvailable > 0 ? `${editAvailable} 可用` : '冷却中');
}
function setBar(id, value, max, color) {
const el = byId(id);
if (!el) return;
const pct = max > 0 ? Math.round(value / max * 100) : 0;
el.innerHTML = `<div class="fill" style="width:${pct}%;background:${color}"></div>`;
}Note
Image / Video 剩余的推导逻辑:
grok-imagine-1.0、grok-imagine-1.0-fast、grok-imagine-1.0-video在 model.py 中都配置为grok_model="grok-3",cost=Cost.HIGH- 每次成功调用消耗
grok-3.remainingTokens -= 4,grok-3.high -= 1 - 因此 Image/Video 的真实可用次数 = grok-3 high 桶剩余(所有 active token 汇总)
Image Edit 的处理:
grok-imagine-1.0-edit的grok_model="imagine-image-edit",是独立上游- 无法通过
/rest/rate-limits查询到真实桶 - 采用与
grok-4.1-mini相同的模型级探测型冷却策略 - 前端只显示「X 可用 / 冷却中」,不显示精确数字
编辑弹窗适配
编辑弹窗的单一「额度」输入框暂时保留不动,因为:
- 分桶额度来自上游
/rest/rate-limits同步,不应手动编辑 - 现有 quota 字段可作为管理员手动覆盖的「总额度上限」
- 如果后续需要手动调整,可以改为只读展示 + 「同步」按钮
降级兼容
- 如果后端尚未返回
grok3_quota/grok4_quota字段,前端自动降级到旧的单一 quota 展示 - processTokens 中对新字段做 optional 处理
- 表格列宽从
w-20调整为w-36以容纳双行进度条
RateLimitsReverse 改造
当前 rate_limits.py 只查询 grok-4-1-thinking-1129 一种模型。需要改为同时查询 grok-3 和 grok-4 的真实桶:
# 需要分别查询以下 modelName:
MODELS_TO_QUERY = [
"grok-3", # 真实桶
"grok-4", # 真实桶
"grok-4-1-thinking-1129", # 探针
"grok-420", # 探针
]价值与场景
额度显示
备选方案(可选)
No response