具体问题与边界
Prompt 不是把 system、history 和当前输入做字符串拼接。Codex 同时处理基础指令、AGENTS 指导、 历史规范化、World State、工具规格、压缩替换和增量请求。如果相同语义每次产生不同前缀,缓存会 失效;如果为了命中缓存而隐藏真实变化,模型又会在错误世界中推理。
本节把缓存友好视为编译约束,而不是正确性的第一原则,也不推断服务端未公开的缓存实现。
输入层与变化频率
| Prompt 输入 | 典型稳定期 | 编译要求 | 错误风险 |
|---|---|---|---|
| Base instructions | 模型/配置周期 | 保持稳定前缀 | 无意义改写造成 cache miss |
| Developer/AGENTS 指导 | 工作区/路径作用域 | 按优先级、边界和硬上限注入 | 越界或重复指令 |
| 规范 History | Thread | 保持 Item 顺序与 Call/Result 配对 | 非法模型输入 |
| World State patch | Step | 只表达真实变化 | 陈旧环境或重复大块文本 |
| ToolSpec | Step | 与 Runtime 同快照且序列稳定 | Spec/Handler 漂移 |
| Compaction summary | 替换 checkpoint | 明确替换旧前缀 | 双重历史与 token 膨胀 |
正常编译路径
- 从 TurnContext 选择基础指令和模型能力,形成稳定的请求前缀。
- ContextManager 输出规范模型视图:删除只供 UI 的瞬时事件,修复必要的 Call/Result 形状。
- 依据 StepContext 追加当前 World State 变化和本 Step ToolSpec。
- 计算请求身份或 prompt cache key;序列化必须对相同输入保持确定性。
- Transport 决定发送完整请求还是基于 previous response 的增量,但两种传输应表达同一语义输入。
“稳定”不等于永不变化。模型切换、权限变化、AGENTS 作用域变化或 Compact checkpoint 都必须产生 新的请求身份;强行沿用旧 key 只会把性能优化变成语义错误。
Python 风格伪代码
@dataclass(frozen=True)
class PromptSnapshot:
instructions: str
items: tuple[ResponseItem, ...]
tools: tuple[ToolSpec, ...]
cache_key: str
def compile_prompt(turn: TurnSnapshot, step: StepSnapshot, history: History) -> PromptSnapshot:
instructions = compile_stable_instructions(turn)
items = normalize_for_model(history.effective_items())
items += world_state_delta(step.world_state, history.last_world_state)
tools = stable_serialize_specs(step.tool_plan.specs)
cache_key = fingerprint(instructions, history.checkpoint_id, tools)
return PromptSnapshot(instructions, items, tools, cache_key)
async def send(snapshot: PromptSnapshot, session: ModelSession) -> ModelStream:
if session.can_increment(snapshot.cache_key):
return await session.send_incremental(snapshot)
return await session.send_full(snapshot)
伪代码没有把 fingerprint 当作正确性来源。它先生成完整 PromptSnapshot,再让 Transport 选择编码;
增量优化失败时可以退回完整请求。
失败与失效路径
| 故障 | 结果 | 正确处理 |
|---|---|---|
| 动态时间戳放进稳定前缀 | 每 Step 前缀都变化 | 动态上下文放入有界后缀 |
| Compact 后仍发送旧历史 | token 重复、语义冲突 | checkpoint 替换 effective history |
| Call 无 Result | Provider 拒绝或模型误解 | Prompt-only normalize 补配对结果 |
| 权限/工具改变却复用旧 key | 命中错误请求身份 | 将影响语义的快照纳入身份 |
| previous response 不可用 | 增量链中断 | 回退完整请求,不改 Prompt 语义 |
| 为缓存删除 World State 变化 | 模型观察陈旧世界 | 正确性优先,允许失效 |
设计判断
缓存友好设计的核心不是猜测 Provider 缓存算法,而是让同一语义得到确定序列,让变化只出现在其 真实作用域。这个原则同时改善测试快照、录制回放和故障诊断,即使 Provider 不提供缓存也有价值。
代价是编译器必须知道各片段的作用域、上限和替换关系。小型 Agent 若只有固定 system prompt 和短 history,可以采用单一编译函数;但一旦增加动态工具、压缩或恢复,就需要显式 PromptSnapshot。
证据与 Mini Codex
生产证据为 core/src/client_common.rs、core/src/context_manager/history.rs、core/src/client.rs
以及模型传输请求测试;4.24—4.28 和 5.9—5.12 已证明 checkpoint、cache key、previous response
与完整/增量请求边界。
Mini Codex 保留纯 PromptCompiler、规范 History 和录制回放 fingerprint:
cd examples/mini-codex
uv run pytest -q -k 'prompt or history or replay'
它没有实现生产 WebSocket、Provider cache policy 或远程 Compact,不能用于估算真实 cache hit。
本节边界
已经证明:Prompt 应先成为确定的语义快照,再交给传输层优化。下一节解释 ToolSpec 为什么也必须和 执行 Runtime 共享同一份 Step 计划。详细源码映射由研究仓库中的配套索引维护。
评论
登录后即可评论