Codex 的工具调用系统
Codex 的工具调用系统
学习目标
- 理解 Codex 工具系统的四层架构实现
- 掌握 Codex 的并行/串行工具执行模型
- 分析 Codex 的工具审批和编排策略
前置知识
本章涉及工具调用的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 Codex 的具体实现。
项目实践
工具系统四层架构
Codex 的工具系统分布在两个 crate:
codex-rs/tools/src/:规格定义层codex-rs/core/src/tools/:路由、编排、执行层
ToolSpec 枚举
codex-rs/tools/src/lib.rs 中的 ToolSpec 定义了 Codex 支持的所有工具类型:
pub enum ToolSpec { Function(ResponsesApiTool), // 标准函数调用工具 Namespace(ResponsesApiNamespace), // 命名空间工具 ToolSearch { ... }, // 工具搜索 ImageGeneration { ... }, // 图像生成 WebSearch { ... }, // 网络搜索 Freeform(FreeformTool), // 自定义自由格式工具}设计特点:ToolSpec 直接序列化为 OpenAI Responses API 的 Tool JSON,无需额外的转换层。
核心 Handler 列表
core/src/tools/handlers/mod.rs 中注册的所有 Handler:
| Handler | 功能 | 风险等级 |
|---|---|---|
shell | Shell 命令执行 | 高 |
unified_exec | 统一执行命令 | 高 |
apply_patch | 应用代码补丁 | 高 |
mcp | MCP 工具调用 | 中 |
mcp_resource | MCP 资源操作 | 中 |
plan | 生成执行计划 | 低 |
goal | 目标管理(创建/获取/更新) | 低 |
request_permissions | 请求权限变更 | 中 |
request_user_input | 请求用户输入 | 低 |
request_plugin_install | 请求安装插件 | 中 |
tool_search | 工具搜索 | 低 |
multi_agents | 多 Agent 管理 | 中 |
agent_jobs | Agent 作业管理 | 中 |
view_image | 查看图像 | 低 |
test_sync | 测试同步 | 低 |
dynamic | 动态工具 | 中 |
ToolOrchestrator 编排逻辑
ToolOrchestrator 是 Codex 工具执行的核心编排器,统一处理:
- 审批决策:根据
execpolicy规则判断是否需要用户确认 - 沙箱选择:根据工具类型和当前配置选择合适的沙箱
- 重试策略:失败时按配置重试,带指数退避
// 伪代码示意impl ToolOrchestrator { async fn execute_tool(&self, tool_call: ToolCall, turn_context: &TurnContext, ) -> Result<ToolResult> { // 1. 检查审批策略 let approval = self.check_policy(&tool_call).await?; if approval == Decision::Prompt { self.request_user_approval(&tool_call).await?; }
// 2. 选择沙箱 let sandbox = self.select_sandbox(&tool_call, turn_context);
// 3. 执行工具 let result = self.run_with_retry( || handler.execute(tool_call, sandbox), retry_policy, ).await?;
Ok(result) }}并行工具执行
ToolCallRuntime(core/src/tools/parallel.rs)管理并行/串行执行:
pub(crate) struct ToolCallRuntime { lock: RwLock<()>, // 读写锁控制并发 cancellation: CancellationToken, // 取消信号}执行模式:
- 并行:获取
RwLock的 read guard,多个读 guard 可共存 - 串行:获取
RwLock的 write guard,独占执行 - 取消:
CancellationToken传播到所有子任务,中断时统一终止
调度决策:Codex 默认尽可能并行执行工具,除非:
- 工具之间存在资源依赖(如都操作同一文件)
- 工具需要用户审批(审批必须串行)
- 工具配置了
serial: true
问题与规避
工具超时与取消
Codex 为每个工具调用设置超时。超时机制通过 tokio::time::timeout 实现:
- 超时后发送 SIGTERM 给子进程
- 宽限期(如 5 秒)后发送 SIGKILL
CancellationToken确保所有相关任务同步终止
沙箱切换开销
不同工具可能需要不同的沙箱级别。Codex 的策略是:
- 在 Turn 开始时确定沙箱配置,尽量保持不变
- 仅在必要时(如权限提升请求)切换沙箱
- 沙箱切换通过
shell-escalation机制实现,而非完全重建沙箱
工具结果过大
Codex 对工具输出进行截断处理:
- 命令输出超过阈值时截断,保留头部和尾部
- 文件读取支持分页(
offset+max_lines) - 图像数据压缩或转为描述文本
设计取舍
为什么将工具规格和实现分离到不同 crate?
Codex 将 ToolSpec 定义在独立的 tools crate 中,而 Handler 实现放在 core crate 中。这种分离使得:
- 其他 crate(如
protocol、tui)可以引用工具规格而不依赖工具实现 - 工具实现的变更不会影响依赖工具规格的上层代码
- 便于为工具规格编写独立的测试
代价是增加了 crate 间的协调成本,新增工具时需要同时修改两个 crate。
为什么使用 RwLock 而非异步锁?
Codex 在 ToolCallRuntime 中使用 std::sync::RwLock 而非 tokio::sync::RwLock。这是因为工具执行本身就是异步的,获取锁的操作很短(只是检查是否可以执行),使用同步锁减少了异步状态机的复杂度。代价是在锁竞争时可能阻塞异步任务。