具体问题与边界
怎样从规范历史生成一次模型请求,同时修复协议形状而不篡改耐久事实?
HistoryManager 保存完整规范 Item;model_view() 生成可变副本并修复 Tool Call/Result;PromptCompiler 只组合快照,不拥有历史。 本节不是把 Rust 改写成 Python;它从锁定提交的字段、调用顺序和测试行为提炼实现合同,再检查 Mini Codex 是否以 Python 的并发原语保持同一条不变量。
协议、类型与状态所有权
| 对象 | 创建/所有者 | 生命周期与作用域 | 是否持久化 |
|---|---|---|---|
| raw history | HistoryManager | append/extend/replace | 由 Rollout 持久化 |
| prompt view | HistoryManager.model_view | 每次请求临时创建 | 否 |
| CompiledPrompt | PromptCompiler | 一个 Step | 否 |
| synthetic ToolResult | prompt normalization | 稳定 UUIDv5 ID | 只在请求副本 |
正常路径
- Session 只把完整 Message、ToolCall、ToolResult 加入 raw history;Delta 不进入这里。
model_view()先收集所有 call_id 与 result call_id,再按原顺序构造新列表。- 孤儿 Result 没有对应 Call时从请求副本删除;无 Result 的 Call 后立即插入
abortedResult。 - 合成 Result ID 由源 ToolCall Item ID 和固定 namespace 用 UUIDv5 派生;重复 Resume 不改变 Prompt cache 形状。
- 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 沙箱。
失败、取消与恢复
| 故障或错误设计 | 会留下什么 | Mini Codex 的处理 |
|---|---|---|
| 直接改 raw history 补 Result | 恢复假设被伪装成真实执行结果 | 只改 prompt view |
| 合成 ID 每次随机 | 重复恢复改变请求指纹与缓存 | 固定 namespace 的 UUIDv5 |
| Compiler 内部读取全局 Registry | Spec 与 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_onlytest_router_uses_same_registry_snapshot_as_tool_specs
全量离线基线为 27 项通过;真实 Responses 测试需要显式环境变量,默认跳过。单项测试名用于定位,不代替对断言内容的解释。
官方源码导航
- codex-rs/core/src/context_manager/history.rs:
for_prompt、WorldState baseline 和规范化入口 - codex-rs/core/src/context_manager/normalize.rs:补 Call Output、移除孤儿 Output
- codex-rs/core/src/session/turn.rs:请求输入、工具与 Step Context 组合
Mini Codex 对照
src/mini_codex/context/history.py:raw/model_view/compact/rollbacksrc/mini_codex/context/compiler.py:纯 Prompt 组合src/mini_codex/models/client.py:ModelRequest Protocol
Mini Codex 保留本节的状态所有权、顺序和失败反馈;省略的产品能力会在边界处明确列出,不能由测试通过外推为生产等价。
本节边界
已经证明:规范历史只陈述已经发生且完整的 Item;Prompt 修复只保证模型协议可继续,不宣称未知工具副作用被撤销或执行。
尚未覆盖的生产问题由后续单元继续展开;公开版保留官方源码链接和可运行测试合同。
评论
登录后即可评论