首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Codex 额度消耗太快怎么排查:用 NDJSON 定位上下文膨胀、重试与工具失败

Codex 额度消耗太快怎么排查:用 NDJSON 定位上下文膨胀、重试与工具失败

原创
作者头像
用户12688822
发布2026-08-25 15:25:18
发布2026-08-25 15:25:18
220
举报

很多人发现 Codex “额度掉得快”,第一反应是换套餐。更有效的做法,是先回答三个可观测问题:任务是不是越来越长、失败后是不是在重复重试、这次运行到底使用 ChatGPT 登录还是 API Key。下面给出一套不记录提示词、不读取密钥的 NDJSON 日志方案,并用一个纯标准库 Python 脚本把问题定位到具体任务。

先给结论:不要把三类消耗混在一起

一次 Codex 任务看起来只是“帮我改代码”,实际可能同时放大三类成本:

  1. 上下文膨胀:同一会话不断追加历史、日志和文件,后面的每一步都要携带更大的上下文。
  2. 重复重试:命令、网络或权限已经确定失败,自动化仍按原参数重跑。
  3. 认证方式混淆:以为在使用 ChatGPT 订阅访问,实际当前会话使用了 API Key;或者反过来。

OpenAI 官方认证文档明确区分了两条路径:使用 ChatGPT 登录属于订阅访问,使用 API Key 属于按用量访问;本地 Codex 客户端可支持两者,而认证方式也决定相应的管理与数据策略。API Quickstart 则建议把密钥放进 OPENAI_API_KEY 环境变量,由 SDK 自动读取。也就是说,排查前必须先把 auth_mode 记清楚,不能只看“同一个 Codex”就把两套用量混算。

设计一个不碰敏感内容的最小日志

下面不是 Codex 固定输出格式,而是给本地脚本、CI 包装器或任务调度器使用的自定义观测格式。每完成一个任务,追加一行 JSON:

代码语言:json
复制
{"ts":"2026-08-25T08:00:00Z","task_id":"t-001","auth_mode":"chatgpt","duration_s":860,"input_chars":184000,"retries":2,"tool_failures":1,"result":"ok"}
{"ts":"2026-08-25T08:28:00Z","task_id":"t-002","auth_mode":"api","duration_s":92,"input_chars":24000,"retries":3,"tool_failures":3,"result":"rate_limit"}

字段含义:

  • task_id:随机任务编号,不要写客户名、仓库私有地址;
  • auth_mode:只记 chatgptapiunknown
  • duration_s:任务总耗时;
  • input_chars:本次送入上下文的字符数,仅作趋势代理,不等于 token;
  • retries:相同目标的自动重试次数;
  • tool_failures:命令、文件、网络或外部工具调用失败次数;
  • result:建议使用 okquotarate_limiterrorcancelled

不要记录提示词全文、源码内容、Cookie、Authorization 请求头、API Key,也不要读取 ~/.codex/auth.json。认证方式可在运行前用 codex login status 人工确认,再写入枚举值。

日志从哪里产生

最稳妥的采集点不是模型内部,而是你自己的任务入口和退出处:开始时生成随机 task_id 并记下单调时钟;结束时只写耗时、输入规模、重试计数和结果类别。若任务由 CI 触发,可在流水线包装脚本的 finally 阶段追加一行;若是人工使用 CLI,可先从最容易统计的“耗时、认证方式、结果”三个字段开始,不必为了日志侵入业务代码。

input_chars 也不需要扫描整个仓库。它只统计本次主动提交给任务的文本、选中文件或拼接摘要的字符规模。目录里存在多少代码,不等于实际上下文有多大。对文件输入,可以只记录汇总后的字符数;对二进制文件则记录数量并从字符指标中排除。这样既保留趋势,又不会把源码复制进观测文件。

四个指标分别回答什么

  • 长任务率回答“是否少数任务占据了大部分运行时间”,默认以十分钟作为诊断起点,不是产品限额;
  • 大上下文率回答“最近任务的输入规模是否明显抬升”,阈值应取团队自己的分位数;
  • 发生重试率按任务计算,只要重试一次就计入,避免某个循环把总次数冲高;
  • quota/rate_limit 命中只说明日志观察到相应结果,不能证明扣费金额,也不能把速率限制等同于余额耗尽。

因此,第一次运行脚本的目标不是得到一个“标准答案”,而是建立可重复的基线。连续几天用同一口径采集,才有资格比较优化前后变化。

可直接运行的 Python 汇总脚本

把日志保存为 codex_tasks.ndjson,再把下面脚本保存为 analyze_codex_usage.py

代码语言:python
复制
#!/usr/bin/env python3
import argparse
import json
from collections import Counter
from pathlib import Path

def pct(n, total):
    return f"{(n / total):.1%}" if total else "0.0%"

