给 Claude Code 会话设计稳定的系统提示词前缀:让缓存在整轮运行里持续命中
本文讲解在 Claude Code 等长会话编码场景中,如何通过设计稳定的系统提示词前缀来维持提示缓存的持续命中。文章分析了会话中途修改系统提示词或注入上下文为何会导致缓存失效,并给出组织指令的具体方法,确保可缓存前缀在整轮运行中字节不变,从而降低延迟与成本。
为什么缓存命中对长会话很重要
在 Claude Code 这类长时间运行的编码会话中,每一轮对话都会把系统提示词、历史消息和当前输入一起发送给模型。如果每次请求的前缀部分完全相同,服务端就可以复用之前计算好的缓存,显著降低延迟和计费成本。反之,前缀一旦变化,缓存就会失效,模型需要重新处理整个上下文。
缓存通常按前缀匹配:从请求的最开头开始,逐字节比对,直到遇到第一个不同的位置。因此,可缓存的部分必须位于请求的最前面,并且在整轮会话中保持字节级不变。
中途改系统提示词为什么会打断缓存
很多开发者习惯在会话进行中动态调整系统提示词,比如:
- 根据当前任务阶段追加新的指令(“现在进入测试阶段,请只关注测试代码”);
- 注入项目上下文、文件内容或工具输出;
- 根据用户输入切换角色或约束。
这些操作如果直接修改系统提示词,就会改变请求开头的字节序列。即使只增加一个空格或换行,缓存也会从该位置开始失效,后续所有内容都需要重新计算。
更隐蔽的情况是“注入上下文”:把动态内容拼接到系统提示词后面,看似没有改动原有指令,但实际上改变了前缀长度和内容,同样会打断缓存。
设计稳定前缀的原则
要让缓存在整轮运行中持续命中,需要把请求分成两部分:
- 稳定前缀:在所有请求中完全一致,放在最前面。
- 动态内容:放在稳定前缀之后,可以每轮变化。
稳定前缀通常包括:
- 角色定义与总体行为约束;
- 输出格式要求;
- 工具使用规范;
- 安全与合规声明(如有);
- 固定的示例或模板。
动态内容则包括:
- 当前任务描述;
- 对话历史;
- 文件内容或工具输出;
- 阶段性指令。
关键点是:稳定前缀必须逐字节固定,不能因为“优化措辞”或“补充说明”而随意改动。
如何组织指令让前缀字节不变
1. 把阶段性指令放在用户消息里
不要修改系统提示词来切换阶段。相反,在用户消息中说明当前阶段和目标。例如:
系统提示词(固定):你是一个严谨的编码助手,遵循以下规则……
用户消息:当前阶段:测试。请只关注测试代码,不要修改实现。
这样系统提示词保持不变,缓存可以持续命中。
2. 用固定分隔符隔离动态上下文
如果需要注入文件内容或工具输出,使用固定的分隔符和标记,并放在系统提示词之后。例如:
系统提示词(固定):……
用户消息:
<context>
……动态内容……
</context>
当前任务:……
只要系统提示词部分不变,缓存就能覆盖它。
3. 避免在系统提示词中使用时间戳或随机值
任何每次请求都变化的内容(如当前时间、随机 ID、会话编号)都会破坏前缀稳定性。把这些内容移到用户消息或元数据中。
4. 谨慎使用“追加”式修改
如果确实需要在系统提示词中增加内容,考虑在会话开始时就预留好结构,而不是中途插入。例如,一开始就定义好多个章节,后续只填充用户消息中的对应部分。
5. 保持工具定义稳定
如果请求中包含工具定义(function calling),工具列表和 schema 也应保持稳定。频繁增删工具或修改参数描述同样会改变前缀。
一个可操作的会话结构
下面是一个适合长会话的请求结构示例:
[系统提示词] 固定角色、规则、输出格式、工具规范
[工具定义] 固定列表与 schema
[用户消息 1] 任务描述
[助手消息 1] 回复
[用户消息 2] 阶段性指令 + 动态上下文
……
在这个结构中,系统提示词和工具定义构成稳定前缀,缓存可以覆盖它们。随着会话进行,只有用户消息和助手消息在增长,前缀部分始终不变。
检查与验证
在接入聚合站时,可以通过响应中的缓存相关字段(如 cachecreationinputtokens 和 cachereadinputtokens)观察缓存命中情况。如果发现缓存读取量持续为 0,应检查:
- 系统提示词是否在会话中被修改;
- 工具定义是否发生变化;
- 是否有动态内容被拼接到前缀中;
- 请求开头是否存在时间戳或随机值。
小结
稳定的系统提示词前缀是长会话缓存命中的基础。把固定指令放在最前面并保持字节不变,把阶段性指令和动态上下文放入用户消息,就能让缓存在整轮编码会话中持续生效,从而降低延迟和成本。