Part B · 横切深挖 · B03

Context engineering and caching上下文工程与缓存

口述 2 min 追问 7 层 适合 垂直 Agent 研发, FDE
Prompt 题面

先想清楚再开口

你讲完一个长任务 Agent,面试官接着问:「它跑了两个小时,上下文快满了。上周改版之后,账单上的输入成本涨了一截,首 token 也变慢了。你怎么设计它的上下文?压缩之后,你怎么知道它没把任务忘了?」

题面为本书自拟的训练题,不代表任何公司的真实面试题或真实系统。场景和数字均为虚构练习设定。

约束

约束取值(练习设定)
任务长任务 Agent(虚构):把一个中型仓库的测试框架整体迁移,跑数百步,中间会被压缩多次
上下文上限 200k tokens;固定部分为 system 8k + 工具定义 12k + 目标与验收条款 2k
工具结果单次最多约 30k tokens(测试日志、整份文件);多数只需要其中一小段
计费缓存读取按普通输入价格的 r 倍计(r < 1),写入缓存按 w 倍计(w ≥ 1);r、w 是练习参数
现象上周改版后,缓存命中率从约八成掉到约两成,输入成本和首 token 延迟一起上升
要求压缩后不能丢目标、验收条款和用户给过的硬约束;任务进度有外部账本
答到一半再点开:面试官补了一句

「产品要求在 system prompt 里加上当前时间和用户时区,方便模型处理日期。」

Answer 参考回答

先口答,再展开

展开参考回答

该先问的问题

  1. 哪些信息是任务真值——目标、验收条款、用户的硬约束、已经做出的决定?它们在对话之外有没有权威副本?这决定哪些东西可以被摘要、哪些必须钉住。
  2. 工具结果能不能重取?重取要多久、有没有副作用?这决定大结果能不能只留摘要和句柄。
  3. 你用的缓存怎么匹配、按什么粒度命中、有效期多长、读写怎么计价?这决定 prompt 的排布规则和估算参数,要以服务文档为准,不能凭印象。
  4. 命中率是从哪个版本开始掉的?那次改了什么?这决定先查 diff,而不是先改架构。
  5. 压缩由谁触发、谁写摘要、摘要能不能校验?这决定压缩后怎么验证没丢任务。

一句话框架

上下文按稳定度分层:不变的内容放最前、逐字节不动;任务真值钉住并在账本里留权威副本;可重取的观察只留摘要和句柄;压缩少做、在边界做,做完拿账本回归检查。

设计取舍表

按稳定度排序:越不变越靠前,新内容只往后追加 system 8k 工具定义 12k · 顺序固定 钉住块 目标 · 验收 压缩摘要 带版本 历史观察(只追加) 大结果留摘要 · 按需受控留存 当前轮 时间等易变字段 上一次调用已经见过的前缀 → 可命中缓存(若服务按前缀匹配) 新增部分 把时间戳写进 system:第一个字节就变了,后面全部失效 时间戳 每轮都变 system · 工具定义 · 钉住块 · 摘要 · 历史 · 当前轮 内容没变,但前缀已经对不上:整段按未命中计费,还可能重新写入缓存 稳定前缀:逐字节不变 任务真值:钉住,账本有权威副本 可重取观察 易变:只放末尾 长度为练习设定;缓存的匹配规则、有效期和计价以你所用服务的文档为准
上下文按稳定度排布。上:越不变越靠前,新内容只往后追加,上一次调用已经见过的前缀可以命中缓存。下:把时间戳写进 system,第一个字节就变了,后面内容虽然没变也全部对不上。缓存的具体匹配规则、有效期和计价以所用服务的文档为准。
设计点采用否决的备选什么证据会让我改
排布顺序system → 工具定义(顺序固定)→ 钉住的目标与验收条款 → 压缩摘要 → 历史观察(只追加)→ 当前轮按「重要性」排,或者把进度、计划写进 system 每轮改写如果所用服务不是按前缀匹配缓存(例如按显式标记的块缓存),按它的规则重新排;原则仍是稳定内容和易变内容分开
易变字段时间、随机 ID、计数器、当前进度只放在当前轮,或者让模型用工具去取放进 system 或工具描述实测某个易变字段放在前面对质量有明显帮助,且代价可以接受时,降到最粗的粒度(例如只写日期),并接受按天失效
工具结果模型只拿摘要和关键片段。确实需要恢复的原始结果(有副作用、结果会随时间变化、重取很贵)按来源 ACL 和分级存进受控存储,给一个不可猜的句柄;每次按句柄读取都由服务端重新授权;设保存期,并能按数据主体删除。其余结果只存摘要原样全部塞进上下文;或者把所有原文都存成一个没有权限和删除边界的常驻副本结果很小、下一步确实要用全文时,直接放入;取片段的往返延迟比多读几千 token 更贵时,也直接放入
钉住块目标、验收条款、用户硬约束作为钉住块,不参与摘要,原样放在稳定前缀里;权威副本在外部账本,带版本号和对话一起被摘要目标或条款改版时,按新版本整体替换钉住块,这会让缓存失效,是有意为之
压缩时机到达上限的约七成,或者在子任务完成这类自然边界压缩一次;摘要写进账本,带版本每轮滚动摘要(每轮都让摘要之后的前缀变化,缓存几乎用不上)实测显示长上下文下的质量下降出现得更早,就把阈值往前挪;以任务完成率为准,不以 token 为准
压缩验证压缩后让模型复述目标、下一步和尚未满足的条款,与账本比对;对不上就用账本重建上下文,并记一次压缩失败相信摘要写得对复述一致率长期很高、压缩后的假完成率不升,才减少验证频率,但不取消
监控每次调用记录缓存读、写 token,命中率,首 token 延迟;按版本对比;前缀哈希一变就告警只看月底账单不改;缓存失效是静默的,不监控就只能从账单上事后发现
Two-minute English 口述