def main():
    p = argparse.ArgumentParser()
    p.add_argument("file", type=Path)
    p.add_argument("--long-seconds", type=int, default=600)
    p.add_argument("--large-chars", type=int, default=120000)
    args = p.parse_args()

    rows, bad = [], 0
    with args.file.open(encoding="utf-8") as f:
        for line in f:
            if not line.strip():
                continue
            try:
                r = json.loads(line)
                rows.append({
                    "task_id": str(r.get("task_id", "unknown")),
                    "auth_mode": str(r.get("auth_mode", "unknown")),
                    "duration_s": max(0, float(r.get("duration_s", 0))),
                    "input_chars": max(0, int(r.get("input_chars", 0))),
                    "retries": max(0, int(r.get("retries", 0))),
                    "tool_failures": max(0, int(r.get("tool_failures", 0))),
                    "result": str(r.get("result", "unknown")),
                })
            except (ValueError, TypeError, json.JSONDecodeError):
                bad += 1

    total = len(rows)
    long_tasks = [r for r in rows if r["duration_s"] >= args.long_seconds]
    large_tasks = [r for r in rows if r["input_chars"] >= args.large_chars]
    retried = [r for r in rows if r["retries"] > 0]
    tool_failed = [r for r in rows if r["tool_failures"] > 0]
    quota_hits = [r for r in rows if r["result"] in {"quota", "rate_limit"}]
    auth = Counter(r["auth_mode"] for r in rows)

    print(f"有效任务: {total},坏行: {bad}")
    print("认证方式:", dict(auth))
    print(f"长任务率: {pct(len(long_tasks), total)}")
    print(f"大上下文率: {pct(len(large_tasks), total)}")
    print(f"发生重试率: {pct(len(retried), total)}")
    print(f"工具失败率: {pct(len(tool_failed), total)}")
    print(f"quota/rate_limit 命中: {len(quota_hits)}")

    score = lambda r: (
        r["duration_s"] / max(args.long_seconds, 1)
        + r["input_chars"] / max(args.large_chars, 1)
        + r["retries"]
        + r["tool_failures"]
        + (2 if r["result"] in {"quota", "rate_limit"} else 0)
    )
    print("\n优先排查:")
    for r in sorted(rows, key=score, reverse=True)[:10]:
        print(f"{r['task_id']}: auth={r['auth_mode']}, "
              f"sec={r['duration_s']:.0f}, chars={r['input_chars']}, "
              f"retry={r['retries']}, tool_fail={r['tool_failures']}, "
              f"result={r['result']}")

if __name__ == "__main__":
    main()

运行:

代码语言:bash
复制
python3 analyze_codex_usage.py codex_tasks.ndjson

阈值应按自己的基线调整,例如:

代码语言:bash
复制
python3 analyze_codex_usage.py codex_tasks.ndjson \
  --long-seconds 900 --large-chars 200000

如何读结果:四种典型组合

观测结果

更可能的原因

第一动作

长任务率、大上下文率都高,重试低

会话历史和输入文件持续膨胀

一个目标一个新任务,只传相关 diff、错误片段和文件路径

上下文不大,重试率高

网络、权限或命令参数出现确定性失败

区分可重试错误与硬错误;硬错误立即停止并保留现场

工具失败率高,但任务最终成功

同一故障被多次调用掩盖

先修复命令、路径或权限,再重新运行任务

chatgpt 下出现 quota

ChatGPT 工作区、订阅访问或当前登录状态需要核对

先执行 codex login status,确认账号与工作区,再复现一次

api 下出现 rate_limitquota

API 组织的用量、限额或请求节奏问题

去 API 平台检查用量与限制,降低并发并加入退避

同一天混用两种 auth_mode

两套访问路径被合并统计

按认证方式分组比较,不用总次数判断“额度异常”

注意:这个脚本只定位异常模式,不能替代平台账单,也不能根据字符数计算准确费用。

优化清单:先止住浪费,再考虑扩容

  1. 拆任务:把“读全仓库、改代码、跑测试、写文档”拆成可验收的阶段,每阶段输出短摘要。
  2. 缩上下文:传最小复现、相关文件和 diff;排除依赖目录、构建产物、二进制和重复日志。
  3. 限制重试:网络抖动可指数退避;鉴权失败、参数错误、文件不存在等硬错误不要原样重跑。
  4. 固定认证基线:每天第一条任务记录 codex login status 的结果,但只写 chatgpt/api,不保存凭证。
  5. 设置停止条件:限定最长耗时、最大重试和验收点;超过阈值先总结现场,不让任务无限滚动。
  6. 做前后对照:至少收集 20~50 个任务,再比较长任务率、重试率和 quota 命中,而不是凭某一天的体感判断。

常见问题

ChatGPT 订阅是否自动等于 API 余额?

不能这样理解。官方文档把 ChatGPT 登录的订阅访问与 API Key 的按用量访问分开;使用 API Key 时应查看 API 平台侧的用量与限制。

input_chars 能换算成 token 或金额吗?

不能。中英文、代码、结构化文本的切分不同,字符数只适合观察“输入是不是越来越大”。

脚本会不会泄露提示词或密钥?

不会主动读取。前提是日志只保留数值、状态和随机任务号,不把提示词、源码、Cookie 或密钥写进去。

一次 quota 错误就说明账户异常吗?

不一定。先确认认证方式,再区分订阅访问、API 限额、速率限制和临时失败。连续数据比单次报错更有诊断价值。

真正有用的“额度优化”不是盲目减少使用,而是让每一次上下文扩张和重试都有可解释的产出。先用日志建立基线,再针对最大的一项损耗下手,通常比直接换套餐更快看到效果。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 先给结论:不要把三类消耗混在一起
  • 设计一个不碰敏感内容的最小日志
    • 日志从哪里产生
    • 四个指标分别回答什么
  • 可直接运行的 Python 汇总脚本
  • 如何读结果:四种典型组合
  • 优化清单:先止住浪费,再考虑扩容
  • 常见问题
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档