很多人发现 Codex “额度掉得快”,第一反应是换套餐。更有效的做法,是先回答三个可观测问题:任务是不是越来越长、失败后是不是在重复重试、这次运行到底使用 ChatGPT 登录还是 API Key。下面给出一套不记录提示词、不读取密钥的 NDJSON 日志方案,并用一个纯标准库 Python 脚本把问题定位到具体任务。
一次 Codex 任务看起来只是“帮我改代码”,实际可能同时放大三类成本:
OpenAI 官方认证文档明确区分了两条路径:使用 ChatGPT 登录属于订阅访问,使用 API Key 属于按用量访问;本地 Codex 客户端可支持两者,而认证方式也决定相应的管理与数据策略。API Quickstart 则建议把密钥放进 OPENAI_API_KEY 环境变量,由 SDK 自动读取。也就是说,排查前必须先把 auth_mode 记清楚,不能只看“同一个 Codex”就把两套用量混算。
下面不是 Codex 固定输出格式,而是给本地脚本、CI 包装器或任务调度器使用的自定义观测格式。每完成一个任务,追加一行 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:只记 chatgpt、api 或 unknown;duration_s:任务总耗时;input_chars:本次送入上下文的字符数,仅作趋势代理,不等于 token;retries:相同目标的自动重试次数;tool_failures:命令、文件、网络或外部工具调用失败次数;result:建议使用 ok、quota、rate_limit、error、cancelled。不要记录提示词全文、源码内容、Cookie、Authorization 请求头、API Key,也不要读取 ~/.codex/auth.json。认证方式可在运行前用 codex login status 人工确认,再写入枚举值。
最稳妥的采集点不是模型内部,而是你自己的任务入口和退出处:开始时生成随机 task_id 并记下单调时钟;结束时只写耗时、输入规模、重试计数和结果类别。若任务由 CI 触发,可在流水线包装脚本的 finally 阶段追加一行;若是人工使用 CLI,可先从最容易统计的“耗时、认证方式、结果”三个字段开始,不必为了日志侵入业务代码。
input_chars 也不需要扫描整个仓库。它只统计本次主动提交给任务的文本、选中文件或拼接摘要的字符规模。目录里存在多少代码,不等于实际上下文有多大。对文件输入,可以只记录汇总后的字符数;对二进制文件则记录数量并从字符指标中排除。这样既保留趋势,又不会把源码复制进观测文件。
因此,第一次运行脚本的目标不是得到一个“标准答案”,而是建立可重复的基线。连续几天用同一口径采集,才有资格比较优化前后变化。
把日志保存为 codex_tasks.ndjson,再把下面脚本保存为 analyze_codex_usage.py:
#!/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()运行:
python3 analyze_codex_usage.py codex_tasks.ndjson阈值应按自己的基线调整,例如:
python3 analyze_codex_usage.py codex_tasks.ndjson \
--long-seconds 900 --large-chars 200000观测结果 | 更可能的原因 | 第一动作 |
|---|---|---|
长任务率、大上下文率都高,重试低 | 会话历史和输入文件持续膨胀 | 一个目标一个新任务,只传相关 diff、错误片段和文件路径 |
上下文不大,重试率高 | 网络、权限或命令参数出现确定性失败 | 区分可重试错误与硬错误;硬错误立即停止并保留现场 |
工具失败率高,但任务最终成功 | 同一故障被多次调用掩盖 | 先修复命令、路径或权限,再重新运行任务 |
| ChatGPT 工作区、订阅访问或当前登录状态需要核对 | 先执行 |
| API 组织的用量、限额或请求节奏问题 | 去 API 平台检查用量与限制,降低并发并加入退避 |
同一天混用两种 | 两套访问路径被合并统计 | 按认证方式分组比较,不用总次数判断“额度异常” |
注意:这个脚本只定位异常模式,不能替代平台账单,也不能根据字符数计算准确费用。
codex login status 的结果,但只写 chatgpt/api,不保存凭证。ChatGPT 订阅是否自动等于 API 余额?
不能这样理解。官方文档把 ChatGPT 登录的订阅访问与 API Key 的按用量访问分开;使用 API Key 时应查看 API 平台侧的用量与限制。
input_chars 能换算成 token 或金额吗?
不能。中英文、代码、结构化文本的切分不同,字符数只适合观察“输入是不是越来越大”。
脚本会不会泄露提示词或密钥?
不会主动读取。前提是日志只保留数值、状态和随机任务号,不把提示词、源码、Cookie 或密钥写进去。
一次 quota 错误就说明账户异常吗?
不一定。先确认认证方式,再区分订阅访问、API 限额、速率限制和临时失败。连续数据比单次报错更有诊断价值。
真正有用的“额度优化”不是盲目减少使用,而是让每一次上下文扩张和重试都有可解释的产出。先用日志建立基线,再针对最大的一项损耗下手,通常比直接换套餐更快看到效果。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。