第3节 决策循环、行动接口与工具调度

技术报告§3.1–3.7
Codex 通过 Turn 有限状态机和采样-工具飞轮实现多步推理,在通用 Shell 接口与安全可控之间取得平衡。

3.1 Turn 作为控制循环

Codex 的核心执行单元是 Turn——从用户输入到最终回复的完整交互周期。每个 Turn 可形式化为一个有限状态机,其状态转移序列可表示为:

$$\text{Pre} \to \text{Compacting} \to \text{ContextBuilding} \to \text{Sampling} \leftrightarrow \text{ToolExecution} \to \text{Complete}$$

其中:

状态转移由转移函数 $\delta: S \times E \to S$ 控制,其中 $S$ 为状态集合,$E$ 为事件集合。关键的事件类型包括模型响应、工具执行完成、用户中断、上下文窗口超限等。

循环不变量保证整个 Turn 执行过程中的状态一致性:

$$\forall n \in \mathbb{N}: \text{Invariant}(state_n) \implies \text{Invariant}(state_{n+1})$$

核心不变量包括:

  1. 历史完整性:对话历史始终包含所有已处理的输入和输出
  2. TurnContext 不可变性:Turn 级别配置在整个执行过程中保持不变
  3. 环境快照一致性:Turn 开始时的环境选择快照在多步执行中保持一致

单个 Turn 的执行可递归定义为:

$$\text{run\_turn}(input) = \begin{cases} \text{complete} & \text{if } \neg \text{needs\_follow\_up} \\ \text{run\_turn}(\text{compact}(\text{history} \cup tool\_result)) & \text{if } \text{needs\_follow\_up} \wedge \text{token\_limit\_reached} \\ \text{run\_turn}(\text{history} \cup tool\_result) & \text{otherwise} \end{cases}$$

这种递归结构支持任意深度的工具调用链,同时通过压缩机制确保上下文窗口不超限。

3.2 采样-工具飞轮

采样-工具飞轮是 Codex 实现多步推理的核心机制。每轮迭代可形式化为:

$$\mathcal{F}_t: (C_t, M_t) \xrightarrow{\text{Sampling}} (A_t, O_t) \xrightarrow{\text{Execution}} (C_{t+1}, M_{t+1})$$

其中:

整个飞轮过程可建模为迭代函数系统(Iterated Function System, IFS):

$$C_{t+1} = f(C_t) = \begin{cases} \text{compact}(C_t \cup \{A_t, O_t\}) & \text{if } \|C_t \cup \{A_t, O_t\}\|_1 > B_{\text{context}} \\ C_t \cup \{A_t, O_t\} & \text{otherwise} \end{cases}$$

其中 $\|\cdot\|_1$ 表示 Token 计数,$B_{\text{context}}$ 为上下文窗口上限。

终止条件可形式化为谓词 $\tau: \mathcal{S} \to \{\text{true}, \text{false}\}$:

$$\tau(s) = \begin{cases} \text{true} & \text{if } A_t = \text{AssistantMessage} \wedge \neg \text{has\_pending\_input} \\ \text{true} & \text{if } t > T_{\max} \\ \text{true} & \text{if } \sum_{i=1}^{t} \text{tokens}(C_i) > B_{\text{budget}} \\ \text{false} & \text{otherwise} \end{cases}$$

其中:

最大轮数与预算约束下的有界性可通过归纳法证明:

定理:在给定最大轮数 $T_{\max}$ 和预算上限 $B_{\text{budget}}$ 的条件下,采样-工具飞轮必然在有限步骤内终止。

证明

  1. 每轮迭代至少产生 $\text{tokens}(C_{t+1}) - \text{tokens}(C_t) \geq 1$ 的 Token 增量
  2. 预算约束 $\sum_{i=1}^{t} \text{tokens}(C_i) \leq B_{\text{budget}}$ 确保 $t \leq B_{\text{budget}}$
  3. 轮数约束 $t \leq T_{\max}$ 直接提供上界
  4. 因此 $t \leq \min(T_{\max}, B_{\text{budget}})$,飞轮必然终止

