agent runtime lab · 2026 · 技术研究笔记
02. Prompt 长度、上下文增长与缓存成本
第二轮请求不是只发送新增内容;历史如何保留,决定了延迟、成本和可恢复性
从 Codex 与 Kimi 的两轮请求观察上下文怎样增长,以及缓存、裁剪、摘要和服务端状态引用分别解决什么问题
研究版 · 14 个章节 · 约 21 分钟 · 更新于 2026-07-27
这次抓包里,Kimi 在第一次模型响应中发起两个 Bash 调用。CLI 执行命令后,又向模型发送了第二个请求。第二个请求包含原系统消息、环境、任务、模型给出的工具调用以及两条工具结果。
这条轨迹让我看到一个容易被最终回答掩盖的问题:Agent 每多运行一轮,上下文就会继续增长。
先区分四个经常混用的量
上下文窗口是单次模型调用能够处理的 Token 上限;Token 是模型处理文本时使用的离散单元,并不等于字符。输入 Token是某次调用实际送入模型的数量,累计输入量则是一次任务中所有模型调用的输入之和。
若固定规则和工具定义占 S + T,第 i 轮新增消息占 M_i,完整历史模式下,第 n 轮输入近似为:
input_n = S + T + M_1 + M_2 + ... + M_n
累计输入不是最后一轮的长度。早期内容会在后续请求中再次出现,所以长会话的累计量增长得更快。
本次抓包能确认什么
Codex 的成功轨迹记录了以下用量:
| 指标 | Token |
|---|---|
| 输入 | 33,348 |
| 其中缓存输入 | 16,128 |
| 输出 | 156 |
| 其中推理输出 | 36 |
缓存输入约占本次输入的 48.4%。这个比例只表示服务端把多少输入计入缓存命中,不能直接解释成费用下降 48.4%,因为缓存 Token 的价格由服务端和具体模型决定。
Kimi 的两次 HTTP 请求正文分别约为 94 KB 和 96 KB。字节数可以证明第二次请求更大,却不能换算成 Token:JSON 转义、中文编码、字段名和分词方式都会改变换算关系。因此这里保留字节级观察,不把它包装成 Token 统计。
Claude Code 返回 403,Grok 返回 402。两者都没有进入第二轮,无法从本次数据判断其成功会话怎样增长。
四种控制方法解决的是不同问题
| 方法 | 保留什么 | 主要收益 | 主要风险 |
|---|---|---|---|
| 前缀缓存 | 不变的请求前缀 | 减少重复计算 | 前部动态字段会破坏命中 |
| 历史裁剪 | 最近且仍相关的消息 | 直接减少输入 | 可能删掉约束或失败原因 |
| 会话摘要 | 目标、事实、未完成项 | 用较短文本代替历史 | 摘要可能写错或遗漏 |
| 状态引用 | 服务端保存的前序响应 | 客户端少传历史 | 调试依赖服务端状态 |
Prompt 缓存是对相同前缀的复用。它不会扩大上下文窗口,也不会阻止历史继续变长。摘要和裁剪会改变模型能看到的内容,属于信息管理;缓存不改变语义,属于计算复用。
Anthropic 的缓存文档把请求前缀按 tools、system、messages 的顺序处理。稳定内容放在前面、每轮变化的工具结果放在后面,较容易保留相同前缀。Claude Prompt Caching
动态字段放在哪里很重要
日期、随机标识、工作目录和实时状态如果出现在请求开头,缓存会在较早位置分叉。后面的长系统规则即使没有变化,也可能无法沿用同一前缀。
我会把上下文按变化频率排列:
| 顺序 | 内容 | 更新频率 |
|---|---|---|
| 1 | 平台规则、工具定义 | 客户端升级时 |
| 2 | 项目规则、技能说明 | 切换项目时 |
| 3 | 会话摘要、历史消息 | 多轮对话中 |
| 4 | 最新工具结果、当前输入 | 每一轮 |
这不是指令优先级表。优先级描述冲突时听谁的,排列顺序描述怎样形成稳定前缀,两者要分别维护。
为什么累计成本会出现二次增长项
把每轮新增消息都记作固定长度 m,固定前缀记作 p。若客户端每轮重发完整历史,第 i 次模型调用的输入近似为 p + i × m。运行 n 轮之后,累计输入为:
total(n) = Σ(p + i × m)
= n × p + m × n × (n + 1) / 2
式子中的第二项随 n² 增长。它不表示模型的单次上下文窗口按平方增长,而是表示同一段早期历史在后续调用中被重复计入。十轮会话里,第一轮消息最多会进入十次请求,第十轮消息只进入一次。
这个模型还没有计入工具结果变长、模型输出长度变化和摘要操作,因此只能作为成本下界。它已经足以解释一个现象:减少一轮没有必要的模型往返,节省的不只是该轮输出,还会减少后续每一轮携带的历史。
前缀缓存改变的是重复前缀的计算价格和首 Token 延迟,并不删除上式中的 Token。Anthropic 当前文档明确区分了四种手段:工具搜索减少预先加载的工具定义,程序化工具调用减少中间往返,Prompt 缓存复用稳定前缀的计算,Context Editing 删除已经失去用途的旧工具结果。四者作用在不同位置,不能互相替代。Anthropic:Manage tool context
KV Cache 与跨请求 Prompt Cache 不是同一个缓存
KV Cache是模型推理时保存注意力层 Key、Value 张量的内存结构;它避免自回归生成每个新 Token 时重新计算此前 Token。Prompt Cache是服务端在请求之间复用某段相同前缀的机制。前者存在于一次推理过程内部,后者解决多次请求拥有相同前缀时的重复计算。
两者都依赖前缀一致性,但生命周期和可见指标不同。客户端一般无法直接读取模型实例里的 KV Cache,只能从服务端返回的缓存 Token、首 Token 延迟和计费字段判断跨请求缓存是否命中。把两者统称为“缓存”会导致两个错误判断:
| 错误判断 | 实际情况 |
|---|---|
| 命中 Prompt Cache 后不再占用上下文窗口 | 缓存前缀仍是本次上下文的一部分 |
| 裁剪历史会提高已有缓存的命中率 | 裁剪改变了消息序列,可能形成新的前缀 |
| 请求 JSON 相同就一定命中 | 缓存还受模型、工具配置、缓存时效和服务端策略影响 |
Anthropic 的当前缓存顺序是 tools → system → messages。修改工具定义会使后续系统与消息缓存一并失效;改变 tool_choice 只影响消息层;默认缓存有效期为五分钟,也可选择一小时。文档还给出一个容易遗漏的并发条件:缓存条目要到首个响应开始后才可用于其他请求,因此同时发出的第一批请求不能假定共享刚创建的缓存。Anthropic:Prompt caching
缓存命中率不能单独回答成本问题
设普通输入单价为 C_in,缓存写入单价为 C_write,缓存读取单价为 C_read。一次调用的输入成本应按三部分计算:
cost = uncached_tokens × C_in
+ cache_write_tokens × C_write
+ cache_read_tokens × C_read
缓存命中率相同的两条轨迹,成本仍可能不同。第一条轨迹可能反复读取一个短前缀,第二条轨迹可能读取一个很长的工具定义区;两者的缓存 Token 绝对量不同。缓存命中率也不能表示延迟改善,因为首 Token 延迟还包含排队、网络、服务端调度和未缓存后缀的预填充时间。
因此十轮实验至少同时报告四个量:缓存读取 Token 的绝对值、未缓存输入 Token、首 Token 延迟和累计费用。只报告“命中 80%”没有说明基数,也没有说明写入成本。
压缩本质上是一笔信息债务
摘要不是把 20 KB 文本无损编码成 2 KB,而是由模型选择哪些事实继续保留。这里的信息债务指被删除内容在当前轮看似无用,却可能在后续验证、恢复或审计时重新需要。
可以把轨迹中的内容按可压缩性分成四类:
| 内容 | 默认处理 | 原因 |
|---|---|---|
| 用户目标、禁止事项、批准范围 | 原文保留 | 一个否定词被改写就可能改变权限 |
| 外部系统回执、文件位置、对象标识 | 结构化保留 | 后续动作需要精确引用 |
| 大段搜索结果与文件正文 | 外置并保存引用 | 正文占用大,但仍需回查证据 |
| 已完成的中间解释 | 可摘要 | 后续主要需要结论与证据指针 |
摘要正确性不能只让生成摘要的同一个模型自评。更直接的办法是从压缩前轨迹抽取一组必须回答的问题,例如“用户是否允许联网”“已经修改了哪些文件”“最后一次工具错误是什么”,再让压缩后的上下文回答。若任一答案改变,摘要不能替换原轨迹。
这是一种保真度测试:保真度表示压缩后仍能恢复多少任务关键事实。它与语言是否通顺无关。一段读起来完整的摘要,仍可能遗漏一个路径、一个否定条件或一次尚未确认的写操作。
摘要至少要保留哪些事实
摘要如果只写“用户正在分析项目”,恢复后会缺少继续执行所需的目标、约束和进度。一个可用的会话摘要至少要包含:
- 当前目标与明确禁止事项。
- 已读取或修改的资源。
- 已完成步骤及其证据。
- 工具失败、错误类型与重试次数。
- 尚未完成的步骤。
- 需要用户确认的操作。
还要保存摘要基于哪一段历史生成。否则出现错误时,无法回到摘要前定位被遗漏的信息。
我会怎样验证缓存设计
同一任务连续执行五轮,每轮只改变末尾的一小段用户输入。日志同时记录输入 Token、缓存 Token、首 Token 延迟和总延迟。随后把当前时间移到系统块最前部,再运行同一组任务。
如果第二组的缓存命中位置提前失效,就能证明问题来自上下文排列,而不是模型回答内容。最终答案相同,并不代表运行成本相同。
十轮实验应该怎样记录
每轮至少记录请求序号、输入 Token、缓存读取 Token、输出 Token、首 Token 延迟、总延迟和本轮新增事件数。只比较最后一轮会漏掉前九轮重复发送的成本;只比较累计 Token 又无法定位从哪一轮开始失控。
实验还需要保存缓存边界之前的内容指纹和变化位置。这里的指纹用于比较同一次实验中的文本是否相同,不保存敏感正文。若第三轮在平台规则前加入时间戳,而缓存命中从第三轮开始下降,日志应能把这两个事件连接起来。
更完整的实验使用四组配置,每组重复相同的十轮任务:
| 组别 | 历史策略 | 缓存策略 | 要隔离的变量 |
|---|---|---|---|
| A | 完整历史 | 关闭 | 建立累计输入基线 |
| B | 完整历史 | 稳定前缀缓存 | 测量计算复用 |
| C | 删除已消费的工具结果 | 稳定前缀缓存 | 测量上下文编辑 |
| D | 关键事实原文 + 其余内容摘要 | 稳定前缀缓存 | 测量有损压缩 |
四组必须使用相同模型版本、工具定义、任务输入和请求间隔。B 组若超过缓存有效期再运行,结果不能与有效期内的 A 组直接比较。D 组除了 Token 与延迟,还要运行前述保真度问题集;否则它可能以丢失约束为代价得到最低输入量。
实验判定也不能只看最终回答。若 Agent 在第六轮忘记已读取某文件,于第七轮重复调用工具,最后仍给出正确答案,结果正确性通过,过程效率和状态保持已经失败。轨迹需要分别标记这两类结果。
裁剪与摘要需要可逆的审计路径
裁剪策略不能只按“保留最近 N 条消息”工作。权限批准、失败原因和仍未完成的约束即使较早出现,也可能继续影响当前动作。每次删除历史时,应记录被删消息的标识、删除原因以及替代它的摘要版本。
摘要也不能直接覆盖上一版。一个可审计的实现会保留摘要输入范围、生成模型、生成时间和验证结果。若后续发现摘要把“不得联网”写成“可以联网”,运行时需要回到生成该摘要的历史,而不是只剩一段错误文字。
何时应该开始压缩
压缩阈值不宜只使用上下文窗口百分比。工具结果很大但后续不再使用时,可以提前外置;约束密集的短会话即使 Token 不多,也不适合机械摘要。更合适的触发条件同时考虑剩余窗口、预计下一轮输出、未完成工具数量和必须原样保留的证据量。
验收时至少设置两类任务:一类需要引用第一轮出现的约束,另一类只依赖最近结果。若策略只在第二类任务上表现正常,不能说明它适合长期 Agent 会话。
一个可以落地的触发器会先预留下一轮预算,再判断是否压缩:
usable = context_limit
- reserved_model_output
- reserved_tool_results
- safety_margin
当当前输入接近 usable 时,运行时先外置体积大且已消费的工具结果,再删除可重新获取的内容,最后才摘要含有决策历史的消息。这个顺序体现的是可恢复性:能够从文件或对象存储重新读取的内容,比只存在于对话中的批准理由更适合先移出窗口。
压缩后还要记录“上下文世代”。上下文世代是一次压缩产生的新版本号。后续轨迹若出现行为偏移,可以比较压缩前后的事实集合,而不是面对一份已经被覆盖的聊天记录。
从当前资料还能得到一个新的工程结论
截至 2026 年 7 月,厂商已经把上下文压力拆成多个独立产品能力。Anthropic 同时提供 Prompt Caching、Context Editing、Tool Search 和 Programmatic Tool Calling;这说明“扩大上下文窗口”并不是长任务的唯一方向。窗口扩大只能延后容量耗尽,不能消除重复预填充、低价值工具结果、无关工具定义和错误摘要带来的问题。
对 Agent 运行时而言,更稳定的设计不是选择一种统一压缩算法,而是为每类内容保存三个属性:重新获取成本、丢失后的风险和预计复用距离。只有重新获取成本低、丢失风险低、短期内不再使用的内容,才适合优先移出上下文。
本次抓包可以证明 Kimi 的第二轮请求重发了历史,也可以证明 Codex 的一次成功请求含有缓存输入。它不能证明某个客户端在更长会话中采用哪种压缩策略。缓存有效期、服务端内部状态和模型实例上的 KV Cache 也无法通过单次 HTTPS 正文直接观察。这些结论必须依靠十轮用量数据、延迟分布和压缩前后问答测试继续验证。
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:02. Prompt 长度、上下文增长与缓存成本
本文链接:https://www.sshipanoo.com/blog/ai/agent-runtime-lab/02-上下文增长与缓存成本/
