LLM 101 FIELD NOTE

Agent Context Economics 101|02|Stable Prefix:缓存命中首先是一个架构问题

Prompt Cache 依赖的不是语义相似,而是稳定的 token 前缀。缓存友好性因此首先是一份确定性的上下文组装契约。

语义相同不等于前缀相同

继续同一个 coding agent 任务:“定位失败测试、修改实现、运行完整测试并交付结果”。第一轮已经装入系统规则、工具定义和项目指令,Agent 随后读取文件、执行测试,并把结果追加到历史。若后续每轮能保留同一段开头,Provider 才有机会复用此前处理过的前缀。

这里的“同一”不是人类判断的意思相近,而是 exact prefix identity:Provider 实际用于缓存匹配的、模型可见的最终前缀表示或 token 序列必须精确一致;应用侧可以用对应表示的 digest 检查这一契约。下面两条指令对工程师表达了同一件事:

Run the complete test suite before delivery.
Run all tests before you deliver.

但措辞不同,token 序列也可能不同。在 prompt content 或 Provider 保留其渲染差异的工具 schema 中,即使文字没变,行尾从 LF 变成 CRLF、多一个空格、JSON 从 {"path":"a","line":1} 变成 { "line": 1, "path": "a" },也可能改变最终序列。缓存不会先理解两份提示词的语义,再决定它们“足够相似”;它比较的是产品规则所覆盖的精确前缀。

工具 schema 尤其容易制造这种错觉:两个对象在程序里拥有相同字段,验证器也给出相同结论,但一个注册器按插入顺序输出属性,另一个按字母顺序输出。若该 schema 序列化是模型可见内容,或 Provider 的缓存渲染保留了这项差异,最终前缀就可能成为两段不同序列。单元测试若只比较反序列化后的对象相等,就会漏掉这个缓存回归;还应按 Provider 契约比较缓存相关的最终表示。

因此,Prompt Cache 优化首先不是“把提示词写得意思一致”,而是让 Context Builder 每次用同一份输入生成同一份模型可见、缓存相关的最终前缀表示、token 序列或 digest。语义等价只能保障模型意图接近,不能保障缓存命中。反过来,前缀一致也不代表缓存一定可用:模型、路由、有效期、长度门槛和 Provider 资格条件仍可能决定最终结果。

第一个 mismatch 决定后面有多少内容要重算

设两轮请求开头依次为 A B C D EA B X D E。前三个位置中的 A B 仍是共同前缀;第一个 mismatch 出现在 C/X,从这里开始,后面的 D E 即使再次相同,也不再属于两轮的连续共同前缀。直觉上,可以把一轮输入分成“首个差异之前仍可能复用的前缀”和“差异处及其后需要作为新后缀处理的内容”。

第一个 mismatch 如何让后续前缀失去复用资格

这正是前缀缓存不同于通用文档去重的地方。它不能因为后面又出现一段相同文字,就跳过中间差异并把两段自动拼回同一个缓存命中。对于 coding agent,如果系统提示开头插入一个变化的时间戳,那么后面完全稳定的工具 schema、项目规则和长历史,也会落在首个差异之后。一个很小的早期内容变化,可能让数万 token 失去本轮的连续复用资格。

“后面需要重新处理”是应用架构层的安全表述,不等于所有系统都以单个 token 为物理存储和查找单位。精确的命中行为、最小可缓存长度、资格条件、Provider 的 block 粒度和断点策略都可能变化;首个差异之前也只是可能复用,还要满足对应产品边界。截至 2026-07-26OpenAI Prompt Caching以精确前缀匹配描述复用,并按具体模型说明缓存规则;不能把某个模型观察到的 block 行为写成通用实现定律。

稳定内容应该放在哪里

如果可复用对象是连续前缀,排序就直接决定可复用长度。一种实用的稳定到易变顺序是:

system instructions
tool definitions and schemas
project-level stable rules
conversation and tool results
current user input and volatile state

这五行是概念顺序,不是要求调用方重排顶层 API envelope。实际 wire/request 字段顺序、角色语义和缓存层级由 Provider 定义;例如截至 2026-07-26Claude API 的缓存层级tools → system → messages 组织。不要为了追求稳定而把内容跨角色移动,或削弱 system instruction 与安全边界的语义。稳定到易变原则只能在这些角色和产品边界内应用,并应验证 Provider 实际匹配或渲染的前缀。

系统指令、稳定的工具定义和项目级规则通常跨轮不变,应尽量靠前;对话、文件读取和测试输出以追加方式增长,适合跟在稳定材料之后;本轮用户输入与临时状态最易变化,应放在末尾。这样第二轮不是重新编排第一轮,而是在相同开头后追加新后缀,最大化可复用前缀。

把它代回贯穿任务,第一轮可以是“稳定头部 + 用户要求定位失败测试”,第二轮在原内容后追加“读取测试文件及结果”,第三轮再追加“补丁与完整测试输出”。工具结果越来越多,账单里的总输入仍会增长;但只要组装器不回头重排,较早的连续前缀就有机会继续复用。若第三轮为了展示“最新进度”,把完整测试结论提升到 system 后第一行,逻辑信息没有增加,却会把首个 mismatch 提到最前面。这说明布局本身就是成本决策。