3.3 中断语义

中断是用户意图的体现,而非系统错误。Codex 将中断形式化为高优先级外部事件插入:

$$\text{InterruptEvent} = (\text{type}, \text{timestamp}, \text{reason}) \in \mathcal{E}_{\text{external}}$$

中断的传播延迟取决于其插入点与当前执行状态的相对位置:

$$d_{\text{interrupt}} = t_{\text{propagate}} - t_{\text{insert}}$$

其中 $t_{\text{insert}}$ 为中断产生时间,$t_{\text{propagate}}$ 为中断被处理时间。

传播延迟的下界由当前操作的可中断性决定:

$$d_{\text{interrupt}} \geq \begin{cases} 0 & \text{if } \text{state} = \text{Idle} \\ \Delta t_{\text{cancellation}} & \text{if } \text{state} \in \{\text{Sampling}, \text{ToolExecution}\} \\ \Delta t_{\text{checkpoint}} & \text{if } \text{state} = \text{Compacting} \end{cases}$$

中断的一致性保证通过原子操作实现:

$$\text{abort\_all\_tasks}(\text{reason}) = \text{take\_active\_turn}(); \text{handle\_task\_abort}(); \text{emit\_event}(); \text{clear\_pending}()$$

这确保了中断处理的四个步骤要么全部执行,要么全部不执行,避免了中间状态的不一致。

进行中的工具调用处理遵循最佳effort原则:

$$\text{ToolResult}_{\text{aborted}} = \begin{cases} \text{partial\_output} & \text{if } \text{tool\_completed} \\ \text{Error}(\text{TurnAborted}) & \text{if } \text{tool\_interrupted} \\ \text{discard} & \text{if } \text{tool\_not\_started} \end{cases}$$

中断点与可恢复点的选择遵循策略:

$$\mathcal{R}_{\text{recoverable}} = \{s \in \mathcal{S} \mid \text{persistent}(s) \wedge \text{can\_rollback}(s)\}$$

其中 $\text{persistent}(s)$ 表示状态已持久化到 Rollout,$\text{can\_rollback}(s)$ 表示状态可回滚。

3.4 行动接口设计经济学

行动空间的设计面临接口覆盖度与安全可判定性的根本权衡。Codex 采用"通用 Shell + 策略层"架构,其行动空间可表示为:

$$\mathcal{A}_{\text{Codex}} = \mathcal{A}_{\text{shell}} \cup \mathcal{A}_{\text{patch}} \cup \mathcal{A}_{\text{search}} \cup \mathcal{A}_{\text{network}}$$

其中 $\mathcal{A}_{\text{shell}} = \{(\text{command}, \text{args}) \mid \text{command} \in \Sigma^*\}$ 是命令执行的通用接口。

对比细粒度安全工具的并集:

$$\mathcal{A}_{\text{fine\_grained}} = \bigcup_{i=1}^{n} \mathcal{A}_{\text{tool}_i}$$

其中每个 $\mathcal{A}_{\text{tool}_i}$ 是受限的专用接口,如 $\mathcal{A}_{\text{read\_file}} = \{(\text{path}) \mid \text{path} \in \mathcal{P}_{\text{allowed}}\}$。

定理:通用 Shell 接口的表达能力强于细粒度工具的并集。

证明

  1. 对于任意细粒度工具 $T_i$,存在 Shell 命令 $c_i$ 使得 $\text{execute}(c_i) = T_i(\cdot)$
  2. 存在复合操作(如管道、重定向)无法分解为单一细粒度工具调用
  3. 因此 $\mathcal{A}_{\text{shell}} \supsetneq \bigcup_{i=1}^{n} \mathcal{A}_{\text{tool}_i}$

然而,这种表达能力的提升带来了安全判定的复杂化:

$$\text{SafetyPred}(\mathcal{A}_{\text{shell}}) = \{p \mid \forall a \in \mathcal{A}_{\text{shell}}: p(a) \in \text{PSPACE}\}$$