两分钟,先结论后展开

展开 2 分钟英文口述稿

I'd organize the context by how stable each part is, because caching and correctness both depend on it.

At the front goes what never changes: the system prompt and tool definitions, byte for byte, in a fixed order. Next comes the pinned block — the goal, the acceptance criteria, and the user's hard constraints. It's never summarized, and the ledger outside the conversation keeps the authoritative copy. After that, history is only appended. For large tool results the model gets a summary and the few lines it needs; raw output is kept only when we must recover it, under the source's access rules and a retention limit.

Anything volatile — timestamps, random IDs, progress — goes in the latest turn or behind a tool. So if the product wants the current time, I add it at the end, not in the system prompt.

Compaction is the exception: it resets whatever follows the pinned block. So I compact rarely, at natural boundaries such as a finished subtask. Afterwards I ask the agent to restate the goal and the next step and compare that with the ledger; if they disagree, I rebuild the context from the ledger.

For the drop in cache hits, I'd diff the prompt prefix between the two releases and find the first byte that changed, and I'd alert whenever the prefix hash changes.

Follow-ups 追问

一层一层往下问

  1. 为何不用备选为什么不干脆换更大的上下文窗口,把所有东西都留着?
    先口答,再展开要点
    • 窗口再大也有上限,长任务迟早会碰到;换大窗口只是推迟压缩,不是取消压缩。
    • 每一步都要把整个上下文读一遍:没有缓存时,输入成本随上下文长度线性增长,首 token 延迟也跟着涨。
    • 长上下文里,中间位置的信息更容易被忽略(原理见 AI 3.4);关键约束埋在几十万 token 中间,并不比钉在前面更安全。
    • 什么时候接受大窗口:任务短、上下文增长有界、且实测质量没有下降时,大窗口加缓存是最省事的方案。
  2. 需求变化产品要在 system prompt 里加上当前时间和用户时区。
    先口答,再展开要点
    • 先问为什么需要:多数时候是模型要处理「今天」「下周一」这类相对日期。把时间放在当前轮,或者提供一个取时间的工具,效果相同,前缀不动。
    • 如果坚持要放在前面:降到最粗的粒度(只写日期和时区),只在日期变化时变,接受每天一次缓存失效;不要精确到秒。
    • 把代价讲成数字:按下面的估算,前缀每轮都变时,整段都按未命中计费,还可能每轮都重新写入缓存。
  3. 失败反证一次压缩之后,Agent 又去改了一个用户早就说过「不要动」的目录。怎么防?事后怎么发现?
    先口答,再展开要点
    • 根因通常是:这条约束只在对话里说过,没进钉住块,被摘要丢掉了。
    • 防:用户给出的硬约束当场写入账本和钉住块,不依赖摘要保留;执行端对这类约束做确定性检查(例如禁止写入的路径列表),不只靠模型记得。
    • 发现:压缩后的复述检查要覆盖「硬约束」这一项;把这次事故做成回归用例——人为触发压缩,检查约束是否还在、是否被违反。
  4. 诊断命中率从八成掉到两成,你第一步查什么?
    先口答,再展开要点
    • 先查 diff,不先改架构:取改版前后两个请求的前缀,逐段对比,找第一个不同的字节。
    • 常见元凶:时间戳或请求 ID 进了 system;工具列表的顺序不稳定(例如从无序集合里迭代生成);JSON 序列化时字段顺序变了;把用户信息或实验分组插在前缀中间。
    • 另一类原因不在前缀:两次调用间隔超过了缓存有效期,或者请求没有落到同一份缓存上(如果服务有这类机制)。按服务文档逐项排除。
    • 修完之后,用前缀哈希做回归测试:同一任务连续两轮的前缀哈希必须相同。
  5. 工具结果一次测试日志有 30k tokens,模型只需要最后的失败栈。怎么处理?
    先口答,再展开要点
    • 给模型的是:退出码、失败用例名、最后若干行、以及一个可以按范围读取的句柄。日志原文只在确实需要回看时才留:按来源仓库的权限存进受控存储,设保存期。
    • 模型需要更多时,用句柄取指定片段,不重跑测试。重跑有成本,有些工具还有副作用。
    • 失败模式要防:摘要把关键行裁掉了。所以句柄在任务期内和保存期内要能用,并在评估里统计「因为信息被裁掉而多走的步数」。
    • 句柄不是凭证:它只是一个引用,每次读取仍按请求人和来源 ACL 在服务端重新授权。另一个项目的人猜到或拿到句柄,也读不到内容;越权读取要记审计日志。
  6. 压缩代价压缩本身会让缓存失效,那什么时候值得压?
    先口答,再展开要点
    • 压缩的代价是一次性的:一次摘要调用,加上压缩后第一轮要重新写入摘要之后的部分。
    • 收益是持续的:之后每一步读的上下文都变短,首 token 延迟下降,也离上限更远。
    • 所以压缩要少而准:在自然边界做,而不是每轮滚动;裁剪工具结果能让上下文长得更慢,压缩次数也就更少(见估算的敏感性)。
  7. 评估你怎么证明新的上下文策略没有降低任务完成率?
    先口答,再展开要点
    • 用固定、可重置的长任务集做对照(评估口径见 B06):完成率、假完成率(自称完成但检查器不通过)、每个任务的输入 token 与缓存命中率、压缩后复述与账本的一致率。
    • 故意制造压力:人为触发压缩、塞入超长工具结果、在对话早期给出硬约束,看这些情况下的违反率。
    • 只看成本下降不够;成本降了但假完成率升了,就是退步。