这不是说所有历史都应永久保留,也不是说工具表永远固定。它是一份布局原则:变化频率越低、复用范围越广的内容越靠前;天然 append-only 的易变历史越靠后。工具装载策略会在下一篇展开,裁剪和压缩则留到后文。此处最重要的架构边界是:不要为了更新末尾状态,重写前面的稳定材料。

动态状态不要回写到前缀

假设 Agent 在第一次测试后记录:

run 1: one unit test failed

完整测试后,状态更新为:

run 2: full suite passed

若应用把这行状态写进 system prompt 顶部,每次进展都会改变首个差异位置。更缓存友好的做法,是保持系统规则和项目规则不变,把新的观察、工具结果和状态事件追加到后面。旧状态可以在语义上被后续事件覆盖,但不必为此修改已经发出的前缀。

动态状态也不应伪装成“项目规则”。当前分支名、工作树 dirty 状态、待执行命令、测试进度和用户刚选择的选项,都是某一时刻的运行事实。它们确实可能影响模型行为,所以要进入上下文;但进入的位置应靠后,并有清楚的版本或事件顺序。若某份环境快照必须整体替换,也要承认它会形成新的后缀,而不是把它注入稳定头部。

追加不是绝对命令。过期信息可能误导模型,窗口也有容量上限;需要裁剪或压缩时,应用可以有意识地建立新前缀。关键是把这种重建视为可观测的架构事件,而不是让任意状态更新悄悄改写开头。

时间戳、随机值和序列化顺序

最常见的缓存破坏并不来自业务需求,而来自组装过程的不确定性:

  • 在 system prompt 附近插入 timestamp,即使模型并不需要知道当前时间;
  • 把每次请求新生成的 UUID、随机 seed 或随机值写进提示词;
  • 在模型可见内容、工具 schema / tool-use content 或 Provider 保留其差异的渲染中,依赖非确定性的 JSON / object-key 顺序;
  • 随机或按发现时机排列工具定义,使 schema 清单每轮洗牌;
  • 在前部反复重写完整环境快照,只为更新一个字段;
  • 不统一 locale、Unicode、空格和 newline normalization,同一模板在不同机器产生不同文本。

这些字段要先问一个问题:它是否需要改变模型行为?若请求 ID 只用于链路追踪、路由或日志关联,就不应占用 prompt;在 API 允许时,应放到请求 metadata。metadata 与 prompt content 是不同层:前者服务基础设施,后者才是模型要理解的内容。顶层 API envelope 的字段顺序、wire JSON 的缩进或无关 metadata 的空白,并不天然属于缓存前缀;只有 Provider 明确把某项纳入缓存相关渲染时,才应把它列入稳定性契约。把追踪字段移出提示词,既减少 token,也避免制造早期 mismatch。

确实影响任务的时间、平台或环境信息仍应进入 prompt,但放在产品角色允许的易变尾部,并用固定格式渲染。例如统一 UTC 或明确时区、固定 ISO 8601 格式、对模型可见的工具定义做稳定排序、为模型可见结构使用 canonical JSON、统一相关文本的换行和 Unicode normalization。确定性不是删除动态信息,而是保证“相同输入生成相同的缓存相关表示,不同动态输入只改变预期位置”。

Breakpoint 和 cache key 能做什么

显式 cache breakpoint 可以告诉支持它的产品:在这里把既有前缀作为候选查找或写入边界。它有助于把稳定的系统指令、工具和项目规则纳入一个可复用候选,但不是把提示词切成彼此独立、可任意重组的“语义缓存段”。若 breakpoint 之前的内容变了,边界不会把不同 token 修复成相同;它也不是允许应用改写早期内容而继续命中的许可证。

截至 2026-07-26OpenAI Prompt Caching所述显式 breakpoints 是 GPT-5.6 及后续模型家族的行为;更早的受支持模型保留既有的自动缓存路径。OpenAI breakpoints 与 Anthropic 的 cache_control 不是一套共享协议,字段、边界和资格条件不能互换。

同样截至 2026-07-26Anthropic Prompt Caching定义了 Claude API 的 cache_control 放置与 TTL 行为。这是特定 Provider、API、模型和部署条件下的产品契约,不存在跨 Provider 的统一 breakpoint 或 TTL 语义。vLLM Automatic Prefix Caching则用分块、哈希与共享前缀说明一种实现机制;它可以作为“精确前缀为什么可复用”的工程证据,却不是任何托管 API 的命中承诺。

OpenAI 的 prompt_cache_key 也不能把两份不同内容声明成等价。根据截至该日期的 OpenAI Prompt CachingPrompt Caching 201,应用仍应把重复内容放在开头、保持内容与顺序稳定;prompt_cache_key 用于帮助请求路由与缓存亲和,使共享相同前缀的请求更有机会落到合适缓存路径。相同 key 加不同前缀不会创造内容等价,不同 key 的具体影响也要按官方文档与模型范围验证。

