☰
Codex CLI token消耗排查:从流量上G到成本降50%的实战复盘
2026/9/26 6:41:40 网站建设 项目流程

1. 从“上 G 流量”说起:一次让我肉痛的排查

1.1 先把背景交代清楚

我用 Codex CLI 的时间不算短,大部分时候是让它跑一些看起来很小的任务:修一个类型错误、给某个模块补测试、按代码规范重构一个函数。这些任务单看都很轻,结果月底一翻 OpenAI API 用量,整个人有点懵。再顺手看了眼公司网关的流量报表,发现这台机器的上传流量居然是以 GB 计算的,而我当天明明没有往任何代码仓库推送过超过几十 MB 的改动。那一刻我意识到,Codex 在“上传”这件事上花的代价,远比我想象中大得多。

Codex 是 OpenAI 推出的终端编程助手,它会把项目里的文件读进上下文,再调用命令行工具做分析、跑测试、给修改建议。它的计费方式很简单也很残酷:按 token 计费。你可以把 token 理解成模型读到的语义碎片,读入的代码、提示词、历史对话、模型吐出来的回答,统统都要换算成 token 扣费。我最初并没有把“流量暴涨”和“token 浪费”这两件事绑定在一起看,因为流量单位是字节,token 是计费点,中间还隔着 HTTP 头、TLS 握手和压缩。但几轮排查下来,结论很明确:上 G 的流量里有相当一部分是同一个内容被重复发送造成的,而这部分重复内容,每一轮都会被模型重新读取、重新计费。

这篇文章就是我当时完整排查过程的复盘。内容围绕三个问题展开:Codex 的流量到底浪费在哪、token 是怎么在不知不觉中翻倍的、以及我后来靠哪些操作把这个量压了下去。如果你也在用 Codex,或者正在帮团队控制 AI 编程工具的 API 成本,这篇文章应该能帮你少走不少弯路。

1.2 先别急着骂工具,先搞清楚流量和 token 的关系

这里要先给 Codex 说句公道话:流量上 G,并不等于真的消耗了几亿 token。HTTPS 握手、HTTP 头部、TCP 重传、TLS 记录,都会把网络层的字节数放大。比如我一次请求体实际只有 200KB,但在网络层看到的上传流量可能是 210KB 甚至更多;如果中间有超时重传,这个数字还会继续膨胀。更关键的是,Codex 每一轮请求都会把当前上下文原样重发一遍,即使模型那边已经通过缓存命中了前缀,网络侧依然要把完整请求体再跑一次。所以“上传 1GB”直接除以“一个 token 差不多 4 个字符”来估算 token,方向从一开始就错了。

但也不能反过来认为流量和 token 完全没有关系。请求体如果一直稳定在几十 KB 到几百 KB,那只有跑几百次任务才会积累到 1GB;可如果我发现单次请求体已经到几 MB,那只有一个原因:prompt 里塞进了大量文件内容。后来我把网关流量按小时切开,对照 Codex 的执行日志,发现流量高峰完全对应我在一个交互式会话里连续改了一下午代码的那几个小时。也就是说,真正浪费的并不是某一瞬间的大流量,而是同一个会话反复重放大量上下文所累积出来的 token。

在这里也顺便给新手一个粗粒度换算参考:英文大约 1 token 约等于 0.75 个单词,中文则通常 1 到 2 个汉字对应 1 个 token。一个 2000 行的 TypeScript 文件,体积大概 50KB 到 100KB,换算下来差不多是 1 万到 2 万 token。只看一次请求,这点量不值一提;但如果一个会话开了 20 轮,这个文件被原样重发了 20 次,那它一个人就吃掉了 20 万到 40 万 token。Codex 的流量和费用,就是这样在你看不见的地方悄悄滚起来的。

1.3 第一轮排查:把 token 明细从日志里捞出来

