大模型应用的成本,往往不是由单次请求决定的,而是被多轮对话、工具调用和不断增长的上下文放大。一个 Agent 每运行一轮,通常都要把系统提示、历史消息、工具说明以及本轮输入重新提交给模型。如果其中大部分内容没有变化,却每次都按未缓存输入计费,长会话的成本会很快失控。
提示词缓存解决的不是“少发请求”,而是让服务商识别重复的输入前缀,并对缓存命中的部分采用不同的计费方式。它对模型输出没有直接影响,真正需要改造的是消息组织方式:把稳定内容放在前面,把会变化的内容放在后面,并避免在已建立的历史前缀中间插入动态数据。
先理解:缓存命中依赖稳定前缀
大多数提示词缓存机制都围绕“重复的输入前缀”工作。可以把一次请求拆成三部分:
- 稳定前缀:系统提示、角色设定、工具定义、固定规则和不会变化的历史消息;
- 动态后缀:本轮用户问题、临时通知、当前状态和最新工具结果;
- 输出部分:模型生成的回答或工具调用。
理想情况下,下一轮请求会复用上一轮已经处理过的前缀,只重新计算发生变化的部分。这里的“相同”不能只理解为语义相同,消息顺序、角色、文本内容和序列化结果都可能影响命中。哪怕只是把一条消息插入历史中间,插入点之后的内容也可能不再属于同一个缓存前缀。
不同模型服务商对缓存的触发条件、最小 token 数、有效期和价格定义并不完全一致。因此,不能把某一家服务商的折扣比例直接套到所有 API 上。实际接入时应以当前模型的计费文档和控制台账单为准,并通过请求日志确认是否真的产生了 cache hit。
三类写法最容易让缓存失效
1. 把临时信息写进正式历史
任务进度、后台推送、当前时间、重试次数和一次性的“继续执行”指令,只对当前请求有用。如果它们被永久追加到 conversation history,下一轮的前缀就会不断变化。
更稳妥的方式是把这类内容作为本轮临时消息注入:请求发送时加入,持久化历史时不写入。这样模型能看到最新状态,下一轮仍可以从稳定的历史前缀开始。临时消息是否使用 user 角色要遵循所用模型接口的消息规范,重点是区分“发给模型的本轮上下文”和“需要长期保存的会话历史”。
2. 把程序内部字段混入请求
应用常会给消息附加时间戳、追踪 ID、清理标记或内部状态字段。这些字段可以用于程序内部管理,但不应直接参与发给模型的消息结构。
可以在请求出口统一清洗:
def strip_internal_fields(messages):
internal_keys = {"_timestamp", "_ephemeral", "_trace_id"}
cleaned = []
for message in messages:
cleaned.append({
key: value
for key, value in message.items()
if key not in internal_keys
})
return cleaned如果内部字段会改变序列化结果,或者被转换成文本后进入提示词,它们就可能破坏原本稳定的前缀。清洗应放在统一的 API 适配层,而不是依赖每个业务模块自行记住这条规则。
3. 每轮重建 system prompt
系统提示通常包含角色、工具使用约束、输出格式和安全规则,适合在会话开始时确定。不要为了传递当前时间、任务状态或最新统计数据,每轮都重写 system prompt。
动态内容可以放在独立的用户消息、工具结果或结构化状态中。系统提示确实需要变更时,应把它视为一次新的缓存阶段,而不是期待旧前缀继续命中。对多租户应用来说,还要注意不要把用户 A 的私有内容放进可跨会话复用的公共前缀。
Agent 应该怎样组织消息
一个较实用的消息管线可以分为四层:
- 固定层:系统规则、工具 schema、应用级说明;
- 持久层:已经确认且不会频繁改写的对话历史;
- 临时层:当前任务状态、外部通知和本轮补充信息;
- 请求层:发送前的字段清洗、序列化和供应商适配。
在代码上,建议把“生成上下文”和“持久化上下文”分开。前者负责让模型获得完成任务所需的信息,后者只保存下一轮仍然有价值的内容。不要先把所有消息写入数据库,再从数据库原样拼请求;这种做法很容易把时间戳、调试字段和一次性事件带进稳定前缀。
还可以在日志中记录以下指标:请求总输入 token、缓存命中 token、缓存未命中 token、上下文版本号和 system prompt 哈希。这样才能区分“代码看起来保持稳定”和“服务商实际判定为命中”。日志中不要记录用户隐私或完整提示词,可只保存长度、哈希和必要的计量字段。
成本优化不能只看折扣
提示词缓存通常能降低重复输入的费用,但它不是无限期存储,也不代表所有输入都会命中。以下情况需要单独评估:
- 对话很短,稳定前缀没有达到服务商要求的最小长度;
- 每轮都在前部注入时间、随机数或检索结果;
- 工具定义频繁变化,导致固定前缀版本不断更新;
- 上下文超长,系统自动截断了早期消息;
- 流量过低或间隔过长,缓存已被回收;
- 供应商只对特定模型、区域或请求接口提供缓存计费。
因此,优化目标不应只是“让缓存命中”,还包括控制上下文长度、减少无效历史、稳定工具 schema,以及在必要时做摘要。对于 RAG 或 Agent 应用,检索结果通常是动态内容,适合放在稳定前缀之后;但如果每轮都把大量重复文档拼到最前面,也会让缓存收益和上下文窗口一起受到影响。
适合谁采用这套方案
多轮客服、代码 Agent、知识库问答和自动化运维任务通常更容易获得收益,因为它们会反复发送相同的规则、工具说明和历史上下文。一次性问答、短提示词或每轮内容都完全不同的应用,改造收益可能有限。
落地时可以按这个顺序推进:先确认服务商是否支持输入缓存及其计费规则;再给消息分层,标出固定、持久和临时内容;然后在发送前统一清洗和序列化;最后用一组固定的多轮测试比较 cache hit、总 token 和实际账单。只有当这些指标同时改善,才说明缓存设计真正发挥了作用。
提示词缓存的核心不是把所有内容“保存起来”,而是让每一轮请求尽可能复用真正稳定的那一段上下文。对 Agent 来说,临时状态不落入正式历史、内部字段不进入模型请求、system prompt 不随意改写,是比单纯调整参数更可靠的起点。