截至 2026-07-26Claude Code 的 Prompt Caching 说明提醒我们:在一个具体 Agent 产品里,模型、effort、fast mode、compact 等变化可能改变 cache key 或前缀;追加消息、rewind,以及延迟处理 MCP 工具定义,对既有前缀的影响也并不相同。本篇只取它的架构结论——缓存身份包含产品运行配置与实际请求形状。工具变化的细节留到 03,切换、TTL 与 miss 成本留到 05。

把 Context Builder 当成确定性构建器

Context Builder 不该是“从各处捞字符串再拼起来”的辅助函数,而应是一条可测试的确定性构建流水线:

  1. 版本化输入。 为 system 模板、工具清单、项目规则、序列化协议和模型配置记录版本;运行状态以显式字段输入,不从全局环境偷偷读取。
  2. 固定顺序。 在 Provider 的角色和缓存层级内明确各区块的 canonical order;模型可见的同类工具、文件或键使用稳定排序,新增易变事件只追加到约定位置。
  3. 规范序列化。 对 prompt content、工具 schema 与 tool-use content 等缓存相关表示,固定编码、locale、换行、Unicode normalization、JSON 键序和空值规则;顶层 API envelope 只有在 Provider 契约明确纳入时才属于这里的稳定对象。
  4. 纯渲染。 stable render function 在给定版本化输入时生成唯一的模型可见、缓存相关前缀表示;当前时间、随机数和 UUID 必须显式传入,并只能出现在声明为易变的区块。
  5. 自动验证。 用 golden test 检查缓存相关布局,用 token digest 或前缀 fingerprint 检查跨版本稳定性;当摘要变化时,诊断应能报告 first mismatch 所属区块和偏移。

测试不应只断言“提示词包含项目规则”,还要断言相同 fixture 构建两次得到相同的最终缓存相关表示或 token digest、工具顺序不受注册时机影响、只更新运行状态时稳定区块的 fingerprint 不变。若团队比较渲染文本的字节 identity,应明确它只是已确认缓存相关区块的工程代理,而不是整份 wire request 必须逐字节一致。Tokenizer 或模型版本变化也应成为显式迁移,因为相同文本在不同 tokenizer 下未必对应相同 token 边界。

一次有效的回归报告可以只写:system-v7 未变、tools-v12 未变、project-v3 未变,首个差异位于 history/event-184,此前前缀为多少 token,本轮缓存读写用量是多少。若差异意外落在 tools-v12,再由构建器输出“工具排序变化”或“schema 序列化版本变化”这样的结构原因。这样工程师可以从结果追到输入版本,而不是面对 miss 猜测 Provider 是否“忘了缓存”。

版本升级也需要明确策略。修改系统规则或工具 schema 时,新的 prefix epoch 是合理结果;团队应在发布记录中声明它,预期冷启动或缓存写入,并观察后续请求是否在新版本上重新形成稳定前缀。真正的问题不是任何变化都会 miss,而是变化没有边界、没有版本、也无法解释。

可观测性不等于记录原始 prompt。生产诊断应优先保存模板版本、区块长度、模型与 tokenizer 标识、带访问控制的 keyed digest、首个差异的区块名,以及 Provider 返回的缓存用量。不要为了排查命中率,把源码、凭据、用户对话或测试输出以明文复制进普通日志;fingerprint 的密钥、保留期和访问权限也要按敏感数据治理。

Stable Prefix Review

在合并一次 Context Builder 或 Agent 配置变更前,可以逐项审查:

  • 用两份完全相同的 fixture 连续构建,模型可见、缓存相关的最终前缀表示与 token digest 是否一致?
  • system、工具 schema、项目规则、历史、当前输入是否遵守 Provider 的角色 / 缓存层级,并在各自边界内具有明确顺序和版本?
  • 新字段会改变模型行为吗?若不会,是否已移到 API metadata,而不是 prompt?
  • timestamp、UUID、随机值、环境快照是否只出现在声明过的易变尾部?
  • 模型可见或 Provider 保留渲染差异的 JSON 键、工具定义和集合项是否稳定排序;相关 locale、Unicode 与换行是否规范化?
  • 只追加一条测试结果时,稳定前缀 fingerprint 是否保持不变,first mismatch 是否出现在预期后缀?
  • breakpoint 是否只作为候选查找 / 写入边界,而没有被当作语义分段或内容等价声明?
  • prompt_cache_key 是否用于相同前缀的路由亲和,而没有掩盖实际内容差异?
  • 模型、effort、compact 或工具版本改变时,系统是否记录了一次可解释的 prefix epoch 变化?
  • usage、TTFT、模板版本和首个差异区块能否关联,同时避免在日志中保存原始敏感 prompt?
  • 冷请求、相同前缀追加请求、故意修改早期前缀三组测试,是否呈现可解释的缓存读写差异?

完成这份 review,团队得到的不是“强制命中”的保证,而是一份可复现的上下文组装契约。Provider 是否最终命中仍受产品边界影响;应用能控制的是,不要在请求到达 Provider 之前就亲手破坏 stable prefix。


上一篇:The Context Bill:Agent 每一轮到底在为什么付费

下一篇:Tool Loadouts:为什么多加一个工具可能更贵

参考资料