一句 continue 背后的完整请求
继续上一章的 coding agent 任务:“定位失败测试、修改实现、运行完整测试并交付结果”。
第一轮开始时,Agent 收到系统约束、工具定义和项目指令。它读取失败测试与实现文件,运行测试,再把文件内容、命令和测试输出写进历史。假设它改完一处实现后暂停,用户只输入一句 continue。产品界面上,本轮新增内容只有一个英文单词;发给模型的请求却更接近:
[system][tools][project][history][tool results][continue]
系统提示告诉模型如何行动,工具定义描述可以怎样读文件、改代码和执行命令,项目指令限定代码规范;history 保存此前的对话与工具调用,tool results 则可能装着长测试日志和多个文件。continue 只是附在所有这些内容末尾的新后缀。模型不会仅凭这个词凭空找回工作状态,应用必须把继续任务所需的上下文重新组装进本轮输入。
这就是 Context Bill 的起点:界面上的“新消息长度”不等于模型实际接收的“请求长度”。一轮请求可能携带很大的旧前缀,只新增极短的后缀。接下来要问的不是“continue 有几个 token”,而是这份完整输入中,多少内容需要重新处理,多少内容在写缓存,多少内容命中缓存,以及模型最终生成了多少输出。
Prefill 和 decode 是两种不同的工作
一次生成可以粗分为两个阶段。
输入预填(prefill),是模型处理整份输入、为各层注意力建立后续生成所需状态的阶段。它面对的是系统提示、工具、项目指令、历史、工具结果和本轮新输入的总和。输入越长、可复用部分越少,服务端通常需要承担越多 prefill 工作。
逐 token 生成(decode),是模型基于已建立的状态,一个 token 接一个 token 地生成答案的阶段。Agent 可能输入了 100k token,却只回复一个短确认;也可能输入不长,却生成详细补丁说明。两种情况的输入处理与输出生成负担并不相同。
因此,延迟也要分开看。首 token 延迟(Time to First Token,TTFT)关注从请求发出到第一个输出 token 到达的时间,其中会包含排队、路由、输入处理和服务框架等环节;生成吞吐则更关注开始输出以后,token 以多快的速度持续产生。一个请求首 token 慢,不代表它后续生成一定慢;反过来也一样。讨论 Agent “变慢”时,至少应先确认慢在首 token 之前,还是慢在输出过程中。
回到 coding agent:读取完整测试输出主要扩大输入侧,首先影响的是下一轮需要处理的上下文;让模型写一份很长的失败原因与交付报告,则会增加 decode 阶段的输出。把两者统称为“token 太多”,会让优化动作错位。前者更值得检查前缀能否复用、日志是否需要原样保留,后者则要判断输出长度是否真的服务于交付。即使两轮总 token 恰好相同,它们的时延形态和成本组成也可能不同。
KV Cache 和 Prompt Cache 分别省掉什么
KV Cache 是注意力计算中的中间状态缓存。在一次自回归生成里,模型可以保存既有 token 的 key/value 状态,让生成下一个 token 时不必从头重算此前全部 token;Hugging Face 的机制说明展示了这个基本作用。推理系统也可能用不同的 KV Cache 实现承载可复用前缀;Hugging Face 的 cache strategies 文档展示了这些实现在内存、延迟、offloading 和编译兼容性之间的不同取舍,不能只看是否叫“缓存”。
Prompt Cache 则是 Provider 对外提供的产品能力:当不同 API 请求共享符合规则的前缀时,服务端复用已经处理过的前缀,并用缓存读取、缓存写入等用量字段呈现。它对调用方暴露的是前缀匹配、有效期、路由与计费规则,而不是让应用直接操纵每一层的 K/V 张量。
两者有关,但不能画等号。KV Cache 回答的是“注意力中间状态如何避免重复计算”;Prompt Cache 回答的是“跨请求的已处理前缀,Provider 允许怎样复用、观测和计费”。Prompt Cache 的底层实现可能利用 KV 状态或其他服务机制,但应用不能从一个产品字段反推出唯一实现。完整 Q/K/V 推导可延伸阅读 LLM101 的 Prompt Cache 长文。
还要区分生命周期。一次生成内部的 KV 状态通常随着该生成过程增长;跨请求是否还能复用,要看推理服务有没有前缀缓存机制,以及缓存是否仍在、能否路由到、前缀是否匹配。Prompt Cache 则把其中一部分条件包装成 API 契约,但不同 Provider 对最小可缓存长度、断点、TTL 和命中字段的定义可以不同。“上一轮生成时有 KV Cache”不能推出“下一轮 API 请求一定是 Prompt Cache hit”。