相比之下,细粒度工具的安全性谓词通常在多项式时间内可判定:

$$\text{SafetyPred}(\mathcal{A}_{\text{tool}_i}) = \{p \mid \forall a \in \mathcal{A}_{\text{tool}_i}: p(a) \in \text{P}\}$$

Codex 通过策略层缓解这一矛盾,将安全性判定分解为:

$$\text{Safe}(a) = \text{ApprovalCheck}(a) \wedge \text{SandboxCheck}(a) \wedge \text{PolicyCheck}(a)$$

其中:

这种分层设计使得安全谓词的复杂度从 $\text{PSPACE}$ 降低到多项式级别。

3.5 结构化编辑与检索的上下文成本

Codex 的两种文件操作模式——补丁式编辑与全文件重写——在 Token 成本上存在显著差异。

设文件 $F$ 的原始大小为 $|F|$ Token,补丁修改的增量为 $\Delta$,则两种模式的成本函数为:

$$\text{Cost}_{\text{patch}}(F, \Delta) = \text{overhead}_{\text{patch}} + \|\Delta\|_1$$

$$\text{Cost}_{\text{rewrite}}(F, \Delta) = \text{overhead}_{\text{tool}} + |F| + \|\Delta\|_1$$

其中:

成本差为:

$$\Delta \text{Cost} = \text{Cost}_{\text{rewrite}} - \text{Cost}_{\text{patch}} = (|F| - \text{overhead}_{\text{patch}} + \text{overhead}_{\text{tool}})$$

当 $|F| \gg \text{overhead}_{\text{patch}} - \text{overhead}_{\text{tool}}$ 时,补丁式编辑显著优于重写。

检索结果截断的 Token 成本可建模为:

$$\text{Cost}_{\text{search}}(q, L) = \text{overhead}_{\text{tool}} + L \cdot \text{avg\_result\_size} + \text{metadata}$$

其中:

设完整结果集大小为 $N$,则截断的信息损失可表示为:

$$\text{InfoLoss}(L, N) = H(\text{top}-L) - H(\text{top}-N)$$

其中 $H(\cdot)$ 为熵函数,$\text{top}-k$ 表示按相关性排序的前 $k$ 个结果。

检索参数 $(L, N)$ 的选择是信息量-成本的帕累托最优问题:

$$\max_{L} \left\{ \frac{\text{InfoGain}(L)}{\text{Cost}_{\text{search}}(L)} \right\} \quad \text{s.t.} \quad L \leq N$$

Codex 的默认选择 $L=20$ 是基于经验的最优值,平衡了上下文压力与信息完整性。

3.6 并行工具调用的收益模型

当多个工具调用相互独立时,并行执行可显著降低总时延。设工具调用集合为 $\mathcal{T} = \{T_1, T_2, \ldots, T_n\}$,其中每个工具的执行时间为 $t_i$。

串行执行的总时延为:

$$T_{\text{serial}} = \sum_{i=1}^{n} t_i$$

并行执行的总时延为:

$$T_{\text{parallel}} = \max_{i \in \{1, \ldots, n\}} t_i + \delta_{\text{coord}}$$

其中 $\delta_{\text{coord}}$ 为协调开销(锁获取、结果聚合等)。

加速比定义为:

$$S = \frac{T_{\text{serial}}}{T_{\text{parallel}}} = \frac{\sum_{i=1}^{n} t_i}{\max_i t_i + \delta_{\text{coord}}}$$

并行效率为:

$$E = \frac{S}{n} = \frac{\sum_{i=1}^{n} t_i}{n \cdot (\max_i t_i + \delta_{\text{coord}})}$$

当所有工具执行时间相等($t_i = t$)且协调开销可忽略($\delta_{\text{coord}} \approx 0$)时:

$$S_{\text{ideal}} = n, \quad E_{\text{ideal}} = 1$$

Codex 的并行控制采用读写锁策略:

$$\text{Lock}(T_i) = \begin{cases} \text{RwLockRead} & \text{if } \text{supports\_parallel}(T_i) \\ \text{RwLockWrite} & \text{otherwise} \end{cases}$$

这确保了支持并行的工具可同时执行,而不支持并行的工具独占访问。

3.7 设计原理剖析

3.7.1 飞轮的稳定性

采样-工具飞轮的稳定性依赖于错误不发散性质。设错误传播函数为 $\epsilon: \mathcal{E} \to \mathcal{E}$,其中 $\mathcal{E}$ 为错误空间。

稳定性条件:对于任意初始错误 $\epsilon_0$,存在 $k \in \mathbb{N}$ 使得 $\epsilon_k \in \mathcal{E}_{\text{benign}}$。

Codex 通过以下机制保证稳定性:

  1. 重试机制:临时性网络错误通过指数退避重试(见第9章)
  2. 错误隔离:工具失败通过 `ErrorItem` 呈现,不影响 Turn 生命周期
  3. 审批回退:沙箱拒绝时自动回退到无沙箱执行并重新审批
  4. 上下文压缩:自动压缩机制防止上下文窗口超限导致的级联失败

3.7.2 可观测性

事件系统(见第10章)提供完整的可观测性。每个操作都通过事件广播:

$$\text{Event} = (\text{type}, \text{payload}, \text{timestamp}) \in \mathcal{E}_{\text{broadcast}}$$

事件流满足以下性质:

  1. 完整性:$\forall o \in \text{Operations}, \exists e \in \text{Events}: \text{caused}(o, e)$
  2. 有序性:$\forall e_1, e_2 \in \text{Events}: \text{timestamp}(e_1) < \text{timestamp}(e_2) \implies e_1 \prec e_2$
  3. 类型安全:事件类型通过 Rust 枚举保证编译时完整性

3.7.3 确定性回填

工具结果的回填采用确定性策略,确保模型可以基于结果继续推理:

$$\text{backfill}(tool\_result) = \text{ResponseItem}::FunctionCallResult \{\text{call\_id}, \text{output}\}$$

回填策略满足:

  1. 原子性:工具结果要么完全回填,要么完全不回填
  2. 顺序性:工具调用与结果回填按 FIFO 顺序处理
  3. 幂等性:重复回填相同结果不会产生副作用

这种设计使得采样-工具飞轮形成一个闭合反馈环:模型生成工具调用 → 执行工具 → 回填结果 → 模型基于结果继续推理 → ...

本节符号表

符号含义
$S$状态集合
$E$事件集合
$\delta$状态转移函数
$C_t$第 $t$ 轮对话上下文
$M_t$第 $t$ 轮模型状态
$A_t$第 $t$ 轮生成的动作
$O_t$第 $t$ 轮执行的观测
$\mathcal{F}_t$第 $t$ 轮飞轮函数
$\tau$终止条件谓词
$T_{\max}$最大轮数限制
$B_{\text{context}}$上下文窗口上限
$B_{\text{budget}}$Token 预算上限
$d_{\text{interrupt}}$中断传播延迟
$\mathcal{A}_{\text{shell}}$Shell 命令动作空间
$\mathcal{A}_{\text{tool}_i}$第 $i$ 个细粒度工具动作空间
$\text{SafetyPred}(\mathcal{A})$动作空间 $\mathcal{A}$ 的安全谓词集合
$\text{Cost}_{\text{patch}}$补丁式编辑成本函数
$\text{Cost}_{\text{rewrite}}$全文件重写成本函数
$\text{InfoLoss}(L, N)$信息损失函数
$T_{\text{serial}}$串行执行总时延
$T_{\text{parallel}}$并行执行总时延
$S$加速比
$E$并行效率
$\delta_{\text{coord}}$并行协调开销
$\epsilon$错误传播函数
$\mathcal{E}_{\text{benign}}$良性错误空间
$\text{Event}$事件类型
$\mathcal{E}_{\text{broadcast}}$广播事件空间
← 第2节第4节 →