雨天小六

读懂 Codex(10.3):Prompt 编译和缓存友好设计

· 更新于 2026-08-03 · 专栏:读懂 Codex

#Codex#Agent Runtime#软件架构#系统设计

具体问题与边界

Prompt 不是把 system、history 和当前输入做字符串拼接。Codex 同时处理基础指令、AGENTS 指导、 历史规范化、World State、工具规格、压缩替换和增量请求。如果相同语义每次产生不同前缀,缓存会 失效;如果为了命中缓存而隐藏真实变化,模型又会在错误世界中推理。

本节把缓存友好视为编译约束,而不是正确性的第一原则,也不推断服务端未公开的缓存实现。

输入层与变化频率

Prompt 输入典型稳定期编译要求错误风险
Base instructions模型/配置周期保持稳定前缀无意义改写造成 cache miss
Developer/AGENTS 指导工作区/路径作用域按优先级、边界和硬上限注入越界或重复指令
规范 HistoryThread保持 Item 顺序与 Call/Result 配对非法模型输入
World State patchStep只表达真实变化陈旧环境或重复大块文本
ToolSpecStep与 Runtime 同快照且序列稳定Spec/Handler 漂移
Compaction summary替换 checkpoint明确替换旧前缀双重历史与 token 膨胀

正常编译路径

稳定指令前缀、规范历史、动态 World State 和工具规格编译成模型请求的流程图
图 10.3-1:先规范语义,再确定稳定前缀与动态后缀;缓存键和增量传输不能改变模型实际应见内容。
  1. 从 TurnContext 选择基础指令和模型能力,形成稳定的请求前缀。
  2. ContextManager 输出规范模型视图:删除只供 UI 的瞬时事件,修复必要的 Call/Result 形状。
  3. 依据 StepContext 追加当前 World State 变化和本 Step ToolSpec。
  4. 计算请求身份或 prompt cache key;序列化必须对相同输入保持确定性。
  5. 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 选择编码; 增量优化失败时可以退回完整请求。

失败与失效路径

Prompt 缓存键复用错误、历史不规范和动态内容前移造成的失败路径
图 10.3-2:缓存失效只损失性能;错误复用旧语义则会让模型在错误权限、工具或历史上继续推理。
故障结果正确处理
动态时间戳放进稳定前缀每 Step 前缀都变化动态上下文放入有界后缀
Compact 后仍发送旧历史token 重复、语义冲突checkpoint 替换 effective history
Call 无 ResultProvider 拒绝或模型误解Prompt-only normalize 补配对结果
权限/工具改变却复用旧 key命中错误请求身份将影响语义的快照纳入身份
previous response 不可用增量链中断回退完整请求,不改 Prompt 语义
为缓存删除 World State 变化模型观察陈旧世界正确性优先,允许失效

设计判断

缓存友好设计的核心不是猜测 Provider 缓存算法,而是让同一语义得到确定序列,让变化只出现在其 真实作用域。这个原则同时改善测试快照、录制回放和故障诊断,即使 Provider 不提供缓存也有价值。

代价是编译器必须知道各片段的作用域、上限和替换关系。小型 Agent 若只有固定 system prompt 和短 history,可以采用单一编译函数;但一旦增加动态工具、压缩或恢复,就需要显式 PromptSnapshot。

证据与 Mini Codex

生产证据为 core/src/client_common.rscore/src/context_manager/history.rscore/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 计划。详细源码映射由研究仓库中的配套索引维护。

阅读导航

上一节:10.2 · 下一节:10.4

评论


← 返回文章列表