核验清单

  • ✓打开这一次请求实际提交的 messages。里面若还有上一轮的用户句和模型回复,输入就不是新打的那一句。
  • ✓用这一次响应里的输入 token 数对分档,不要用聊天框里最后一句的字数。流式调用要打开 include_usage,空 choices 那一块才有 usage。
  • ✓过线之后,按价格页的规则看整单是不是都落在高档。不要只把超过档线的那一截乘高价、线以下仍乘低价。
  • ✓若返回里有缓存命中,只把它当成另一列单价。命中不删除历史,也不等于这一轮还停在低档。
  • ✓滚动摘要是另一次调用。它有自己的输入和输出。不要把它当成上一轮账单的退款。

1. 窗口记得,接口不记得

通义千问多轮对话把前提写在开头:API 是无状态的,不会保存对话历史。要实现多轮,每次请求都要显式传入历史消息。工作方式是维护一个 messages 数组。每一轮都把用户的新提问和模型的回复追加进去,整份数组成为下一次请求的输入。第二轮看起来只问了一句「导演是谁」,提交给模型的却是第一轮的问题、第一轮的回答,再加上这句新问题。

产品界面把这个数组藏起来了。用户看见的是气泡,账单看见的是数组。所以「我只补了一句」和「这一次输入只有一句」不是同一件事。前几轮已经各自结过一次账,那些 token 不会因为你又把它们贴回来而退掉。贴回来的后果发生在这一轮:它们重新成为这一次的输入。文档后文写的是,发起多轮对话时,messages 部分会重复计算并计费。

火山方舟的对话接口用另一个字段名说同一类计量。方舟 Chat API 里 usage.prompt_tokens 是输入给模型处理的内容的 token 数量。你这次把哪些历史放进消息,这一列就按哪些内容计。方舟另有 session 缓存接口,那种模式可以只传最新一轮、用 context_id 指向已经存下的上下文。那是另一条接口。它不把 Chat API 里你亲手贴回去的历史变成免费,缓存输入和存储也不在这一列里抵消。对账先看你实际打的是哪条接口。

2. 过了线,整单换档

历史变长之后,贵的往往不只是「多出来的那几百 token」。百炼模型价格写明,部分模型实行阶梯计费。单价取决于单次请求的输入 token 总量。该请求的所有 token 均按对应阶梯的单价结算。文档自己举的例子是:两档为大于 0 且不超过 32K,以及大于 32K 且不超过 128K。输入 100K 时,因为落在第二档,所有 token 都按第二档单价结算。不是前 32K 仍按低档、只有超出的 68K 按高档。

这就是短句把整单抬上去的原因。上一轮结束时,输入也许还在低档。你把那些消息再贴回来,再加一句很短的追问,总量跨过档线。跨过之后,连同本来可以留在低档的那一段历史,这一轮都按高档计。档线本身会随模型改,价格页上的具体边界和单价以控制台为准,本文不把某一款的元价写死。要核对的是规则:分档看的是这一次请求的输入总量,过线作用于这一次请求的全部 token。

同一页还写了一条容易和半价搞混的限制。模型若支持批量调用,输入和输出可以按实时推理价格的一半计。模型若支持上下文缓存,折扣只发生在输入。两者不能同时生效。批量那一半是另一篇文章里的成功行和思考 token,不能拿来把已经跨档的这一次实时请求拉回低档。思考链记在输出侧,也不改变「输入总量决定档位」这一句。

3. 命中改的是单价,不是总量

多轮文档建议优先用支持上下文缓存的模型,并点名 qwen-max、qwen3.8-max 等。它说的是降低成本、提高速度,缓存自动开启,不用改代码。降低不是删除。messages 仍会重复计算并计费。命中的那一段改走缓存单价。价格页写明,价格表里的输入单价不含缓存单价。所以你在表上看到的低档输入价,不能直接乘到命中的那一段上,也不能把命中理解成「这一轮只剩下新打的几个字,所以还在低档」。

两列要分开读。第一列是这一次提交的输入总量,用它看落在哪一档。第二列是命中的 token,用它看那一段按缓存单价而不是表上的标准输入价。未命中的部分仍按这一档的输入价。显式缓存还会在创建时按另一套比例计,并且可能另有存储时钟。那些比例、例外模型和按小时的存储,上下文缓存和显式缓存两篇文章已经分开写了。本文只钉住一件事:命中不把历史从 messages 里拿掉,也不把「整单按所在档结算」改成「只结算新的一句」。

流式响应更容易把这两列看丢。多轮文档的流式示例要把 include_usage 打开,usage 出现在 choices 为空的那一块。不打开,你只能看见正文,看不见这一次的输入总量,更看不见其中有多少命中。用最后一句的字数去除以一个经验换算,会稳定地小于账单。

4. 截断和摘要都不是退款

多轮文档给生产环境的办法是管住数组长度,不是让接口开始记性。上下文截断保留最近 N 轮。下一轮提交的输入变短,有机会落回低档。被丢掉的早先几轮不会从已经出账的请求里退出。你失去的是模型还能看见的早期事实,换来的是下一次请求的总量变小。

滚动摘要更贵一点,因为它自己就是一次调用。文档的做法是,历史长到上下文上限的大约七成时,把较早的一半单独拿出去,发起一次独立 API 调用,让模型写成记忆摘要,再用这段摘要替换冗长历史,拼上最近几轮。这次摘要调用有自己的输入总量和输出。它可能自己就落在某一档。它缩短的是下一次聊天请求,不是把前面每一轮已经按当时档位结掉的账冲销掉。摘要写丢的细节,下一轮也不会免费想起来。

向量召回是第三条路。每轮结束后把对话放进向量库,提问时只检索相关的几段,再和最近的输入拼在一起。拼进去的片段仍然是这一次的输入,仍然计入总量,仍然参与分档。库本身的规格和空闲小时是知识库那篇文章的时钟。召回避免的是把整段线性历史每次都贴回去,它不避免「这一次真正提交的内容要计输入」。

5. 充值缩不短这段历史

对账按四列走。第一列是这一次提交的 messages 里有没有历史,而不只是屏幕上的新句子。第二列是这一次的输入 token 总量落在哪一档,过线后是整单换档,不是只给超出的一截加价。第三列是缓存命中:它改单价,不删除历史,批量半价也不能和它叠在同一次请求上。第四列是你用来缩短下一轮的手段:截断丢掉早期上下文,摘要另计一次调用,召回只把检索到的片段算进这一次输入。

给百炼或方舟账户充值,发生在扣款之前。链上把稳定币换成能充进账户的资产,不会把 messages 缩短,也不会把已经越过的分档线拉回来,更不会把一次滚动摘要变成退款。需要这一步时,可以用无需注册、非托管的 AllSwap 完成链上兑换。兑换成功只说明资产换了手。这一轮的输入总量该落在哪一档,还是看你交上去的那份历史。