正式动手之前,我给自己定了一个很朴素的问题:Codex 自己知不知道每次请求到底花了多少 token?答案非常明确:知道。OpenAI 的 API 响应里自带 usage 字段,记录每一次请求消耗的输入 token 和输出 token。不同接口的字段名略有差异,Chat Completions 接口里叫 prompt_tokens 和 completion_tokens,Responses API 里则叫 input_tokens 和 output_tokens,思路完全一致。

我用一个小脚本把 Codex 日志里的 usage 字段全部汇总出来,再按 input_tokens 从高到低排序。跑了二十多组请求之后,结论非常鲜明:大约 80% 的 token 消耗在了 prompt_tokens 上,也就是“模型看到了什么”在花钱,而“模型输出了什么”在总费用里的占比反而不高。这个结果让我立刻调整了优化方向——不要追求让模型少说话,真正要解决的是让模型少看不该看的东西。后文会具体拆一个请求的结构,看看哪些内容在吃 token,以及我后来是怎么把这些内容压下去的。

2. 一次 Codex 请求里,token 到底去了哪里

2.1 拆开一个请求的结构

想弄清楚 token 浪费,第一个动作应该是把一个 Codex 请求的完整结构摊开看。我整理日志时发现,一个典型请求大概由五部分组成:

  • 系统提示词和工具定义:Codex 预设的规则、可用工具 schema、安全提示等,这部分相对固定,通常几千 token,并且会稳定出现在每个请求里。
  • 多轮对话历史:你在这个会话里的每次提问、Codex 每次回复、每次工具执行结果,都会按时间顺序附加到后续请求中。这是最有增长空间的一块。
  • 当前项目上下文:Codex 在工作目录里找到的文件、最近修改过的文件、在对话中主动引用的文件,这些内容会以明文形式写进 prompt。
  • 工具调用结果:在沙箱里执行命令后的 stdout 和 stderr,例如运行测试、执行 lint、打印目录结构,都会成为后续请求的一部分。
  • 模型输出:包括代码补全结果和回答文本,对于某些推理模型,还会包含内部 reasoning token。

只看列表可能还不够直观,我把一次修改 5 个文件的真实请求体抓了下来,做了一个简化示例,你可以直观感受一下里面装了多少东西:

{ "model": "gpt-5.2-codex", "instructions": "你是 Codex,一个终端编程助手……", "tools": [ { "type": "function", "name": "read_file", "description": "读取指定文件的完整内容" }, { "type": "function", "name": "shell_command", "description": "在工作目录执行命令" } ], "conversation_history": [ { "role": "user", "content": "帮我看看 src/utils/format.ts 里的日期格式化为什么对负数不友好" }, { "role": "assistant", "content": "我先读取 src/utils/format.ts 并进行测试……" }, { "role": "user", "content": "好的,顺便把相关单测也补上" } ], "current_file_content": [ { "path": "src/utils/format.ts", "content": "export function formatDate(...) { ... }" }, { "path": "src/utils/__tests__/format.test.ts", "content": "describe('formatDate', () => { ... })" } ], "tool_output": [ { "command": "npm test src/utils", "output": "PASS src/utils/__tests__/format.test.ts\n...\nTest Suites: 1 passed, 1 total" } ] }

这个例子里的 conversation_history 看起来只有三轮,但如果这个会话已经持续了两小时、对话了三十轮,那 conversation_history 就会变成整个会话所有文本的拼接,体量会远超 current_file_content。

2.2 我汇总出来的 token 占比表

为了把问题量化,我把自己最典型的一次任务(让 Codex 补全某个模块的单测和文档)中全部请求做了统计。那次任务前后花了四十多分钟,最终 token 消耗的构成大致如下表:

token 去向我这次任务中的占比为什么这么大
对话历史累积30%-40%每一轮请求都要把之前所有轮次的提问、回答、工具输出原样重发
项目文件上下文30%-40%同一个文件被反复读取,尤其是被修改前读取一次、修改后校验又读取一次
系统提示词和工具定义5%-10%相对固定,但会出现在每一次请求里,也会被缓存命中
模型输出10%-15%代码补全和解释性文本,通常可控
推理 token5%-15%某些模型在输出最终答案前会产生大量内部推理内容,同样计费