Failure Modes 失败模式

这些地方最容易栽

易变字段混进前缀,缓存静默失效
  • 症状:输入成本和首 token 延迟在某次发布后同时上升,功能测试全部通过,没有报错。
  • 根因:时间戳、请求 ID、无序的工具列表之类的易变内容进了前缀,每次调用前缀都不同;缓存失效不会报错,只会在账单和延迟上体现。
  • 防线:前缀哈希监控与告警;「连续两轮前缀相同」作为回归测试;代码评审时把 system prompt 和工具定义当成缓存敏感文件。
摘要吞掉了约束
  • 症状:压缩后 Agent 重复已经做完的步骤,或者违反用户早先说过的约束。
  • 根因:约束和进度只存在于对话里,摘要是模型写的,没有任何东西保证它保留了关键内容。
  • 防线:硬约束和进度写进外部账本和钉住块;压缩后复述并与账本比对;执行端对能写成规则的约束做确定性检查。
裁剪掉的内容取不回来
  • 症状:模型后来需要一段早先的工具输出,却只能重新调用工具,而那次调用有副作用或结果已经变了。
  • 根因:把「只给模型看摘要」做成了「只保留摘要」,原文没有留存;可重取和不可重取的结果没有区分。
  • 防线:先分类:有副作用、结果会随时间变化或重取很贵的原始结果,才存进受控存储;它继承来源的 ACL 和分级,按句柄读取时每次重新授权,设保存期,并能按数据主体删除(备份同样在删除范围内,备份例外要由法务界定并写清最长到期时间)。不需要恢复的结果只存摘要,不留原文,也就不会多出一份机密副本。
Estimate 估算

