从冗余计算到缓存命中:一个成本反转的故事
每次人工智能代理执行一个步骤时,它都会把整个对话历史重新发送给大语言模型。这段历史里包括系统指令、工具定义,还有三个回合前就已经处理过的项目背景。所有这些内容在每个回合都会被重新读取、重新计算、重新收费。
对于长时间运行的代理工作流来说,这种重复计算往往是你整个AI基础设施里最昂贵的开支项。一个两万令牌的系统提示词,运行五十个回合,就意味着有一百万令牌的重复计算被按全价收费,却产生零个新价值。而且这个成本会随着每个用户、每个会话不断叠加。
解决方案就是提示词缓存。但要想用好它,你需要理解底层到底发生了什么。
静态前缀与动态后缀:缓存的核心分界线
在你优化提示词之前,你需要搞清楚哪些东西会变,哪些东西不会变。每个代理请求都包含两个本质不同的部分。
第一部分是静态前缀,它在各个回合之间保持完全一致。这部分包括系统指令、工具定义、项目背景和行为准则。第二部分是动态后缀,它会随着每个回合不断增长。这部分包括用户消息、助手回复、工具输出和终端观察结果。
这种分离正是提示词缓存之所以可行的基础。基础设施会存储静态前缀的数学状态,这样后续那些共享完全相同前缀的请求就可以完全跳过计算过程,直接从内存里读取数据。一旦你从内心接受了这个分界线,这篇文章里的每一个架构决策都会变得显而易见。
预填阶段与解码阶段:变压器的双重面孔
要理解缓存为什么如此高效,你需要知道Transformer在处理你的提示词时到底做了什么。每次大语言模型推理请求都包含两个阶段。
预填阶段负责处理整个输入提示词。它对上下文中的所有令牌运行密集矩阵乘法,以构建模型的内部表示。这个阶段是计算密集型的,因此非常昂贵。解码阶段则逐个生成令牌。每个新令牌被添加到序列中,然后模型预测下一个令牌。这个阶段是内存密集型的,因为它主要是读取历史状态,而不是做大量计算。
在预填阶段,Transformer会为每个令牌计算三个向量:查询、键和值。注意力机制利用这些向量来确定每个令牌与其他令牌之间的关系。任何给定令牌的键向量和值向量只取决于它前面的令牌,而且一旦计算完成,它们就永远不会改变。
键值缓存:扔掉还是保留,这是个成本问题
如果没有缓存,这些键值张量在每个请求之后就会被丢弃,下一个请求会从头开始重新计算它们。对于一个两万令牌的前缀来说,这意味着两万令牌的注意力计算本来完全没必要重复进行。
键值缓存解决了这个问题,它把这些张量持久化在推理服务器上,并通过令牌序列的加密哈希值进行索引。当一个新的请求带着相同的前缀到来时,哈希值匹配成功,张量从内存中加载,这些令牌的预填计算就被完全跳过了。这使计算复杂度从每个生成令牌的O(n²)降到了O(n)。对于一个在两万个令牌的前缀、重复五十个回合的场景来说,这是巨大的计算量削减。
定价结构:九折优惠背后的数学游戏
定价结构使得这个架构决策变得如此重要。缓存读取的价格是基础输入价格的零点一倍,也就是每个缓存令牌享受九折优惠。缓存写入的价格是一点二五倍,需要支付百分之二十五的溢价来存储键值张量。延长到一小时的缓存定价是两倍。
这套数学计算只有在缓存命中率保持高位时才成立。最好的生产环境案例就是Claude Code。Claude Code的整个构建都围绕着一个目标:让缓存保持热状态。
Claude Code实战:三十分钟编码会话的成本账本
从计费角度来看,一个真实的三十分钟编码会话是这样的。第零分钟,Claude Code加载它的系统提示词、工具定义以及项目的CLAUDE.md文件。这个载荷超过两万令牌,而且由于每个令牌都是新的,这是整个会话中最昂贵的时刻。但你只需要支付这一次费用。
第一到第五分钟,你开始给出指令,Claude Code派出探索子代理来浏览代码库、打开文件并运行grep命令。所有这些都被追加到动态后缀中。但那个两万令牌的静态前缀现在是从缓存中读取的,每百万令牌零点三美元,而不是三美元。
第六到第十五分钟,计划子代理收到的是摘要简报而不是原始结果,因为传递原始输出会不必要地膨胀动态后缀。它生成一个实现计划,你批准它,然后Claude Code开始做修改。每个回合都从缓存中读取静态前缀,命中率攀升到百分之九十以上,每次访问都会重置生存时间以保持缓存温热。
第十六到第二十五分钟,你要求修改,这意味着更多的工具调用、更多的终端输出以及动态后缀中积累更多的上下文。到这个时候,会话已经处理了数十万令牌,但每个回合都从缓存中读取了那个两万令牌的基础。
第二十八分钟,你在终端运行斜杠cost命令。如果没有缓存,按照Sonnet 4.5的费率,两百万令牌将花费六美元。而缓存以百分之九十二的效率运行时,一百八十四万令牌是缓存读取,总成本降为一点一五美元。单次任务就节省了百分之八十一。
哈希敏感性:为什么一加二等于三而二加一等于缓存未命中
这就是热缓存的真实面貌。你只需要为静态基础支付一次费用,然后就可以几乎免费地反复读取它。动态尾部是唯一始终按正常费率收费的部分。
关于提示词缓存,最反直觉的一点是:一加二等于三能命中缓存,但二加一却是缓存未命中。基础设施从头开始对整个令牌序列进行哈希计算。如果序列中有任何变化,哪怕只是两个元素的顺序交换,哈希值也会改变,整个前缀就会按全价重新计算。
这不是一个微不足道的实现细节。它是Claude Code中每个工程决策都围绕的核心约束。以下是一些在生产环境中破坏缓存的真实例子:注入系统提示词的时间戳导致每个请求都生成唯一的哈希值;一个JSON序列化器在不同请求之间对工具模式键的排序不一致导致前缀失效;一个代理工具在会话中途更新了参数,导致整个两万令牌缓存被清空。
三条铁律:不要修改工具、不要切换模型、不要改变前缀
根据这些教训,可以得出三条规则。第一条,在会话期间不要修改工具。工具定义是缓存前缀的一部分,所以添加或删除任何一个工具都会使下游所有内容失效。第二条,永远不要在会话中途切换模型。缓存是模型特定的,这意味着在对话中途切换到更便宜的模型需要从头重建整个缓存。第三条,永远不要为了更新状态而改变前缀。Claude Code的做法不是编辑系统提示词,而是将提醒标签追加到下一条用户消息中,这样前缀就保持原样。
无论你是直接使用Claude Code还是从头构建自己的代理,这些规则同样适用。
提示词结构设计:从系统指令到工具输出的分层布局
按照以下顺序组织你的提示词:
- 系统指令和行为规则放在最顶部,不要在会话中途更改它们。
- 预先加载所有工具定义,不要中途添加或删除。
- 接下来放置检索到的上下文和参考文档,让它们在会话期间保持稳定。
- 对话历史和工具输出放在最底部,这就是你的动态后缀。
在Anthropic API上启用自动缓存后,随着对话的增长,缓存断点会自动向前推进。如果没有这个功能,你需要手动跟踪令牌边界,而错误的边界意味着完全错过缓存。
上下文压缩与缓存安全分叉
当你的上下文接近限制需要进行压缩时,使用缓存安全的分叉技术。保持相同的系统提示词、工具和对话历史,然后将压缩指令作为一条新消息追加。缓存前缀被重复使用,唯一计费的新令牌就是压缩指令本身。
监控指标:缓存创建令牌、缓存读取令牌与输入令牌
要验证你的缓存是否正常工作,请监控每个API响应中的三个字段。
- cache_creation_input_tokens是写入缓存的令牌数。
- cache_read_input_tokens是从缓存中服务的令牌数。
- input_tokens是没有经过缓存处理的令牌数。
你的缓存效率计算公式是cache_read_input_tokens除以cache_read_input_tokens加上cache_creation_input_tokens的总和。像跟踪系统正常运行时间一样跟踪这个指标。
架构纪律:提示词缓存不是开关而是设计哲学
提示词缓存不是一个可以随意开关的功能。它是一种你需要围绕它来设计的架构纪律。核心思想很简单:组织你的提示词,让静态内容位于顶部,动态内容在底部不断增长。基础设施对前缀进行哈希计算,存储键值张量,然后在你每次后续读取时给你九折优惠。
但纪律体现在细节中。不要在系统提示词中注入时间戳,不要打乱工具定义的顺序,不要在会话中途切换模型,不要改变缓存断点上游的任何东西。Claude Code展示了这种设计在大规模下的效果,百分之九十二的缓存命中率和百分之八十一的成本降低。如果你正在构建代理系统却没有围绕提示词缓存来设计,那你就是在把大部分利润空间白白扔掉。