这个表最让人警醒的是第一行:对话历史累积。表面上我每次只是在追加一句“再改一下 somewhere.ts”,但因为历史记录带着前面几十轮对话、几十条工具输出、十几次文件内容,这次追加的真实代价可能是新增内容的几百倍。Codex 不是在“记住之前的对话”,而是在把之前所有对话又一次完整地读给模型听。

这也解释了为什么流量会上 G:不是某个瞬间传输了 1GB,而是几十轮请求乘以单轮几百 KB,积少成多后总上传量轻松破 G。这里我踩过的坑也顺便说一下:不要只看单轮请求的延迟和大小,一定要看累计值。我一开始就是被“单次请求好像也没多大”给骗了,直到把几十轮请求体大小累加,才发现总数已经远超我的直觉。

2.3 那些容易被忽略的“隐藏 token”

占比表里的“推理 token”是很多 Codex 新手用户完全没想到的存在。OpenAI 的不少模型在正式输出之前,会先做内部推理过程,这部分内容不出现在你看到的回答里,但会记录在 usage 的 output_tokens_details.reasoning_tokens 字段中。如果你用的模型带 reasoning 能力,一次“看起来只输出了 200 个 token 的回答”可能实际在服务端产生了 2000 个推理 token,而账单按后一个数字计算。

更隐蔽的是 prompt 缓存问题。OpenAI 对稳定的前缀会自动做 prompt caching,命中缓存的前缀不会再按普通输入 token 计价,价格会大幅降低。Codex 在正常工作时,系统提示词、工具定义和较早的对话历史往往会被缓存命中,这也是为什么有时候单次请求本身并不贵,可一旦你把任务拆成多个不相关的小会话,或者频繁改变工作目录导致前缀变化,缓存就会失效,前面那些本该便宜的重复输入又变回全价。我后来在日志里专门对比了 cached_input_tokens 字段,发现几个低效会话的缓存命中率几乎为零,所有历史都被当成了全新输入重新计费。

3. 我在日志里看到的典型 token 浪费场景

3.1 会话历史无限膨胀,每次开会都要重新读一遍全部纪要

Codex 的交互式会话很顺手,但也最容易造成 token 膨胀。我见过一个极端案例:某个会话从早上开到晚上,中间改过十几个文件,跑过几十次测试命令,输出过长日志。到了下午,哪怕我只问一句“那这个改完以后还影响别的地方吗”,Codex 也会把早上的所有文件内容、所有命令输出、所有问答全部再读一遍。当时的单轮请求体已经接近 5MB,换算成 token 大约 80 万到 100 万,而这 100 万 token 里真正和当前问题相关的可能不到 5%。

可以这样理解:多轮会话的 token 消耗是线性累积的,但请求体大小是近似等差数列。第一次请求只有 2 万 token,第二次可能 4 万,第三次 6 万,到了第 50 次请求,单轮就是 100 万 token。后面每一次追加对话,都相当于把前面几十轮内容重新付一次钱。这个问题在交互式会话里无解,唯一的办法是不要在一个会话里无限续命。

3.2 无关文件被反复装进上下文

Codex 为了理解代码逻辑,会主动读取工作目录里的文件。这个“主动读取”是我发现的最大的流量消耗源。有一次它为了修改src/api/user.ts,把整个src/api目录下的十几个文件全部读了一遍,甚至连tests/e2e/fixtures里的 JSON 测试数据都没放过。单轮多塞了 3 万 token,看起来还好,但在随后的每一轮对话里这些文件都会继续驻留在上下文中,又变成了重复计费的一部分。

最让我印象深刻的一次,是 Codex 在执行shell_command查找符号时,打印了某个 JSON 文件里几十条完整记录。这个输出被塞进了工具结果,然后在下一次请求里又作为对话历史的一部分再次出现。相当于我被同一个文件收了两次钱:第一次是文件读取,第二次是命令输出,第三次是历史重放。如果你手头有大量配置文件、数据文件、测试 fixtures,这些都很容易成为 token 黑洞。

