[Bug已解决] Codex-无任何使用时仍扣减用量-虚耗用量计费与幂等记账方案

📅 2026/7/19 4:53:15
[Bug已解决] Codex-无任何使用时仍扣减用量-虚耗用量计费与幂等记账方案
[Bug已解决] Codex 无任何使用时仍扣减用量虚耗用量计费与幂等记账解决方案一、现象长什么样你打开应用但什么都没做没发消息、没跑任务却发现用量usage / 配额在被扣减——额度莫名其妙流失。即 GitHub Issues (openai/codex) #31402Codex Draining Usage Without any use.。 本质是用量计费逻辑把「非实际消耗」的事件也算进了用量——比如后台心跳/健康检查/轮询、会话保活、甚至失败的预检请求被错误地记成「一次使用」并扣额度。这是通用的「用量/计费记账正确性」问题记了不该记的账。 本文聚焦通用技术为什么会出现「虚耗用量」后台活动被计费、重复计数、失败请求计费、如何只在实际消耗时记账、以及如何用幂等事件避免重复扣减。用真实可运行 Python 代码说明不依赖具体产品。二、背景用量应该在「什么时刻」记账用量/配额系统应当只在真实发生消耗时记账真实发起了一次 API 调用并产生 token 消耗或明确的一次「任务执行」。不该记账的应用启动/保活的心跳轮询「有没有新消息」的空查询失败的预检还没真正消耗重复的网络重试同一事件记多次。 bug 在于记账的触发点太宽——几乎「任何网络请求/任何事件」都usage 1于是后台的那些「非用户主动使用」也被算进去了额度虚耗。三、为什么「没使用也扣减」具体路径后台轮询被计费应用定时轮询服务端「有新任务吗」每次轮询发了请求记账逻辑「凡请求即记」→ 静默扣减会话保活/心跳维持 WebSocket 的心跳包被当成「使用」重复重试一次失败的请求自动重试 3 次记账 3 次实际用户只触发了 1 次意图预检/探测启动时的能力探测请求被计费记账在「发起」而非「确认消耗」在「发出请求」时就记账而不是「收到计费回执实际 token 数」时才记于是失败/空响应的也记了。 本质记账时机错了——在「请求发起」就记而非「实际确认消耗」才记且没区分「用户主动使用」与「后台活动」。四、最小可运行错误记账 vs 正确记账下面演示「任何请求都记账虚耗」与「只在真实消耗时记账幂等」class UsageLedger: def __init__(self): self.used 0 self.seen_events set() # 幂等去重 # ❌ 错误任何网络请求都记账含心跳/轮询/重试 def on_any_request_bad(self, req): self.used 1 # 心跳也 1虚耗 # ✅ 正确只在「真实消耗事件」记账且幂等 def on_metered_event(self, event_id, is_real_use, tokens0): if not is_real_use: return # 后台活动不记账 if event_id in self.seen_events: return # 幂等重试/重复不重复记 self.seen_events.add(event_id) self.used max(1, tokens) # 按真实消耗记 if __name__ __main__: l UsageLedger() # 后台心跳 3 次 轮询 2 次都不该记账 for _ in range(5): l.on_any_request_bad(heartbeat) # 错误逻辑虚耗 5 次 print(错误逻辑 used:, l.used) # 5虚耗 l2 UsageLedger() # 只有 1 次真实使用带 event_id 防重试 l2.on_metered_event(evt-1, is_real_useTrue, tokens10) l2.on_metered_event(evt-1, is_real_useTrue, tokens10) # 重试幂等不重复 l2.on_metered_event(hb-1, is_real_useFalse) # 心跳不计 print(正确逻辑 used:, l2.used) # 10仅真实消耗要点区分「真实使用」与「后台活动」且用 event_id 幂等去重虚耗与重复记账都消除。五、解决方案一只在「确认消耗」时记账核心记账时机必须从「请求发起」改到「收到计费回执 / 确认实际消耗」def handle_api_response(resp, event_id): # 只有真正产生消耗有 token 计量才记账 if resp.status 200 and usage in resp.body: tokens resp.body[usage][total_tokens] ledger.record(event_id, tokens) # 真实消耗才记 # 失败 / 空响应 / 轮询无数据 → 不记账这样失败的请求、空轮询、心跳都不产生计费回执自然不记账。记账与「实际发生」绑定而非「发起动作」。六、解决方案二区分「用户主动使用」与「后台活动」记账前打标def call_api(payload, is_user_initiated, event_id): resp do_request(payload) if is_user_initiated and resp.ok: ledger.record(event_id, resp.tokens) # 仅用户主动使用记账 # 后台心跳/轮询is_user_initiatedFalse绝不记账应用的所有出站请求必须显式标is_user_initiated后台活动一律False。七、解决方案三幂等去重防重试/重复记同一用户意图可能因网络重试产生多次请求用event_id客户端生成幂等def record(event_id, tokens): if ledger.contains(event_id): return # 已记过跳过重试不重复 ledger.add(event_id, tokens) # 客户端每次用户操作生成唯一 event_id重试带同一 id这样网络层重试不会让用量翻倍。event_id可用 UUID服务端/本地账本都用它去重。八、解决方案四对账 用量明细可查给用户用量明细每笔记录的时间、事件、消耗便于发现虚耗def usage_detail(): return ledger.list() # 每条: {event_id, tokens, ts, reason} # 用户看到「凌晨 3 点扣了 5 次但我在睡觉」→ 立刻发现后台活动被计费并对账定期对比「记账总额」与「服务端实际计费回执总额」不一致告警可能本地虚记。这能兜底发现 #31402 类问题。九、排查清单虚耗用量没使用却扣额度 → 后台活动/重试/失败请求被错误记账#31402 类。确认消耗才记记账时机从「请求发起」改到「收到计费回执/实际 token」失败/空/轮询不计。区分主动使用请求标is_user_initiated后台心跳/轮询绝不记账。幂等去重event_id 去重重试/重复不重复记。用量明细提供每笔记录用户可发现虚耗定期对账服务端。测试模拟「仅心跳/轮询」应 used 不变。十、小结Codex Draining Usage Without any useopenai/codex #31402的本质是用量记账的触发点太宽——后台心跳/轮询/重试/失败预检等「非真实消耗」的请求也被记成一次使用并扣额度。记账时机在「请求发起」而非「确认实际消耗」且没区分用户主动使用与后台活动也没幂等去重导致额度虚耗。 通用解决方案适用于任何用量/配额计费系统确认消耗才记记账从「请求发起」改到「收到计费回执/实际 token」失败/空/轮询不计区分主动使用请求标is_user_initiated后台心跳/轮询绝不记账幂等去重event_id去重重试/重复不重复记明细对账提供用量明细供用户发现虚耗定期对账服务端总额。 记住用量只在「确认实际消耗」时记账且必须幂等。把心跳/轮询/重试当使用来扣就是虚耗——用户没用却在买单。