每次都从头读一遍
第一章讲了这个循环:模型在两次调用之间什么都不保留,所以外壳程序(harness)每次都要把整段对话发给它。在一个小游戏上,这只是寥寥几次调用。本章看一次更长的会话。我们从 Black(一个格式化 Python 代码的工具)的问题跟踪器里找了一份真实的 bug 报告,连同 Black 的源代码一起交给 Claude Code。报告的标题是:
这次会话共调用模型 次,用时 秒,录制于 ,模型是 。对话从第一次调用时的 个 token(词元,一个词或词的一部分)涨到最后一次的 个。把所有调用加起来,模型一共读了 个 token。
这些读取大多来自缓存(cache):模型提供方为它最近见过的文本存下的一份副本。API 为每次调用报告的用量,把模型读取的内容分成两部分:从这份副本读取的 token,和新的 token。下图逐次调用画出这两部分。
从第二次调用起,上一次调用读过的内容几乎都能在缓存里找到。只有之后新加的部分是新的:每次 到 个 token。整个会话里,模型读取的内容有 来自缓存。
就连第一次调用读取的 个 token 里,也有 个来自缓存。这份录制说明不了它们从哪里来,很可能来自我们当天早些时候用 Claude Code 录制的其他会话。
缓存让价格降了多少
从缓存读取的 token 不是免费的,但很便宜。Anthropic 的定价页面列出了 每百万 token 的价格:正常输入 ,从缓存读取的输入 ,模型写出的内容 。从缓存读取一个 token,价格是正常价格的 。
录制记录里只有整个会话的总费用:。用这些价格和每次调用的 token 数,我们算出 ,相差不到一美分。所以下面每次调用的费用是我们根据公布的价格算出来的,并用这个总额核对过,录制里并没有记下它们。
如果没有缓存,同样这些调用要花 ,是现在的 倍。会话越往后,差距越大,因为每次调用都要读整段对话,而对话只会越来越长。有了缓存,模型写出的 个 token 反而比它读取的全部内容还贵,占总费用的 。
先得写进去
缓存里只有放进去的东西。在上面的图里,每次调用新增的 token 就是写入缓存的部分,下一次调用才能读到它们。
写入分两种,定价页面分开列出:一种副本保留 分钟,每百万 token ;另一种保留一小时,。这次会话的写入全是保留一小时的那种,共 个 token,较短的那种是 个。
第一章的录制里能看到一次写入被读回来。在第 次运行中,第一次调用从缓存读取了 个 token, 个全是新的。第 次运行在第 次结束 分钟后开始。它的第一次调用读取的 个 token 里,有 个来自缓存,很可能就是第 次运行写入的那份副本。
什么会让缓存失效
为了看清缓存什么时候会没有命中,我们在 自己做了几个小实验。一个小文件夹里放了 Python 标准库的四个文件,Claude Code(模型是 )读了这些文件,并逐个做了介绍。然后我们问了几个简短的问题。每个问题都重新启动一次 Claude Code,恢复同一段对话,每次只改一个地方。我们让模型不用任何工具直接回答,所以每个问题只有一次调用。
多加的那条指令没有让缓存失效,不过录制记录也说明不了它到底有没有传给模型。换模型就是另一回事了: 在缓存里几乎找不到这段对话,只好把大部分内容重新写入。换回 之后,缓存里还留着之前那份副本。这只是同一天里、在一段小对话上问的四个问题,只能说明发生了什么,说明不了原因。
下一章
第四章:压缩
这次会话在对话长到模型装不下之前就结束了。真到了那一步,Claude Code 可以对对话进行压缩(compaction):用一份更短的摘要替换整段对话。会话结束后我们让它压缩了一次, 个 token 的对话变成了 个 token 的摘要。这份摘要是新的文本。在我们的实验里,压缩之后的第一次调用没有从缓存读到任何内容,把整段对话重新写入了一遍。摘要留下了什么,智能体又忘了什么?