3.3 重试和人工重复请求,等于同一份内容反复付费

重试是一个比预想更常见的浪费场景。我在 Codex 日志里发现过同一串 request_id 连续出现三次的情况:第一次请求发送后,因为网络波动或者上游响应超时,客户端自动重试;但服务端其实已经处理完第一次请求并计了费用。重试之后,后面的对话历史又多了一份失败请求带来的工具输出,等于同一个任务被处理了多次,费用也跟着翻倍。

人工重复请求则更隐蔽。Codex 有时候给出的改动不符合预期,我的第一反应是在同一个会话里重新描述一遍需求;看起来只是“多说了一遍”,实际上前面所有上下文都要跟着再发一遍。有些人会直接开一个全新的会话,但这又会丢掉缓存命中,让本来就该被缓存的前缀重新全价计费。如何权衡这两者,我后面会在操作清单里给出一个比较实际的做法。

3.4 工具输出太长,测试结果膨胀了几十倍

Codex 在沙箱里可以执行各类命令,但工具输出并不总是“短小精悍”。跑一次npm test可能输出几百行 PASS/FAIL 日志;跑一次lint可能把十几个文件的警告信息全部打出来。这些输出会进入 tool_output,然后在下一次请求时进入对话历史,成为后续所有请求的常驻居民。我见过一次只改了一行代码的任务,最后因为工具输出太长,足足比预期多消耗了 20 万 token。

更麻烦的是,Codex 默认经常使用npx执行的一些脚本,如果脚本本身会打印大量调试信息,那这些噪音全都会被模型看见并计费。这里建议在项目代码里把那些会大量输出的本地脚本做好默认配置,或者至少在 prompt 里明确要求“只返回简洁的结果摘要”,而不是让 Codex 无脑把整段 stdout 都粘进下一个请求。

4. 我总结的可直接照做的减 token 操作清单

4.1 给 Codex 划定工作边界,别让它把整个仓库当背景板

第一步,也是最有效的办法:缩小 Codex 的工作目录和工作范围。比如一个大仓库里有client、server、docs三个子项目,如果这次只想改server,就不要在根目录启动 Codex,直接进入server子目录再开。这一招能让 Codex 默认扫描的文件范围立刻缩小,同时也能降低很多误读文件进入上下文的概率。

第二步,配置忽略规则。Codex 默认会尊重项目里的.gitignore,所以node_modules、dist、build、.next、coverage这些目录通常不会被当作普通上下文读出。但如果你有很多.json、.sqlite、fixtures文件,或者一些体积很大的测试数据,最好在.gitignore里也加上,或者在 Codex 支持的自定义忽略配置里明确排除。实测下来,忽略掉node_modules和coverage之后,我平均单轮请求体降低了 20% 到 30%。

第三步,也是最推荐的方法:代码评审类任务不进交互式会话,直接用一条命令完成任务,结束后让它退出。比如只想要 Codex“分析一下src/utils/format.ts里的日期处理逻辑”,直接执行一次codex exec "分析 src/utils/format.ts 的日期处理逻辑",得到结果后会话结束,后续不会无限累积。这样做的代价是长链路任务不好跟踪,但在多数单点修改、代码解释、单元测试补全场景里,收益非常明显。

4.2 改变使用习惯:单次任务优先,长会话按需拆散

我的实际经验是,把“一个会话干到底”改成“每个子任务一个短会话”,对 token 消耗的改善立竿见影。比如原先我让 Codex“修改登录逻辑,顺便加测试,再更新文档”,这个任务在同一个会话里执行可能需要 15 轮,上下文越长越贵;拆成三个独立小任务后,每个任务都以全新上下文开始,历史累积被彻底切断。

但这里也有个反直觉的细节:不要无脑地开新会话。如果两个任务针对同一个文件的同一段逻辑,开新会话会让 Codex 重新读一次文件,反而可能更贵。更好的做法是合并“针对同一块代码”的多次迭代,把“不同代码块、不同类型任务”拆成独立会话。比如“README 文案”和“登录页样式”就应该拆开;“登录页样式的第一版”和“登录页样式的第二版调整”适合留在同一个会话里。