带着量纲算一遍

问:同一个长任务,缓存用得好、没开缓存、前缀每轮都变,输入成本差多少?裁剪工具结果对压缩次数有什么影响?

假设(练习设定,全部待实测):稳定前缀 S = 22k tokens;每步新增 δ = 6k tokens(裁剪后的工具结果约 5k + 模型输出约 1k);两次压缩之间走 20 步;历史只追加,缓存按前缀匹配、且在有效期内;缓存读取按普通输入价格的 r 倍计,写入按 w 倍计;以普通输入价格为 1 个单位;只算输入 token。

第 t 步的输入长度   L_t = 22k + 6k × t          (t = 1…20)
第 20 步            L_20 = 22k + 120k = 142k    (约为上限 200k 的七成)
20 步输入合计       20 × 22k + 6k × (1+…+20) = 440k + 1,260k = 1,700k

缓存用得好(只追加)
  可命中部分   = 上一步见过的前缀 = 1,700k − L_20 = 1,558k
  新写入部分   = 1,700k − 1,558k = 142k        (第 1 步的 28k + 后 19 步各 6k)
  成本         = 1,558k × r + 142k × w
  r = 0.2, w = 1.2 时  = 311.6k + 170.4k = 482k 单位 ≈ 不开缓存的 28%

不开缓存        = 1,700k × 1 = 1,700k 单位

前缀每轮都变(且每次都重新写入缓存)
                = 1,700k × w = 2,040k 单位 ≈ 不开缓存的 120%

结论:在这组假设下,前缀稳定时的输入成本约为不开缓存的 28%;而前缀每轮都变时,如果每次都重新写入缓存,反而比不开缓存更贵。这是有条件的结论:它依赖 r、w 的实际计价,依赖服务是否自动写入缓存,也依赖两次调用的间隔是否在缓存有效期内。

敏感性:

  • r 从 0.2 升到 0.5(其余不变):成本 = 1,558k × 0.5 + 170.4k = 949.4k 单位,约为不开缓存的 56%。缓存的收益对读取价格比例很敏感。
  • 不裁剪工具结果、每步新增 δ = 15k:从 22k 涨到压缩阈值 142k 只要 8 步(22k + 15k × 8 = 142k),而 δ = 6k 时要 20 步。每 100 步的压缩周期数从 5 个变成约 12–13 个(100 ÷ 8 = 12.5,具体取决于初始上下文和最后一段是否要压),每次压缩都会让摘要之后的部分重新写入。裁剪的收益不只是省 token,还有更少的缓存重置。

没算的:输出 token、压缩调用本身的输出、首 token 延迟(通常随未命中的 token 数增长,但比例要实测)、缓存过期造成的额外未命中。

怎么实测:按版本记录每次调用的缓存读、写 token 和首 token 延迟;统计每步新增 token 的分布和两次压缩之间的步数;向服务文档确认 r、w、有效期和匹配规则后,用实测值替换上面的每个假设,再重算。

Cross-references 回链

原理去哪里补

  • AI 2.2 前缀缓存原理,以及它对 system prompt 设计的反向约束 · AI 系统工程手册
  • AI 3.4 长上下文与检索的成本、质量边界 · AI 系统工程手册
  • AI 4.3 短期记忆与上下文压缩策略 · AI 系统工程手册
  • DV ch11 Context Engineering:上下文的组织与裁剪 · CCDV 备考手册
  • AR ch13 上下文管理:长会话里的上下文取舍 · CCAR 备考手册
  • A02 深度研究:检索子问题的上下文隔离与证据卡
  • A05 浏览器 Agent:截图和 DOM 这类大观察怎么进上下文
  • A06 长任务 goal Agent:目标钉住、账本与压缩后对账
  • B04 Memory:跨会话的记忆,和单次任务内的上下文分开
  • B06 怎么评估上下文策略有没有降低完成率
  • B09 延迟与成本:把缓存收益放进整体估算
Your Turn 本人填写

只写真实经历

只写真实经历,没有就空着,不编。公开版只有空表;填好的版本存在自己的私用练习记录里。

场景当时发生了什么我亲自做了什么(团队 / AI 做了什么)怎么发现的,结果和证据之后怎么防
长会话或压缩后,Agent 丢了任务或约束
一次改动让成本或延迟莫名上升

练习记录

日期口述用时脱稿?答不上的追问下一轮修正(≤2)