1. Codex CLI 账单为什么总对不上:token 成本结构拆解
用 Codex CLI 写代码久了,很容易产生一个错觉:输出越长越贵。我一开始也这么想,直到把本地会话日志翻出来逐条统计,才发现真正吃掉额度的是输入侧那一大坨上下文,尤其是缓存命中的部分。Codex 改成 token 计费之后,这个问题变得更敏感——你每天到底用了多少、花在哪,光看 CLI 界面那点提示根本看不出来。
Codex CLI 的计费结构大致分三块:输入 token、缓存输入 token、输出 token。输出单价最高,但单次请求里输出通常只占很小比例;输入才是大头,因为每次请求都要带上系统提示、工具 schema、历史对话、文件上下文。如果开了长任务或者 agent 模式,历史会不断累积,输入量会滚雪球。缓存命中能把这部分成本压下来,但前提是你得知道命中率是多少。
问题在于,Codex CLI 本身不给你一份清晰的账单。它只在会话日志里埋了token_count事件,格式是 JSONL,散落在~/.codex/sessions/下面。你要想知道今天用了多少、哪个任务组最烧,得自己把这些日志扒出来做聚合。这就是我写统计脚本的起点。
这篇要解决的问题很具体:给你一套可复制的 Python + tiktoken 离线统计方案,把 Codex CLI 本地会话记录拆成输入、缓存、输出三档,算出每档的 token 量和占比,再配合任务组快照做费用归因。同时说明怎么把 Codex 的 endpoint 指到 TaoToken,让用量在一个地方统一看。适合已经在用 Codex CLI、想搞清楚钱花在哪的开发者。
先说清楚边界:这套方案读的是本地日志里已经存在的 token 数,不是重新请求 API 算的,所以它是估算归因,不是官方账单。tiktoken 只在手动导入 transcript 时用来做文本分词估算。两者分工不同,后面会分别讲。
2. TaoToken 前置:把 Codex endpoint 统一到一处看用量
在开始写统计脚本之前,建议先把 Codex CLI 的请求出口统一到 TaoToken。原因很简单:本地日志统计的是"你这边发了多少",但如果你同时用多个 key、多个 endpoint,账单会散在好几个地方,归因就断了。统一到 TaoToken 之后,本地统计和平台用量能对上,排查异常也方便。
TaoToken 的 API 地址是https://taotoken.net/api,官网入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。你需要先去控制台拿一个 API Key,地址是https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。拿到 key 之后,Codex CLI 的配置入口在~/.codex/auth.json,这个文件同时管 Base URL、Key 和 Model ID 三件套。
Codex CLI 读取auth.json的字段名在不同版本略有差异,常见的是OPENAI_API_KEY和OPENAI_BASE_URL。如果你用的是较新的版本,可能还会看到base_url或api_base这种写法。最稳妥的做法是先跑一次codex --version确认版本,再对照官方文档改。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各版本对应的字段说明。
改完之后,Codex CLI 的所有请求都会走 TaoToken,你在控制台就能看到统一的用量曲线。这时候再跑本地统计脚本,两边数据能互相印证:本地日志算出来今天输入 800 万 token,平台显示消耗也对得上,说明统计逻辑没问题;如果对不上,大概率是日志里有你没统计到的事件类型,或者缓存字段没解析对。
有一点要注意:auth.json里如果同时存在旧的 endpoint 配置,Codex 可能会优先读旧的。改完之后建议把文件备份一份,然后清掉旧字段,只留 TaoToken 的 Base URL 和 Key。改完跑一次codex exec "print hello"做冒烟测试,能正常返回就说明接入成功。
如果你还想在网页端直接对话验证模型是否正常,可以用模型对话入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite。长期跑编码任务或者 agent 的话,Coding Plan 更划算,入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。
3. 可复制配置:auth.json 与统计脚本 settings 片段
这一节给两份可直接复制的配置。第一份是 Codex CLI 的auth.json,第二份是统计脚本的config.json。两份都按实际路径写,你复制过去改 key 就能用。
先看~/.codex/auth.json。这个文件是 JSON 格式,注意不要写成 TOML。字段名以你当前 Codex 版本为准,下面这份是较通用的写法:
{ "OPENAI_API_KEY": "sk-你的TaoToken密钥", "OPENAI_BASE_URL": "https://taotoken.net/api", "model": "gpt-5-codex" }如果你用的是支持base_url字段的版本,把OPENAI_BASE_URL换成base_url即可,值不变。Model ID 按你实际用的填,Codex 系列常见的是gpt-5-codex,具体以接入文档里的模型列表为准。改完记得chmod 600 ~/.codex/auth.json,避免权限过宽。
再看统计脚本的config.json。这个文件放在项目根目录,和codex_logs.py同级。它管三件事:日志目录、tiktoken 编码、任务组存储路径。
{ "codex_log_dir": "~/.codex/sessions", "tiktoken_encoding": "o200k_base", "storage_dir": ".codex-usage", "default_model": "gpt-5-codex", "timezone": "Asia/Shanghai" }tiktoken_encoding这个字段很关键。tiktoken 支持多种编码,常见的有cl100k_base(GPT-3.5/4 系列)和o200k_base(GPT-4o 及更新系列)。Codex 系列模型一般用o200k_base,如果你不确定,可以跑一段代码探测:
import tiktoken def pick_encoding(model_name: str) -> str: try: enc = tiktoken.encoding_for_model(model_name) return enc.name except KeyError: return "o200k_base" print(pick_encoding("gpt-5-codex"))这段代码会返回模型对应的编码名。如果模型不在 tiktoken 的映射表里,就回退到o200k_base。注意 tiktoken 的版本要>=0.7.0,低版本可能不认识新模型。
storage_dir是任务组数据的存放位置,脚本会在里面生成groups.jsonl、snapshots.jsonl、turns.jsonl三个文件。JSONL 的好处是每行一条记录,追加写不会破坏已有数据,出问题也好手动修。
配置写完之后,跑一次初始化:
python -m codex_usage.cli doctor --config config.jsondoctor会检查日志目录是否存在、tiktoken 能否加载、存储目录是否可写。三项都通过再往下走。
4. 验证请求:跑通统计脚本并核对成功结果
配置就绪后,先跑一次最小验证,确认脚本能读到日志、能算出数。命令是:
python -m codex_usage.cli codex report --today --lang zh --config config.json这条命令做四件事:扫描~/.codex/sessions下当天的 JSONL 日志、提取token_count事件、按时间窗口取 delta、汇总成终端表格。输出大概长这样:
日期: 2025-XX-XX 请求数: 142 输入 token: 8,432,100 缓存输入 token: 7,910,300 (93.8%) 输出 token: 521,800 (6.2%) 合计: 8,953,900看到这个结果,先别急着下结论。重点看缓存输入占比。如果缓存占比超过 90%,说明你的请求大量复用了历史上下文,这通常出现在长任务或 agent 模式里。如果缓存占比很低,说明每次请求都在带全新上下文,成本会高很多。
接下来验证 tiktoken 那条线。手动导入一段 transcript:
python -m codex_usage.cli turn add \ --group repo-refactor \ --file transcript.md \ --config config.jsontranscript.md里用注释标记角色,脚本会按标记分段估算:
<!-- codex-usage:user --> 帮我把这个函数改成异步的 <!-- codex-usage:assistant --> 好的,这是改后的版本... <!-- codex-usage:tool --> pytest 运行结果:3 passed <!-- codex-usage:file-context --> src/service.py 全文 200 行导入后跑turn list --group repo-refactor,会看到每段的 token 估算值。这里 tiktoken 的作用是把可见文本转成 token 数,它不知道 Codex 内部真实请求带了多少隐藏内容,所以这个数是下限估算。
最后做任务组快照验证。任务开始前记一次用量,结束后再记一次:
python -m codex_usage.cli snapshot --group repo-refactor --usage 42 --config config.json # ... 跑任务 ... python -m codex_usage.cli snapshot --group repo-refactor --usage 47 --config config.json然后group show repo-refactor会输出这个任务组的汇总:turn 数、估算请求数、工具调用次数、可见 token 估算、有效 token 估算、用量变化、每 1% 对应多少可见 token。最后这个指标最有用,它帮你建立"1% 额度大概等于多少 token"的经验值。
成功结果的判断标准有三条:当天报告能出数、transcript 导入能分段、任务组快照能算出每 1% 对应 token。三条都过,说明统计链路是通的。
5. 常见错排查:401、local proxy failed、reading choices 与 OAuth
统计脚本跑起来之后,报错主要集中在接入侧和解析侧。下面按真实报错逐条给排查路径。
401 Unauthorized。这个最常见,出现在 Codex CLI 请求阶段,不是统计脚本的问题。原因通常是auth.json里的 key 失效或 Base URL 写错。先确认 key 有没有多余空格,再确认 Base URL 是https://taotoken.net/api而不是带路径的完整地址。如果 key 是从控制台复制的,注意别把前后引号也复制进去。改完跑codex exec "print hello"验证。
local proxy failed。这个报错说明 Codex CLI 尝试走本地代理但连不上。检查环境变量里有没有残留的HTTP_PROXY或HTTPS_PROXY,有的话先清掉。另外确认auth.json里没有指向本地端口的 Base URL。如果你之前配过别的 endpoint,旧配置可能还在生效,把auth.json清空重写一遍。
reading choices 相关报错。这个出现在统计脚本解析日志时,通常是日志格式和脚本预期不一致。Codex 不同版本的token_count事件字段名可能有差异,比如input_tokens和prompt_tokens混用。打开一条原始日志看一眼实际字段名,然后对照codex_logs.py里的解析逻辑改。如果字段缺失,脚本会跳过该条记录,不会崩,但统计数会偏小。
OAuth 相关报错。Codex CLI 某些版本支持 OAuth 登录,如果你之前用 OAuth 登录过,auth.json里可能有 token 字段和 API Key 字段并存。这种情况下 Codex 可能优先走 OAuth,导致你的 TaoToken key 不生效。解决办法是把 OAuth 相关字段删掉,只留 API Key 和 Base URL。删之前备份文件。
tiktoken 加载失败。报错通常是Unknown encoding或KeyError。先确认 tiktoken 版本pip show tiktoken,低于 0.7.0 就升级。如果版本没问题,检查config.json里的tiktoken_encoding是不是拼错了。o200k_base和cl100k_base是最常用的两个,别写成o200k或cl100k。
统计数和平台对不上。这个不算报错,但很常见。原因通常是本地日志只记录了部分事件,或者缓存字段没解析。先确认脚本版本和 Codex 版本匹配,再检查日志目录是不是有多个 session 文件没被扫到。如果差异在 5% 以内,属于正常范围;差异超过 20%,就要逐条核对日志字段。
排查顺序建议:先保证 Codex CLI 能正常请求(401 和 proxy 问题在这一层解决),再保证统计脚本能读到日志(reading choices 和 tiktoken 问题在这一层解决),最后核对两边数据。三层分开排查,比一上来就混着看效率高。
6. 语义一致 CTA:把用量归因变成日常习惯
统计脚本跑通之后,最有价值的不是某一天的数字,而是持续记录形成的经验表。我自己的做法是每天收工前跑一次codex report --today,把输入、缓存、输出三档记下来,周末做一次汇总。跑了两周之后,基本能判断出"今天这个用量算正常还是异常"。
如果你还没接入 TaoToken,建议先把 endpoint 统一过去,这样本地统计和平台用量能互相印证。API Key 在https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite拿,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。想先在网页端验证模型是否正常,用模型对话入口https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite。长期跑编码任务或 agent,Coding Plan 入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。
最后给一个实用技巧:把codex report --today加到你的 shell 启动脚本里,每天第一次开终端时自动跑一次。这样你不用记着去查,用量异常会第一时间暴露出来。统计这件事,频率比精度重要——每天看一眼,比一个月后翻账单有用得多。