雨天小六

读懂 Codex(5.18):Tool Search、Hosted Tool 和 Custom Tool 事件

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

#Codex#Agent Runtime#Responses API#流式协议#软件架构

同样名为“工具调用”的 ResponseItem 可能由客户端执行、由服务端托管执行,或只是展示服务端已经完成的动作。ToolRouter 不是看到 *Call 就一律本地 dispatch,而是按 Item 类型与 execution 字段分流。

具体问题与启用条件

本节解释模型响应中的工具事件类型和执行归属。具体每种工具如何执行、审批和回灌留到第六章。

条件来源决定字段或状态对本机制的影响
ToolSearchCallexecution=client 且有 call_id构造成客户端 tool_search 调用
ToolSearchCall其他 execution作为非本地调用,不 dispatch
CustomToolCallname/namespace/call_id/input构造 Custom payload 并本地路由
Hosted itemWebSearchCall/ImageGenerationCall服务端执行结果进入事件/历史,不本地调用

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
ResponseItem 类型模型/Responses 服务完成 Item声明调用种类与 execution
ToolCall payloadToolRouter本地执行期间仅为可客户端执行类型创建
Hosted Tool 状态服务端响应内Core 不伪造本地 ToolOutput
TurnItem projectionstream_events_utils客户端事件仅部分类型可展示

机制调用链如下:

OutputItemDone(ResponseItem)
→ ToolRouter.build_tool_call
→ FunctionCall → Function payload
→ CustomToolCall → Custom payload
→ ToolSearchCall(execution=client) → ToolSearch payload
→ ToolSearch hosted / WebSearch / ImageGeneration → 不本地 dispatch
→ 记录完整 Item/展示相应 TurnItem
→ 本地调用才产生 Tool Result 与 follow-up
模型工具 Item 按类型和 execution owner 分流
图 5.18-1:只有明确由 client 执行的调用进入本地 Router。

机制怎样工作

FunctionCall 和 CustomToolCall 都可形成本地 ToolCall,但 payload 不同:Function 保留 JSON 字符串,Custom 保留 freeform input。ToolSearchCall 只有同时具备 call_id 且 execution == "client" 时才解析 SearchToolCallParams 并路由到名为 tool_search 的工具;参数解析失败返回 RespondToModel。其他 ToolSearchCall 由服务端拥有执行,不创建本地 future。

WebSearchCall 是 Hosted Tool 的典型例子:它可被投影为 TurnItem、发 Started/Completed 并记录 History,但 ToolRouter 不为其创建本地执行。ImageGenerationCall 同样是服务端返回的完整项,当前通用非工具投影路径并不把所有变体都做成客户端 TurnItem;仍可作为 ResponseItem 记录。服务端意外返回 Function/Custom/ToolSearch Output 时,Core 记录诊断并不把它再当模型发起的工具。

Python 风格伪代码

这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。

def classify_response_tool(item: ResponseItem) -> LocalToolCall | HostedItem | None:
    match item:
        case FunctionCall(name, namespace, arguments, call_id):
            return LocalToolCall(qname(namespace, name), call_id,
                                 FunctionPayload(arguments))
        case CustomToolCall(name, namespace, input, call_id):
            return LocalToolCall(qname(namespace, name), call_id,
                                 CustomPayload(input))
        case ToolSearchCall(call_id=str() as cid, execution="client", arguments=args):
            return LocalToolCall("tool_search", cid,
                                 ToolSearchPayload(parse_search_args(args)))
        case ToolSearchCall() | WebSearchCall() | ImageGenerationCall():
            return HostedItem(item)
        case FunctionCallOutput() | CustomToolCallOutput() | ToolSearchOutput():
            return None  # response stream 中的意外 output,不再次执行
        case _:
            return None

失败、取消与恢复

本地工具与 Hosted Tool 的事件合同矩阵
图 5.18-2:两组都可产生 ResponseItem,但只有左侧需要客户端 Tool Result。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
client ToolSearch 缺 call_id无法配对结果不创建本地 ToolCall不适用作为非执行项处理
ToolSearch 参数解析失败尚未执行RespondToModel模型可修正记录调用与错误 output
未知本地 Custom Tool完整调用已记录Handler/Router 错误模型可见失败下一采样修正
Hosted Tool 误当本地会重复外部副作用当前类型分支阻止由服务端状态为准

设计取舍与验证

显式类型与 execution 分支防止重复执行 Hosted Tool,也允许同一种 ToolSearch 协议选择客户端或服务端执行。代价是新增工具 Item 必须同时决定 Router、TurnItem 投影、History 和事件行为,不能只加一个 enum variant 就算完成。

可验证契约证据方式预期结果
execution=client 的 ToolSearch 进入本地 RegistrySearch Tool 集成测试产生结果并触发下一采样
Hosted WebSearch 只展示/记录web_search fixture无本地 ToolOutput 调用
Custom Tool 使用 freeform inputtool harness 请求捕获Handler 收到原始字符串

Mini Codex 对照

Mini Codex 应在工具调用领域类型中加入 execution_owner: client|provider,并让 Router 只接受 client。若暂不支持 Hosted Tool,应保留其历史项但不假装本地执行。

本节边界

本节只决定“谁执行”。第六章将对每个 client-owned 工具追到验证、审批、副作用和结果编码。5.19 先把所有完成 Item 的历史提交边界闭合。

评论


← 返回文章列表