雨天小六

读懂 Codex(9.5):PromptCompiler 与 HistoryManager

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

#Codex#Agent Runtime#Python#软件架构

具体问题与边界

怎样从规范历史生成一次模型请求,同时修复协议形状而不篡改耐久事实?

HistoryManager 保存完整规范 Item;model_view() 生成可变副本并修复 Tool Call/Result;PromptCompiler 只组合快照,不拥有历史。 本节不是把 Rust 改写成 Python;它从锁定提交的字段、调用顺序和测试行为提炼实现合同,再检查 Mini Codex 是否以 Python 的并发原语保持同一条不变量。

协议、类型与状态所有权

对象创建/所有者生命周期与作用域是否持久化
raw historyHistoryManagerappend/extend/replace由 Rollout 持久化
prompt viewHistoryManager.model_view每次请求临时创建
CompiledPromptPromptCompiler一个 Step
synthetic ToolResultprompt normalization稳定 UUIDv5 ID只在请求副本

正常路径

PromptCompiler 与 HistoryManager正常路径图
图 9.5-1:怎样从规范历史生成一次模型请求,同时修复协议形状而不篡改耐久事实?
  1. Session 只把完整 Message、ToolCall、ToolResult 加入 raw history;Delta 不进入这里。
  2. model_view() 先收集所有 call_id 与 result call_id,再按原顺序构造新列表。
  3. 孤儿 Result 没有对应 Call时从请求副本删除;无 Result 的 Call 后立即插入 aborted Result。
  4. 合成 Result ID 由源 ToolCall Item ID 和固定 namespace 用 UUIDv5 派生;重复 Resume 不改变 Prompt cache 形状。
  5. PromptCompiler 把 base instructions、prompt view、ToolSpec、turn/step 身份与 generation 组合成 ModelRequest;它不执行网络或修改 History。

机制调用链

1. Session 只把完整 Message、ToolCall、ToolResult 加入 raw history;Delta 不进入这里
→ 2. `model_view()` 先收集所有 call_id 与 result call_id,再按原顺序构造新列表
→ 3. 孤儿 Result 没有对应 Call时从请求副本删除;无 Result 的 Call 后立即插入 `aborted` Result
→ 4. 合成 Result ID 由源 ToolCall Item ID 和固定 namespace 用 UUIDv5 派生;重复 Resume 不改变 Prompt cache 形状
→ 5. PromptCompiler 把 base instructions、prompt view、ToolSpec、turn/step 身份与 generation 组合成 ModelRequest;它不执行网络或修改 History

这里最重要的不是类名,而是控制权何时转移:创建者决定 ID 和初值,状态所有者决定何时修改,跨越 await 的调用必须明确取消、失败和可见性边界。任何绕过这些边界的“便捷调用”都会让恢复或并发测试失去确定答案。

Python 风格伪代码

def model_view(raw_items):
    call_ids = {x.call_id for x in raw_items if is_call(x)}
    result_ids = {x.call_id for x in raw_items if is_result(x)}
    view = []
    for item in raw_items:
        if is_result(item) and item.call_id not in call_ids:
            continue
        view.append(item)
        if is_call(item) and item.call_id not in result_ids:
            view.append(ToolResult(
                id=uuid5(FIXED_NAMESPACE, item.id),
                call_id=item.call_id,
                output="aborted",
                is_error=True,
            ))
    return tuple(view)

def compile(turn, step, history, tool_plan):
    return ModelRequest(
        instructions=turn.base_instructions,
        history=history,
        tools=tool_plan.specs,
        turn_id=turn.id,
        step_id=step.id,
        tool_generation=tool_plan.generation,
    )

伪代码只保留设计职责;Mini Codex 的可运行版本见下方实现导航。它没有伪造官方源码中不存在的 Python API,也没有把路径策略写成 OS 沙箱。

失败、取消与恢复

PromptCompiler 与 HistoryManager失败路径图
图 9.5-2:失败必须回到实际状态所有者,不能用一条通用异常吞掉协议差异。
故障或错误设计会留下什么Mini Codex 的处理
直接改 raw history 补 Result恢复假设被伪装成真实执行结果只改 prompt view
合成 ID 每次随机重复恢复改变请求指纹与缓存固定 namespace 的 UUIDv5
Compiler 内部读取全局 RegistrySpec 与 Router 快照可能漂移调用方传入同一个 ToolPlan
半截模型 Delta 进入历史后续请求包含未完成消息Accumulator 完成后才 append

必须保持的不变量

规范历史只陈述已经发生且完整的 Item;Prompt 修复只保证模型协议可继续,不宣称未知工具副作用被撤销或执行。

这条不变量同时约束正常路径、异常路径和恢复路径。只在 happy path 里得到正确输出,不足以证明该模块边界成立。

设计取舍

复制与规范化增加每次采样成本,但消除了恢复逻辑改写审计事实的诱惑。Mini 编译器没有官方的多层指令、媒体、缓存键与 token budget。

源码能够直接证明类型、分支、调用顺序和测试期望;关于工程动机的解释是基于这些事实的设计归纳,不冒充未公开承诺。

测试与复现实验

cd examples/mini-codex
uv run pytest -q -k 'test_resume_reports_incomplete_turn_and_repairs_prompt_only' -k 'test_router_uses_same_registry_snapshot_as_tool_specs'
uv run mypy src

本节对应的关键断言:

  • test_resume_reports_incomplete_turn_and_repairs_prompt_only
  • test_router_uses_same_registry_snapshot_as_tool_specs

全量离线基线为 27 项通过;真实 Responses 测试需要显式环境变量,默认跳过。单项测试名用于定位,不代替对断言内容的解释。

官方源码导航

Mini Codex 对照

  • src/mini_codex/context/history.py:raw/model_view/compact/rollback
  • src/mini_codex/context/compiler.py:纯 Prompt 组合
  • src/mini_codex/models/client.py:ModelRequest Protocol

Mini Codex 保留本节的状态所有权、顺序和失败反馈;省略的产品能力会在边界处明确列出,不能由测试通过外推为生产等价。

本节边界

已经证明:规范历史只陈述已经发生且完整的 Item;Prompt 修复只保证模型协议可继续,不宣称未知工具副作用被撤销或执行。

尚未覆盖的生产问题由后续单元继续展开;公开版保留官方源码链接和可运行测试合同。

阅读导航

上一节:9.4 · 下一节:9.6

评论


← 返回文章列表