核验清单

  • ✓打开这一笔响应的 usage.input_tokens,和你这次新写的那一句分开看。新句子很短、输入却接近整段已有历史,就是链在计,不是接口把两个字数错了。
  • ✓请求里的 previous_response_id 应对着上一笔响应的 id。conversation 是另一个字段,留的是长期对象,不要把两个 id 当成同一种免计。
  • ✓看 store。默认留存大约 30 天。设成 false 只是不再留这份对象供你用 id 取回,不能当成把输入免掉的开关。
  • ✓这一轮如果重发了顶层 instructions,把这段算进你亲手送出的输入。链不会自动带上上一笔的顶层说明,重发也不会被免计。
  • ✓缓存命中看 usage 里的 cached input。它是输入里被改价的一部分,不是从 input_tokens 里删掉。推理 token 在输出列,不要和这条输入链加两次。

1. 接上 id,计的仍是已有输入

对话状态先把单次生成写成独立、无状态的请求。多轮要自己把先前的消息放进下一次 input,或者让接口替你保管状态。保管有两条:Conversations API 留一个长期对象;previous_response_id 把响应串成链。两条都是少让你手工拼接,不是少计 token。

同一页把计费写在留存说明后面,句子很硬:即使用了 previous_response_id,链上已有的 input tokens 仍按输入 token 计费。迁移到 Responses 把一种常见假设标成错误:以为这个字段会免掉先前上下文的账单。正确说法是,响应链里已有的输入 token 仍按输入计。所以这一轮只打了「继续」,账单可以对上整段已经送进去过的输入,而不是那两个字。

文档用的词是 previous input tokens,也就是链上各笔已经作为输入存在的 token。它没有另写一条「把上一笔的 output 再乘一个系数」的公式。上一句回答会不会出现在这一轮的输入里,以这一笔返回的 usage.input_tokens 为准。用新消息的字数去估,会把整段链看成免费。

2. store 决定留不留,不决定计不计

对话状态写明:响应对象默认保存 30 天,可以在控制台日志里看,也可以用接口取回。创建响应时把 store 设为 false,就关掉这份留存。Conversations 里的对象和条目不受这 30 天限制。挂到某个 conversation 上的响应,它的条目会一直留着,没有这个 30 天 TTL。

留存和计费写在相邻的两段,读的时候容易并成一件事。关掉 store,是这份对象不再被留下来供你用 id 接着串。它不把已经发生的输入从这一轮的用量里拿掉,也不把上一轮已经记下的账退回。对象不在了,你就得自己把上下文放进下一次 input。亲手放进去的那些 token,仍然是输入。

迁移文档还写了零数据保留组织会被强制 store: false。清单里的做法是:无状态或零数据保留的流程,要自己带上加密的推理项,推理上下文才能跨轮继续。那是你送进下一次请求的内容,不是一份免计的附件。WebSocket 模式沿用同一套 previous_response_id 语义。连接上的本地缓存是为了低延迟续传,字段本身仍决定谱系。解析不到那个 id,文档要求把它设为 null,并自己传完整输入。那份完整输入同样进输入列。

3. 顶层说明不会跟着链走

迁移文档把多轮写成三种做法:用 previous_response_id 让接口保管先前响应的上下文;把上一笔的 output 条目自己放回去,方便你裁剪;或者用 Conversations API 留一个持久对象。第一种有一条容易漏的限制:顶层 instructions 不会被 previous_response_id 带过去。稳定的说明要每一轮重发。

重发的那段是你这次亲手送出的输入,会进这一轮的 usage.input_tokens。漏发,是模型看不到你以为还在的规则。重发,也不是把这段说明变成免费。两条都不要靠「已经接上了」去猜。conversation 的 id 和 response 的 id 也不是同一个字段。一个留的是可以跨会话继续用的对象,一个串的是响应链。省下来的是拼接代码,文档没有把其中任何一条写成免计上下文。

自己回放时也一样。你把先前的用户消息和助手输出放进 input,这些就是这一次请求的输入。接口替你放,和你自己放,差别在谁保管条目,不在这一轮还计不计已有的输入。要核对你亲手放进去的那一段有多长,可以用输入 token 计数。它按 Responses 的输入格式计,包含角色和边界这类格式 token,并且不预测模型会生成多少输出。链被带上之后的总数,仍以这一笔响应里的 usage 为准。

4. 窗口、缓存、推理不要并进这一列

对话状态把上下文窗口定义成单次请求能用的 token 上限,里面包括输入、输出,以及部分模型的推理 token。生成超出窗口的部分,可能在响应里被截断。截断说的是多出来的生成没有完整回到你手里,不是把已经计入的 input 退掉。怎么把过长的历史裁短,文档指到单独的 compaction 页。阈值以那一页为准。裁掉的是这一轮送进窗口的内容,不是上一笔账单的冲正。

提示缓存是另一列。计数说明把 cached input 写成输入里可以被改价的一类,价格可以和未缓存的输入不同,它仍然是输入,不是从 input_tokens 里删除的一段。命中只说明匹配的前缀走了缓存价。链还在,历史就还在这一轮的输入里。写入比普通输入更贵、断点放错等于每次都付写入,那是另一篇的规则,这里不重复单价。

推理 token 记在输出上,屏幕上的短答案可以比输出用量短得多。那一列的对法在推理那篇。这一篇的「继续很短」对的是输入被整段链撑大。两列可以出现在同一笔响应里:输入看链,输出看生成。把 cached 明细再加进 input_tokens,或把推理明细再加进总输出,都会把一笔账数成两笔。文件搜索那种按天走的存储,也是第三口时钟,空闲仍可能在计,和这条「每一轮都重新计量已有输入」不是同一个开关。

5. 充值缩不短这条链

对账按这一笔响应走,不按聊天框里最后一行的字数走。第一列是 usage.input_tokens,里面应能看到链上已有的输入,而不只是「继续」。第二列是 store:默认大约留 30 天,false 只解释对象还在不在。第三列是这次有没有重发顶层 instructions。缓存命中单独看 cached input,推理单独看输出明细。四列用的都是同一次调用,不能收成一个「对话很短所以应该便宜」。

给 OpenAI 账户充值发生在这些列扣款之前。链上把稳定币换成能充进账户的资产,不会把 previous_response_id 变成只计新句子,不会让 store: false 变成退款,也不会把已经串上的历史从下一轮输入里拿掉。需要这一步时,可以用无需注册、非托管的 AllSwap 完成链上兑换。兑换成功只说明资产换了手。链还在,下一句短回复的输入列就还会按整段已有输入计。