做多轮Agent的人都在暗地里算一笔账:一轮对话跑下来,token消耗比单轮暴涨好几倍,而每次请求都要把几十条历史消息重新塞进上下文窗口。这些重复发送的prompt,每一千个token都在悄无声息地烧钱。OpenRouter最近把牌摊开了——Prompt Caching配合Sticky Routing,缓存读取价格直接打到正常输入的十分之一甚至更低,而且他们用session_id解决了缓存命中率最致命的漂移问题。
缓存不是新故事,但把账算到这个份上的不多
0.1倍价格意味着什么
先看一组让财务血压下降的数字。以Claude Sonnet 4.6为例,正常输入价格是每百万token 3美元。开启Prompt Caching后,缓存命中的读取价格暴跌至0.30美元每百万token——只有十分之一。其他模型的比例更夸张,部分模型缓存读取甚至低至正常价格的0.1倍。这意味着你精心设计的长system prompt、反复出现的工具定义、不变的历史对话前缀,一旦被缓存命中,它们的成本直接少了一个零。
这笔账摊到多轮Agent场景里会怎样?假设一个Agent每次对话携带5000个token的固定前缀,每天跑10万次请求。不用缓存时,这5000 token每次都要按输入价格计费;启用缓存后,只有首次写入付费,后续命中的请求只按缓存读取价格计算。日结账单会从一个让团队需要特批的数字,降到可以挂在个人信用卡自动扣款都不心疼的水平。
省钱的幻觉和命中的残酷
但缓存有一个所有工程师都躲不开的痛点——命中率。你能把价格降到一折,但如果十次请求只有一次命中,真实平均成本接近原价。OpenRouter的Sticky Routing就是为了解决这个问题的。它的逻辑很直白:把同一个session的连续请求路由到同一台缓存有先前计算结果的机器上,避免请求跳来跳去导致缓存失效。在传统负载均衡模型里,请求是均匀打散的,缓存更像是碰运气;Sticky Routing则把缓存从概率游戏变成了确定性系统。
实现上是通过一个session_id参数完成的。开发者在请求时传入相同的session_id,OpenRouter的路由层就知道这几条请求属于同一个对话线路,应该被“粘”在同一个计算节点上。不需要自己架设Redis缓存层,不需要在应用代码里手动管理缓存键——session_id就是你买到确定性的唯一凭证。对于长期运行的Agent、持续性上下文的对话系统,这个参数不是可选项,是必选项。
Sticky Routing到底解决了什么真问题
路由漂移:那个让缓存形同虚设的幽灵
在没有Sticky Routing的缓存方案里,缓存写入节点A的上下文,下一次请求可能被负载均衡随机分配到节点B。节点B上没有之前的缓存,只能把整个prompt重新计算一遍,然后可能写入节点B的缓存,第三次请求又跳到节点C。系统确实在努力缓存,但缓存始终在追逐请求,而不是服务请求。这种现象通常被称为缓存漂移,它在高并发环境下尤其严重——请求越多,分布越散,命中率越低。OpenRouter的Sticky Routing实质上是用路由层的一个简单约定,干掉了这个幽灵。
这听上去不算高级技术,但能把这件事产品化成“传一个session_id就行”的体验,背后是路由表和缓存生命周期管理的深度整合。开发者不需要关心底层是哪个节点,不需要处理节点宕机后的缓存迁移策略,OpenRouter在路由层保证了session_id和计算资源的亲和性,同时在后端做了故障转移时缓存重建的策略。所谓“做Agent的人该抄作业”,抄的就是这一层封装。
什么场景下缓存会失效,以及你该知道的事
Sticky Routing不是万能药。当上下文变化超过缓存窗口的固定前缀部分,或者模型的缓存策略认为内容不再可重用,缓存依然会miss。以Claude系列为例,缓存的粒度是前缀匹配——如果system prompt前面加了哪怕一个空格,整个缓存都会失效。动态插入的时间戳、每次请求都变化的部分,必须放在prompt的最末尾,否则前面的缓存也会被一并破坏。OpenRouter在教程里没有藏着掖着这些细节,而是直接给出最佳实践:把不变的内容放在最前面,可变内容放在最后面,并且用固定边界把二者切开。
另外,缓存有TTL限制,通常在5到30分钟之间,视模型和负载而定。长时间闲置的会话,缓存会自动被回收,下一次请求会触发一次全量写入。对于需要长时间挂起的Agent,这意味着设计会话结构时,要么用定时心跳维持缓存活性,要么接受定期出现的全价写入成本。这些都不是OpenRouter独有的限制,而是任何基于前缀缓存的推断服务都会面临的问题,但他们把文档写清楚了,这一点值得给分。
Agent成本结构重塑:从吞金黑洞到可控变量
扔掉那些重复发送的包袱
多轮Agent之所以成本爆炸,很大程度上是因为每次推理都要把整个对话历史、工具描述、安全规则、格式约束重新发给模型。这些内容是推理的前提,但它们在多轮交互中几乎不变。传统API调用模式下,这些不变的部分每一轮都要计费,工程师只能通过截断历史、压缩prompt来苟延残喘地控制成本,而这往往以牺牲Agent的上下文理解能力为代价。
OpenRouter这套组合拳直接改变了计算模式:不变部分的成本从O(n)变成O(1),其中n是交互轮次。你甚至可以在system prompt里放一本中等厚度的产品手册,只要它不变,后续每轮读取成本几乎可以忽略。这对RAG场景、个性化Agent的长期记忆、复杂工具编排的多轮决策,都是结构性利好。以前抠抠搜搜不敢放的上下文,现在可以重新放回去了。
工程侧该动起来的几件事
第一件事,改造你的Agent请求层。把所有可以固定的、前缀性质的内容提取到prompt最前面,严格保证字节级一致——包括注释、缩进、标点。第二件事,为每个会话生成并保持唯一的session_id,在对话整个生命周期内复用。如果你用的是OpenRouter的SDK,这很可能只是一个额外的参数设置;如果用的是自己封装的HTTP客户端,就需要在请求头上传OpenRouter-Session-Id字段。第三件事,监控缓存命中率。OpenRouter会在响应中返回缓存命中情况的元数据,用这个数据建立dashboard,不要让缓存成为黑箱。
更重要的是重新设计Agent的上下文结构。缓存机制奖励的是“前缀高度稳定、变化只发生在尾部”的数据布局。如果你的Agent需要在prompt中间插入动态内容,缓存就会从插入点之后全部失效。把动态内容推到末尾,用标记或分隔符隔离,是优化缓存命中率最有效的一次性改造。这些改造不需要改变业务逻辑,只需要调整prompt的拼接顺序,但能带来立竿见影的成本下降。
写在最后:工具在说话,别当没听见
Prompt Caching不是新概念,OpenRouter也不是第一个做这件事的平台。但他们把价格标签贴到了最显眼的位置,把Sticky Routing做成了传一个参数的简单动作,让这件事从“理论上你可以优化”变成了“不这么做就是在白扔钱”。缓存读取最低一折的数字就摆在那里,Claude Sonnet 4.6的0.30美元对比3.00美元,不需要更多论证。
做Agent是一场持久战,成本是决定战线能拉多长的基础参数。OpenRouter这次拿出的不是新模型,不是新架构,而是一个工程化的成本控制方案。它解决的问题一点都不性感——重复数据不再重复计费,请求不再随意漂移。但就是这样不性感的东西,能在月度账单上给你腾出继续试验、继续迭代的空间。该抄的作业,已经写好了,只差你去读。

