1. 被“继续”两个字吃掉的预算:一个被大多数人忽略的计费黑洞
“继续”——这大概是所有用 Codex 写代码的人敲得最多的两个字。代码写一半断了,敲个“继续”;思路没展开,敲个“继续”;甚至有时候只是手比脑子快,顺手就发出去了。我以前也这么干,直到有一次月底对账,发现某个项目的 Token 消耗比预估高了将近四成,排查了半天代码逻辑和调用频率,最后把账单明细拉出来逐条比对,才发现问题根本不在我写了多少代码,而在于我敲了多少次“继续”。
这件事让我意识到一个很反直觉的事实:在大模型按 Token 计费的体系里,你付出的钱不是由你“说了什么”决定的,而是由模型“看到了什么”决定的。而“继续”这两个字,恰恰是触发模型“看到最多内容”的一类指令。它本身只有两个 Token,但它会让模型把之前所有的对话上下文重新读一遍,然后在此基础上生成新的内容。换句话说,你花的是“两个字”的钱,买的是“整段历史”的账。
这篇文章我想把这件事彻底讲清楚。不管你是刚接触 Codex 的新手,还是已经用了一段时间但从来没仔细算过账的老用户,只要你还在按 Token 付费,这套逻辑就和你直接相关。我会从计费机制、缓存命中率、上下文膨胀、实操排查几个角度,把“继续”背后藏着的成本一笔一笔拆开给你看,并且给出可以直接抄作业的优化方案。
1.1 先搞明白:你付的钱到底是怎么算出来的
很多人对 Token 计费的理解停留在“输入多少钱、输出多少钱”这个层面,但实际上主流大模型的计费结构比这细得多。以 Codex 这类编程助手为例,一次请求的费用通常由三部分组成:
- 输入 Token(Input Tokens):你发给模型的所有内容,包括系统提示词、历史对话、当前提问、附带的代码文件等。
- 输出 Token(Output Tokens):模型生成并返回给你的内容。
- 缓存读取 Token(Cached Input Tokens):如果部分输入内容命中了缓存,这部分按更低的单价计费。
关键就在于第三项。缓存命中率这个概念,直接决定了你的实际支出是“标价”还是“打折价”。不同平台的缓存折扣力度不一样,常见的是缓存命中部分按标准输入价格的 10% 到 50% 计费。也就是说,同样一段上下文,命中缓存和不命中缓存,成本可能差好几倍。
那缓存是怎么命中的?简单说,当你发送的请求前缀(Prefix)和之前某次请求的前缀完全一致时,平台就有可能复用之前计算过的中间状态,这部分就算作缓存命中。注意这里的关键词是“前缀完全一致”——只要开头有一个字符不同,后面的缓存就全部失效。
这就引出了“继续”的第一个问题:它本身不改变前缀,理论上应该能命中缓存。但实际情况是,很多人在使用过程中会不自觉地做一些破坏前缀连续性的操作,比如切换账号、修改系统提示词、插入新的文件引用,这些都会让缓存失效,导致原本可以打折的上下文变成全价输入。
1.2 为什么“继续”会让上下文越滚越大
要理解“继续”的成本,得先理解大模型对话的一个基本特性:它是无状态的。模型本身不记得你上一句说了什么,每一次请求都是独立的。你之所以感觉它在“接着聊”,是因为客户端把之前所有的对话历史都打包塞进了这一次请求里。
所以对话轮次越多,每次请求携带的上下文就越长。第一轮可能只有几百 Token,第十轮可能就上万了,第三十轮可能直接顶到上下文窗口的上限。而“继续”这个指令的特殊之处在于,它通常出现在对话已经进行了一段时间之后——也就是说,你敲“继续”的时候,上下文已经积累到相当规模了。
我拿一个真实的场景算笔账。假设你用 Codex 写一个中等复杂度的功能模块,对话过程大概是这样:
| 轮次 | 你的输入 | 累计上下文(约) | 本轮输入 Token |
|---|---|---|---|
| 第1轮 | 描述需求 | 500 | 500 |
| 第5轮 | 补充细节 | 3000 | 3000 |
| 第10轮 | 修改某处逻辑 | 8000 | 8000 |
| 第15轮 | 继续 | 15000 | 15000 |
| 第20轮 | 继续 | 25000 | 25000 |
| 第25轮 | 继续 | 38000 | 38000 |
看到问题了吗?第15轮你只敲了“继续”两个字,但这一轮实际计费的输入 Token 是 15000。第25轮同样是“继续”,输入 Token 已经涨到 38000。如果这几轮都命中了缓存,成本还能压一压;如果因为某些原因缓存没命中,那这几轮“继续”就是实打实的全价输入。
更麻烦的是,很多人敲“继续”是因为模型上一轮输出被截断了。截断意味着输出 Token 没花完,但下一轮你要把截断的内容连同之前的上下文一起再发一遍,输入 Token 反而更高。这就形成了一个恶性循环:输出省了一点,输入翻了几倍。
1.3 缓存命中率:决定你账单是三位数还是四位数的隐藏开关
缓存命中率这个概念,我在前面提了一嘴,但值得单独拿出来讲,因为它对成本的影响实在太大了。我做过一组对比测试,同样的对话内容,在缓存命中率 80% 和缓存命中率 20% 两种情况下,最终账单差了将近三倍。
影响缓存命中率的因素主要有这么几个:
第一,前缀稳定性。这是最核心的。你的系统提示词、项目背景说明、固定的代码模板,这些如果每次都一样,就能稳定命中缓存。但如果你每次开新对话都改一改系统提示词,或者中途切换了模型版本,缓存就会大面积失效。
第二,对话连续性。在同一个对话线程里连续提问,缓存命中率通常比较高,因为前缀是逐步累加的。但如果你频繁地开新对话、切账号、换设备,每次都要重新建立上下文,缓存命中率就会掉下来。
第三,内容去重。如果你在对话里反复粘贴同一段代码、同一份文档,这些重复内容如果位置靠前,可能命中缓存;但如果位置靠后,或者被其他内容隔开,就不一定能命中。
第四,平台策略。不同平台对缓存的实现方式和过期时间不一样。有的平台缓存有效期只有几分钟,你隔半小时再回来继续,缓存早就没了;有的平台缓存能保留更久,但要求前缀完全一致。
我自己的经验是,把系统提示词和项目背景固定下来,尽量在同一个对话线程里完成一个完整任务,不要频繁切换账号和模型,这三条做到,缓存命中率基本能维持在比较健康的水平。反过来,如果你经常“继续”到一半去切个账号再回来,那缓存基本就废了,每一轮都是全价输入。
2. 上下文膨胀的连锁反应:不只是多花钱那么简单
上下文越滚越大,最直接的后果是账单变高,但它的影响远不止于此。我在实际使用中踩过的坑包括:响应变慢、输出质量下降、甚至模型开始“胡言乱语”。这些问题表面上和计费无关,但根子上都是上下文管理没做好。
2.1 上下文窗口不是越大越好用
现在很多模型都支持很长的上下文窗口,动辄几十万 Token。这给人一种错觉:反正窗口够大,随便塞。但实际用下来,上下文窗口大不等于有效上下文大。模型在处理超长上下文时,注意力会被稀释,靠前的内容容易被忽略,靠后的内容又可能被截断。你塞进去 10 万 Token,模型真正“用上”的可能只有其中一部分。
这就导致一个很尴尬的局面:你为了“继续”一个任务,把大量历史上下文都带上了,花了不少钱,但模型的实际表现反而不如你重新开一个干净的对话、把关键信息精简后重新描述一遍。我做过对比,同样一个代码修改任务,在积累了 3 万 Token 的对话里让模型“继续”修改,和在新的对话里用 2000 Token 重新描述需求,后者的输出质量明显更稳定,成本也只有前者的零头。
2.2 “继续”触发的重复计算
还有一个容易被忽略的点:当你敲“继续”时,模型往往会把之前已经生成过的内容重新“看”一遍,然后接着往下写。如果上一轮输出被截断在某个位置,这一轮它需要先理解截断点之前的内容,才能继续。这个理解过程本身也是要消耗计算资源的,虽然体现在计费上主要是输入 Token,但响应时间会明显变长。
我实测过,在一个积累了 2 万 Token 的对话里敲“继续”,首 Token 返回时间比在新对话里提问慢了将近一倍。如果你是在交互式地写代码,这种延迟会严重影响体验。而且延迟高的时候,人容易烦躁,烦躁的时候更容易乱敲“继续”,形成又一个恶性循环。
2.3 上下文污染:错误会被一路带下去
比多花钱更可怕的是,上下文里的错误会被一路带下去。假设你在第 5 轮的时候给了一个有 bug 的代码片段,模型基于这个片段生成了后续内容。到第 15 轮你敲“继续”,模型仍然会参考第 5 轮那个有问题的片段,导致错误不断累积。你越“继续”,偏离越远,最后不得不推倒重来,前面花的钱全白费。
这种情况我遇到过好几次。最典型的一次是数据库表结构定义错了,我没注意,后面让模型“继续”生成了一堆基于错误表结构的查询代码。等到发现的时候已经生成了几百行,全部要改。如果当时我在发现苗头的时候就开新对话、把正确的表结构重新贴一遍,成本会低得多。
3. 实操:怎么把“继续”的成本压下来
前面讲了这么多问题,接下来是干货部分。我把自己实际用下来有效的几个方法整理出来,你可以直接照着做。
3.1 用“摘要+重启”代替无脑“继续”
这是我觉得最有效的一招。当一个对话进行到一定轮次(我的经验值是 10 到 15 轮),或者你感觉上下文已经很长了,不要直接敲“继续”,而是让模型先做一次摘要:
请把当前对话的核心结论、已确定的方案、待解决的问题整理成一份不超过 500 字的摘要,我要用它开启一个新的对话。
拿到摘要后,开一个新对话,把摘要贴进去,再继续往下做。这样做的好处是:新对话的上下文从零开始,只有你贴进去的摘要,输入 Token 大幅降低;同时摘要过滤掉了中间的试错过程和无关内容,模型的理解反而更聚焦。
我对比过,同样完成一个功能模块,全程“继续”的方式最终消耗的输入 Token 大约是“摘要+重启”方式的三到四倍。而且后者的输出质量更稳定,因为上下文干净。
3.2 固定前缀,提高缓存命中率
如果你确实需要在同一个对话里持续工作,那就尽量保持前缀稳定。具体做法:
- 系统提示词写一次就别改。把项目背景、技术栈、代码规范这些固定信息放在系统提示词里,整个对话过程中不要修改。
- 不要在对话中途切换模型版本。切换模型通常会导致缓存失效。
- 不要频繁切换账号。切换账号后,之前的缓存基本就没了。
- 把重复引用的文件放在对话靠前的位置。这样它们更有可能被纳入缓存前缀。
我自己的习惯是,每个项目开一个专门的对话线程,系统提示词里写清楚项目结构和技术约束,然后所有相关工作都在这个线程里完成。实测下来,缓存命中率能维持在比较高的水平,账单明显比之前乱开对话的时候低。
3.3 控制单次输入的长度
除了控制对话轮次,单次输入的长度也要注意。有时候我们需要把一整段代码或者一整份文档贴给模型,这时候可以做个预处理:
- 只贴相关部分。如果文件有 1000 行,你只需要改其中 50 行,那就只贴这 50 行加上必要的上下文,不要把整个文件都贴进去。
- 用行号或标记定位。告诉模型“第 120 到 170 行是需要修改的部分”,比直接贴一大段更省 Token。
- 压缩注释和空行。如果贴的是代码,可以先把注释和多余空行去掉,能省不少 Token。
这些操作看起来琐碎,但积少成多。我一个月的使用量里,光是“只贴相关部分”这一条,就省了大概 15% 到 20% 的输入 Token。
3.4 监控和排查:怎么知道钱花在哪了
如果你用的是支持用量查看的平台,一定要定期看账单明细。重点看几个指标:
| 指标 | 说明 | 关注点 |
|---|---|---|
| 输入 Token 总量 | 所有请求的输入 Token 之和 | 是否异常偏高 |
| 缓存命中 Token | 命中缓存的输入 Token | 占比是否健康 |
| 缓存命中率 | 缓存命中 Token / 输入 Token 总量 | 低于 50% 要排查 |
| 单次请求最大输入 | 单次请求中输入 Token 的最大值 | 是否接近窗口上限 |
| 输出 Token 总量 | 所有请求的输出 Token 之和 | 与输入的比例是否合理 |
如果发现缓存命中率突然下降,或者单次请求输入 Token 异常高,就要回头检查是不是有“继续”导致的上下文膨胀,或者是不是切换了账号、修改了系统提示词。
4. 常见问题与排查技巧实录
这一部分整理了我自己和身边朋友遇到过的一些典型问题,以及对应的排查思路。
4.1 为什么我明明没写多少代码,Token 却用超了
这是最常见的问题。排查顺序建议如下:
- 先看对话轮次。如果单个对话超过 20 轮,大概率是上下文膨胀导致的。
- 再看缓存命中率。如果命中率低于 50%,说明大量输入是按全价计费的。
- 检查是否有重复粘贴。同一段代码或文档如果在对话里出现了多次,每次都会计入输入 Token。
- 检查系统提示词长度。有些系统提示词写得很长,每轮都会带上,累积起来很可观。
- 检查是否有“继续”密集出现。如果账单明细里能看到短时间内多次请求,且每次输入 Token 都很大,基本就是这个问题。
4.2 缓存命中率突然掉下来是怎么回事
缓存命中率不会无缘无故下降,通常对应某个操作变化:
- 切换了账号:缓存通常和账号绑定,切号后缓存失效。
- 修改了系统提示词:前缀变了,缓存自然失效。
- 切换了模型版本:不同版本的缓存不通用。
- 对话间隔太久:部分平台的缓存有过期时间,隔太久回来缓存已经清了。
- 插入了新的前置内容:比如在对话开头新增了一段说明,导致整个前缀偏移。
排查的时候,回想一下最近有没有做上述操作,基本就能定位。
4.3 “继续”之后模型跑偏了怎么办
如果发现模型在“继续”之后输出质量下降,不要硬着头皮继续“继续”。正确的做法是:
- 立即停止当前对话,不要再往下敲“继续”。
- 让模型做一次摘要,把正确的部分提炼出来。
- 开新对话,把摘要和必要的上下文贴进去,重新开始。
- 如果问题出在某个具体的错误上,在新对话里明确指出“之前有个错误是 XXX,请避免”。
硬撑着“继续”只会让错误越滚越大,最后返工的成本远高于重开的成本。
4.4 一个容易被忽略的细节:输出截断的处理
模型输出被截断时,很多人第一反应是敲“继续”。但更好的做法是:
- 先看截断在哪里。如果截断在代码中间,直接“继续”可能导致代码不完整或逻辑断裂。
- 让模型重新生成完整版本。如果截断的内容不长,可以让模型“把上面的内容完整重新输出一遍”,虽然输出 Token 多花一点,但避免了后续的上下文膨胀。
- 分段处理。如果内容确实很长,让模型分多次输出,每次输出一个完整的段落或函数,而不是截断后“继续”。
我自己的经验是,输出截断时重新生成完整版本,比截断后“继续”更划算。因为“继续”会把截断前的所有内容再算一遍输入,而重新生成只需要多花一点输出 Token。
4.5 速查表:常见症状与对应处理
| 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 账单突然变高 | 上下文膨胀、缓存失效 | 检查对话轮次和缓存命中率 |
| 响应变慢 | 上下文过长 | 摘要后开新对话 |
| 输出质量下降 | 上下文污染 | 停止“继续”,摘要后重启 |
| 缓存命中率低 | 切号、改提示词、换模型 | 固定账号和提示词,减少切换 |
| 单次输入 Token 异常高 | 重复粘贴、全文件粘贴 | 只贴相关部分,压缩内容 |
| “继续”后跑偏 | 错误累积 | 立即停止,摘要后新对话 |
5. 我自己的使用习惯和一些额外建议
说了这么多,最后分享几个我长期用下来觉得比较顺手的习惯。
第一,给每个任务开独立的对话线程。不要在一个对话里既写前端又写后端又调数据库,任务混在一起上下文会非常乱。一个任务一个线程,做完就关,下次需要再开新的。
第二,系统提示词尽量精简但完整。把必须固定的信息写进去,比如项目结构、技术栈、代码风格,但不要写太多废话。系统提示词每轮都会带上,写太长就是每轮都在为它付费。
第三,养成看账单的习惯。不用每天看,但至少每周扫一眼,看看有没有异常。很多成本问题都是积累出来的,早发现早调整。
第四,不要害怕开新对话。很多人不愿意开新对话,觉得“重新描述需求很麻烦”。但实际上,重新描述需求花的那点输入 Token,远比带着几万 Token 的上下文“继续”要便宜。而且新对话的输出质量通常更好,返工更少。
第五,把“继续”换成更明确的指令。与其敲“继续”,不如说“请接着上面的内容,完成 XXX 函数的实现”。这样模型的目标更明确,输出更聚焦,也能减少不必要的上下文回溯。
这些习惯看起来都是小事,但加在一起,我每个月的 Token 支出比之前少了大概三分之一,而且写代码的体验反而更好了。省下来的钱和时间,干点啥不好。