另外,如果某个交互式会话已经超过二十轮,我建议直接强制把它结束掉,记录前面的有效结论后开一个新会话。与其在后面每一轮都为二十轮以前的内容买单,不如拆成两个干净的小任务。实测下来,这一条能省下至少 30% 的重复 token,而且不会明显影响生成质量。

4.3 掐住工具输出和日志噪音

我在第 3.4 节说过,Codex 会把工具输出当作上下文的一部分。要减少这部分浪费,可以直接在 prompt 里给 Codex 定纪律。我常用的说法是:

运行任何命令时,只返回最终结果和摘要;不要返回完整日志、不要打印文件全文、不要把可能存在的大量 JSON 数据贴进上下文。

如果你有需要复用的常用规则,可以写进 Codex 的自定义指令里,或者直接在项目根目录放一个说明文件。这样每次启动新会话时,Codex 都会在系统提示词层面看到这条约束,比你每次口头强调稳定得多。

还有一个容易被忽略的点:尽量不要让 Codex 自己去运行那些输出量很大的本地命令。比如全仓库的 lint、全量测试、依赖安装,这类命令输出可能几十上百行。这些输出进入上下文后,会伴随剩余所有轮次重复计费。正确做法是先自己跑一遍,筛选出关键错误信息后,再把精简后的内容交给 Codex 分析。人先判断,机器再补全过程,往往比让 Codex 盲跑更省钱。

4.4 管理重试和并发,减少没必要发生的重复计费

重试是隐藏成本大户。你可以通过开启 Codex 客户端的调试模式,或者观察 API 日志里的 request_id,检查有没有相同请求被反复发送。如果客户端在网络超时后自动重试,可以考虑调高客户端的超时阈值,避免明明服务端已经在处理了,客户端却因为等不及又发了一次。

另外,并发也要小心。我不是说并发一定浪费,而是并发多个 Codex 实例时,每个实例都会独立读取文件、独立维护上下文。如果你在同一台机器上同时跑四个 Codex 任务修改同一个大仓库,四个实例读到的可能是同一批文件,这四份内容全都会被重复计费。我的做法是:并发任务尽量分散在不同目录,或者让每个任务只负责一个独立模块,从源头上让上下文不重叠。

4.5 每天看一眼 token 账单,把浪费盯在初期

控制 token 浪费不是一次性工程,而是持续的习惯。我最开始是每周看一次 OpenAI 后台的用量页面,后来发现浪费已经发生太久了,于是改成每天早晨花一分钟扫一眼昨天的 token 曲线。不多看,只看两个数字:总 token 变化是否异常、cached_tokens 占比是否稳定。

如果某一天 token 明显飙升,我会去翻 Codex 日志,看看是不是有人开了一个超长会话、或者在一个大仓库里反复跑命令。团队协作时,还可以在项目的 README 或者 CI 配置里放一份“Codex 使用规范”,写上建议目录、忽略规则、会话轮数上限。经过几次复盘调优,我这边同类型任务的 token 成本下降了大约 50%,流量也从“上 G 级别”回到了几百 MB 的合理区间。

5. 常见问题与排查技巧:一张速查表

5.1 本地流量和后台 token 为什么对不上

这是最常见的困惑,我也遇到过。原因有四个:一是 HTTPS 和 HTTP 头部会放大字节数,二是 TCP 重传会让网络层统计偏大,三是 prompt caching 会让模型侧对重复内容打折,而网络层仍然按实际传输算,四是压缩前后差异巨大,代码文本经过压缩后体积会明显缩小。因此不要试图用“流量字节 ÷ 4 = token 数”来精确对账,只要看趋势和量级就足够了。

更准确的做法是:以 API 返回的 usage 为准,流量只作为辅助信号。每天记录 usage_total_tokens 的累计值,和网络报表里的上传流量放在一起看趋势,如果两者在同一天出现同步跳升,那大概率就是某个长会话开启了。