把一次请求拆成四笔 token 账
为了比较不同 Provider 和部署形态,可以先把一轮请求规范化为四类 token:
U:未缓存输入(uncached input),本轮必须按普通输入处理的 token;W:缓存写入(cache writes),本轮处理后写入可复用缓存的 token;R:缓存读取(cache reads),本轮从缓存复用的前缀 token;O:输出(output),按 Provider 的 usage 与计费定义归入输出的 token。可见回复很短时,API 仍可能另行报告或计费 reasoning/thinking token,具体应按该 Provider 的字段口径映射。

用每一类的单位价格表示,一轮的直接 token 成本可以写成:
C_turn = U × P_input + W × P_write + R × P_read + O × P_output
这不是任何一家 API 的账单原样,而是一张归一化工作表。Provider 对 usage 字段的切分、命名和归属不同:有的把缓存读取放在输入明细里,有的单列缓存创建与读取,有的还区分不同有效期。使用公式前,必须先把实际响应字段映射到 U/W/R/O,确认各字段是否互斥、总输入是否已经包含缓存 token,再代入对应价格,避免重复相加。
最常见的错误,是看到 input_tokens 后又直接加上缓存读取字段,却没有先确认前者是总输入还是未缓存输入;另一个错误,是把“命中 token 数”乘以普通输入价,随后又加一笔读取价。正确做法是保留 Provider 原始 usage 响应和模型版本,写清映射规则,再让报表统一输出四个互斥桶。若某个 Provider 没有暴露独立写入量,就不要凭总输入反推一个看似精确的 W;应明确标记为不可观测或按其官方口径合并。
自托管部署也能使用这张表,但不能把 GPU 时间、显存或缓存容量、吞吐损失直接当作同一种标量 P 相加。若团队能把它们统一折算为货币或容量机会成本,才可以代回标量公式;否则应保留多维成本向量,分别观察 GPU 时间、显存占用与吞吐。缓存驻留还带有时间维度,例如一段前缀占用多少显存、持续多久,而不只是每 token 的价格。此时模型的价值不是复刻 Provider 账单,而是让“重新做 prefill”和“保留并读取缓存”进入同一个容量决策。
为什么首 token 会越来越慢
在前面的任务里,Agent 每读取一个文件、执行一次工具、保留一段测试输出,后续请求都可能更长。如果旧前缀没有被复用,服务端通常要在返回第一个 token 前完成更多输入处理。因此,TTFT 往往会随着实际 prefill 工作增加而上升;一条只有 continue 的新消息,也可能因为背后挂着大量未命中前缀而等待很久。
但“输入 token 翻倍,TTFT 必然按固定比例翻倍”并不是可靠规律。TTFT 同时取决于 serving stack、并发负载、排队和路由、硬件、批处理、模型实现以及缓存是否命中。即使输入长度相同,一次命中与一次未命中的实际 prefill 量也可能完全不同;即使缓存用量相同,线上负载也可能改变等待时间。
所以应并列记录输入组成、缓存读写量与 TTFT,最好再区分客户端网络时间和服务端时间。只画“总输入 token—TTFT”散点图,很容易把缓存失效、路由变化或负载尖峰误判为长上下文本身的必然代价。
一次更有解释力的实验会固定模型、区域和请求结构,分别发出“冷前缀”和“相同前缀追加新后缀”的请求,同时记录 TTFT 与 usage。它仍不是硬件性能定律,却能回答当前生产链路中最重要的问题:同样的 100k 逻辑上下文,服务端实际把多少当成重复 prefill,多少当成缓存读取。只有确认这一点,缩短输入、调整缓存断点或改变路由才有明确对象。
用一个 100k 上下文算例看差异
下面只计算输入侧,并把基础输入单价记为 1 个“base-input-price unit”。场景是:稳定前缀 100k token,本轮新增后缀 1k token。输出 O 在三种方案中假设相同,作为独立项暂不计入,避免输出长度掩盖缓存差异。
- A,完全未缓存:
U=101k,输入侧为101k × 1 = 101kunits。 - B,首次写入 5 分钟缓存:
W=100k,按1.25×写入;另有U=1k。输入侧为100k × 1.25 + 1k = 126kunits。 - C,在 TTL 内的后续命中:
R=100k,按0.1×读取;另有U=1k。输入侧为100k × 0.1 + 1k = 11kunits。
这里的 1.25× 与 0.1× 是截至 2026-07-26 Claude API Pricing 对文档覆盖场景中 5 分钟缓存写入和缓存读取的价格系数,不是所有模型、渠道或 Provider 的通用规则。B 比 A 贵 25k units,说明写缓存本身可能有溢价;只有任务生命周期内出现足够多的后续命中,这笔前期投入才会摊薄。若 Agent 写完缓存就结束、命中前 TTL 已过,或者下一轮改变了前缀,缓存方案可能没有回本。
在这个刻意简化的算例里,一次 C 式命中相对再次走 A,可节省 90k units,足以覆盖首次写入相对 A 多出的 25k units;但这只是“前缀确实相同、在 TTL 内命中、输出相同”的两轮比较。真实任务还会改变工具定义、压缩历史或跨越有效期,不能把“一次就回本”写成架构保证。应当用真实任务的命中分布计算,而不是只看最理想的一对请求。
同样截至 2026-07-26,OpenAI Prompt Caching 官方文档说明:GPT-5.6 及后续模型家族仍默认使用隐式自动 cache breakpoints,也支持显式 breakpoints;其差异包括 cache_write_tokens 字段、按未缓存输入价 1.25× 计费的缓存写入,以及 prompt_cache_options.ttl=30m 所表达的至少 30 分钟最短缓存生命周期。文档覆盖的更早期受支持模型沿用既有自动缓存路径,该旧方案不另收缓存写入费,并使用另一套 prompt_cache_retention 行为。上述能力和选项都要按文档中的具体模型范围理解,不能假设所有模型支持相同配置。耐久的分析方法仍是先把当时的 usage 字段映射成 U/W/R/O,再读取对应价格与有效期,而不是把某一家的倍数或旧模型 retention 经验横向套用。
不要只比较单 token 单价
“缓存读取更便宜”只是局部结论。一个 Agent 任务是否经济,要同时看:
- **TTFT:**用户每次等待第一个 token 多久,缓存命中后是否真的改善;
- **重试与失败:**删掉关键项目约束或测试结果,是否导致误改、重读文件、重跑测试;
- **任务质量:**更短的输入是否仍足以正确定位问题并完成完整测试;
- **缓存建立成本:**写入是否有溢价,是否占用自托管缓存容量;
- **命中频率:**稳定前缀在有效期内能被后续多少轮真正复用;
- **任务生命周期:**这是一次性问答,还是要经历探索、修改、测试与交付的长任务。
还要区分单轮成本与任务总成本。A 方案可能让某一轮账单更低,B 方案首次写入可能更贵;但若后面有多次 C 式命中,任务总成本和累计等待可能更低。反之,若为了命中而保留错误或过时信息,造成失败重试,便宜的读取也会输给正确完成任务。《Don’t Break the Cache》提供了长时程任务的实证证据,但结论受论文所选模型、基准、工作负载与策略约束,不能用一个节省比例替代产品自己的线上测量。
给 Agent Builder 的检查清单
产品经理和应用工程师可以用下面的问题审查一条真实链路:
- 抓取一轮完整请求:
system/tools/project/history/tool results/new input各有多少 token?界面展示的新增消息占比是多少? - 在响应 usage 中找到未缓存输入、缓存写入、缓存读取与输出字段;它们如何映射到
U/W/R/O,总量字段是否会造成重复计算? - 记录模型、Provider、区域、路由、缓存断点、TTL 与请求时间。两次请求的“稳定前缀”是否真的逐 token 一致?
- 分开记录客户端 TTFT、输出 token 数、首 token 后的生成耗时与端到端耗时;这些数据只能判断异常主要出现在首 token 之前还是之后。没有服务端分段指标时,根因应标为未知,不能直接归因于 prefill、排队、路由或网络。
- 计算首次写入相对未缓存的增量成本;按实际任务轮数与 TTL,至少需要多少次命中才能回本?
- 统计一个完整任务的读取文件次数、工具调用、失败重试、完整测试运行和最终是否正确交付,不要只优化最便宜的一轮。
- 做一次结构实验:固定模型和负载,保持 100k 前缀不变只追加后缀,再故意改动前缀;对比
U/W/R与 TTFT,验证缓存是否按预期工作。 - 自托管时,分别记录 prefill GPU 时间、KV 缓存显存及驻留时长、驱逐率和吞吐;决定是统一折算为机会成本,还是保留多维指标,确认省下的计算没有转化成不可接受的容量占用。
完成这张检查表,你就能把“一句 continue 怎么这么贵”改写成可排查的问题:是哪部分前缀没有复用,成本落在写入、读取还是未缓存输入,等待发生在 prefill、排队还是 decode,以及这一轮的选择是否降低了整个任务的总成本。
上一篇:Reading Guide:Agent 为什么需要上下文经济学
下一篇:Stable Prefix:缓存命中首先是一个架构问题