Skip to content

附录 F|一枪采样:模型看见什么、失败走哪条、怎么钉住 ​

先别把 messages[] 和「能跑」当成同一件事 ​

读完 B 组和 F3,下一件错事是打开 Responses 请求,把 input 当成聊天记录,把 5xx 和沙箱拒绝丢进同一个 retry(),再用肉眼看 TUI 证明循环对了。

判断先说清楚:一次采样的合同是 Prompt。 三块:input(历史投影)、tools(这一 Step 冻住的 spec)、base_instructions(模型/协作模板)。课程判断在 B7;这一附录只钉 怎么测:mock SSE、失败分流、test_codex。

这一附录服务 F3 的 P0/P1,不新开产品层。


1. Prompt 三块,不要焊成一段 system ​

build_prompt(core/src/session/turn.rs:1508-1525)几乎只做赋值:

字段从哪来不是什么
inputhistory.for_prompt(modalities)(:1571-1576)不是 TUI transcript,也不是 raw AGENTS.md 文件
toolsstep_context.tool_router.model_visible_specs()不是 Handler 注册表全文;看不见的不能 call
base_instructionsSession::get_prompt_base_instructions()(session/mod.rs:1409-1426)不是项目说明书。provenance 是 Model 或 Custom(/review 换的是这一块)

for_prompt 按模型 modality 剥图/音频(context_manager/history.rs:392-396)。历史里已经落盘的 fragment——环境、AGENTS.md、TurnAborted——以 user 角色的带标记 item 出现,不是改 base 文本。C4 的 Personality 只可能动 base 里 # Personality 一节;C1 的说明书走 B4 的差量注入。

parallel_tool_calls 在 build_prompt 里写死 true(turn.rs:1518)。能不能真并行,仍看 B2 的门闩:工具自己报名,默认不准。

没有「三块拆开」会怎样?你会把 AGENTS.md 写进 system,换模型就丢政策;或把 MCP 目录整份塞进 base,compact 永远在救火。B4 禁止 system +=,合同就在这个 struct。


2. 一次 apply_patch 在两枪之间发生什么 ​

不要背 JSON。跟 tool_harness.rs 里那条路径(:324-388):

  1. 用户话进 TurnInput → start_or_steer_turn。
  2. 第一枪 mock SSE:ev_apply_patch_custom_tool_call。模型「看见」的是上一节那份 Prompt;吐出的是 freeform 补丁,不是 sed。
  3. Orchestrator:审批(测试里 Never)→ 选沙箱 → Runtime 落盘。Event:FileChange started/completed。
  4. 补丁结果变成 input 里的 tool output。
  5. 第二枪 mock SSE:ev_assistant_message("patch complete")。同一 ModelClientSession(B1),同一 Turn,新的 Step 可以复用 router。

第二枪的 input 比第一枪多了一对 call/output。resume 能重建,是因为这两枪的 item 都进了 rollout(B4),不是因为测试进程还活着。

shell 里的 apply_patch <<EOF 会被拦截成同一套 Runtime(附录 E)。学习者抄循环时,断言应该落在 文件内容和 Event,不要断言命令字符串。


3. 失败谱:采样、窗口、工具、额度不是一种 retry ​

run_sampling_request 的内层 loop(turn.rs:1566-1647)只重试 Responses 流。默认最多 5 次(DEFAULT_STREAM_MAX_RETRIES,model-provider-info/src/lib.rs:30, 443-445)。用尽且还能切传输,才 Session 级 HTTP fallback(responses_retry.rs:92-106,B1)。远程 compact 故意把流重试压到 2(附录 C),别混用。

失败谁处理不准做的
流断开 / 可重试 5xxhandle_retryable_response_stream_error;重试时重新 for_prompt每枪新建 ModelClientSession;把 compact 的重试预算用光
ContextWindowExceeded立刻返回(turn.rs:1613-1615),交给采样前 compact / 外层在采样 loop 里截断 input
UsageLimitReached记 rate limits,返回(:1617-1622)当连接失败去重连
不可重试的采样错误!err.is_retryable() 直接失败(:1632-1633)盲 sleep 再 POST
沙箱拒绝Orchestrator 升级再试(B3)把 exit 2/126/127 当拒绝
工具/MCP 超时各工具自己的顶(MCP 默认 300s,D3);结果回模型卡死整条 Turn 当「模型挂了」
额度低记忆管道跳过(C2);自动化不自动停(E7)当成 --yolo 或关沙箱

窗口满必须发生在 下一枪采样之前(B4)。采样已经超窗,再改数组就是 rewrite。工具失败是下一枪的 item,不是采样重试。

没有这张表会怎样?一个 retry() 把 compact、审批升级、MCP hang 搅在一起:要么把用户最后一句吞掉,要么把 typo 升出沙箱。


4. 对照:test_codex + mount_sse_once 钉的是循环,不是 widget ​

仓库自己的集成测试在 core/tests/suite/。夹具:

  • test_codex()(core/tests/common/test_codex.rs:1363)起一套连 mock provider 的 Session
  • mount_sse_once(responses.rs:1108)只允许一次 /responses POST
  • ev_* + sse(...) 编模型吐出的事件
  • wait_for_event 听 EventMsg,不是读 stdout 颜色

P0 合同可以缩成 apply_patch_tool_executes_and_emits_patch_events(tool_harness.rs:324)在观察什么:

  1. 第一枪是补丁 call,第二枪是「做完了」
  2. 磁盘上出现 notes.txt
  3. 过程中有 FileChange 类 Event

抄自己的 harness 时,用同等断言。不要新写「prompt 里包含某字符串」——那是钉实现。F3 的完成定义是可观测行为:审批(或 Never)、patch、完成。

跑这一条即可(在 codex-rs/):

text
just test -p codex-core apply_patch_tool_executes_and_emits_patch_events

不要为此开全量 just test。完整套件要问过再跑(仓库 AGENTS.md)。

没有 mock SSE 会怎样?你只能对着真模型手工点。P0「能跑完一轮」会变成不可回归的演示。


5. 结语:带走一句话 ​

一枪采样的合同是 Prompt 的三块——历史投影、这一 Step 的 tools、base 指令——AGENTS.md 在 input 里;流才重试,窗口满走 compact,工具失败当下一枪的 item;用 test_codex 钉文件和 Event,不要钉 TUI。判断回 B7;枪怎么飞出去回 G1。