5.2 省 token 会不会降低生成质量

会有一点点影响,但通常不像想象中那么大。上下文更精简的代价是,Codex 可能缺少某些背景信息,偶尔会按错误方向执行。我的经验是:不要为了省 token 把“悬崖勒马”的关健线索删掉,尤其不要删掉当前文件的类型定义和函数签名;值得删的,是那些重复出现的旧日志、旧版本文件内容、以及无关目录里的样板代码。

实际操作中,我优先采用“先分析后执行”的方式:第一轮让 Codex 先告诉我它需要看哪些文件,第二轮再让它读取指定文件并做修改。这样前瞻性地控制上下文,比事后清理历史要自然得多。质量方面,我测试了十几个日常任务,精简上下文后的代码正确率没有明显下降,反而因为减少噪音,Codex 更不容易跑偏。

5.3 是不是换模型就一定更省 token

不一定。有些模型单次调用很便宜,但如果它理解能力较弱,来回纠错的轮次多了,累计 token 反而更高。我建议在固定 prompt 结构的前提下,先统计同一批任务在不同模型下的 total_tokens 和 cached_tokens 命中率,再做选择。如果你发现某个模型的 reasoning_tokens 总是占据 output 的大头,而你的任务不需要深层推理,那么换一个更轻量的非推理模型可能比“硬忍着”更合理。

同时要留意模型对应的上下文窗口限制。如果单次请求体已经接近模型最大输入长度,Codex 可能会自动截断早期内容,截断后又会造成理解断层。我的原则是:保证单轮请求体不超过模型上下文窗口的大约三分之一,一旦接近就要主动拆碎任务,而不是继续堆历史。

5.4 团队场景怎么做 Codex 成本治理

如果只有你一个人用 Codex,上面的方法已经够用了。但团队里一旦有五六个人同时使用,看到 token 翻倍的速度会非常惊人。我建议做三件事。

一是统一配置模板。将忽略文件规则、自定义指令、常用模型统一成一份团队配置,避免每个人凭感觉乱设置。

二是按任务类型设定轮数上限。像“格式化代码”“补注释”这类轻任务,明确告诉 Codex 只需要一步完成;像“跨模块重构”这样的大任务,单独建分支处理,避免和日常小修改混在同一个会话里。

三是每周做一次用量复盘。把每个项目目录对应的 token 用量拉出来,看是谁贡献了最多,分析那几天的请求日志,找出具体是因为长会话还是因为大量文件被重复读取。我执行这套流程一个月之后,团队整体 token 成本下降了接近一半,而且没有一个人抱怨 Codex 变笨了。

常见问题典型症状排查手段处理建议
长会话导致历史膨胀单轮请求体越来越大,后续每次对话都很慢查看日志里对应会话的 input_tokens 变化趋势在会话超过 20 轮后主动开新会话
忽略规则没生效请求里能看到 node_modules 或构建产物检查 .gitignore 和 Codex 自定义忽略配置把大型目录全部加入忽略列表
工具输出太长工具调用后可以选择性进入下一轮历史看日志中 tool_output 的规模prompt 中明确要求“只输出摘要”
网络重试导致重复计费日志中发现相同 request_id 多次出现查看 API 日志中的请求 ID调整超时阈值,减少自动重试
prompt 缓存命中率低cached_tokens 占比趋近于零查看 usage 中 cached_tokens 字段保持系统提示词和前几轮历史稳定,减少频繁换目录

我自己走完这一圈下来,最深的体会是:Codex 的 token 浪费,绝大多数时候不是工具在“偷”,而是使用习惯在给“重复”创造机会。旧历史要发一遍、大文件要发一遍、失败重试再发一遍,每一次都不贵,但乘上几十轮之后就是很可观的成本。建议你下一次打开 Codex 之前,先想想这次任务到底需要多少个文件的上下文,需要几轮对话才能完成,再做决定。用这个思路去盯一周每天的 token 明细,你会很快看到数字下来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询