雨天小六

读懂 Codex(5.16):Function Call 参数 Diff 的累积和完成

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

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

当前 Codex 并不存在一个为所有 Function Call 拼接 JSON 参数的通用流式累加器。标准 FunctionCall 的 arguments 在 OutputItemDone 中完整交付;只有 Custom Tool Input Delta 会路由给该工具可选的 Diff Consumer。

具体问题与启用条件

本节专门划清标准 Function、Custom Tool 与 Diff Consumer 的合同。apply_patch 如何利用该扩展点在 5.17 继续。

条件来源决定字段或状态对本机制的影响
线事件response.custom_tool_call_input.delta产生 ToolCallInputDelta
活动项OutputItemAdded(CustomToolCall)按工具名创建可选 consumer
Handler实现 create_diff_consumer消费 partial input 并可发预览事件

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
标准 FunctionCall.arguments完整 ResponseItemItem Done 后JSON 字符串由 Router 后续解析
active diff consumer采样事件循环Custom Item Added 到 Done最多一个并绑定 call_id
CustomToolCall.input完整 ResponseItemItem Done 后最终执行输入,不依赖 consumer 重建

机制调用链如下:

标准 Function:OutputItemAdded → 不创建 consumer → OutputItemDone(arguments 完整字符串)
Custom Tool:OutputItemAdded → ToolName → create_diff_consumer? → input delta* → call_id 过滤 → consumer events → Done 前 finish → 完整 CustomToolCall 执行
标准 Function 和 Custom Tool 参数处理分支
图 5.16-1:只有 Custom Tool 可挂 Diff Consumer,两个分支最终都以 Done Item 为执行事实。

机制怎样工作

Wire Mapper 只识别 response.custom_tool_call_input.delta,从 item_idcall_id 建立事件身份。Core 看到 CustomToolCall Added 时,用 namespace + name 查询 Runtime Registry 的 Handler,并请求可选 ToolArgumentDiffConsumer;看到标准 FunctionCall Added 则明确把 Consumer 置空。

每个 ToolCallInputDelta 先与活动 call_id 比较:事件显式给出不同 ID 时直接跳过,没有 ID 时使用活动 ID。consumer 的 consume_diff 可返回客户端 EventMsg,但不负责构造最终工具调用。Item Done 前调用 finish(),清走 consumer;真正执行仍使用 Done Item 中完整 input/arguments。这保证预览解析失败或丢 Delta 不会改变工具执行事实。

Python 风格伪代码

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

async def on_tool_item_added(item: ResponseItem, registry: ToolRegistry):
    if isinstance(item, CustomToolCall):
        consumer = registry.create_diff_consumer(item.qualified_name)
        return ActiveDiff(call_id=item.call_id, consumer=consumer)
    if isinstance(item, FunctionCall):
        return None  # 标准函数参数等待 Done 完整交付
    return None

async def on_tool_input_delta(active: ActiveDiff | None, event: ToolInputDelta):
    if active is None:
        return
    if event.call_id is not None and event.call_id != active.call_id:
        return
    ui_event = active.consumer.consume_diff(event.delta) if active.consumer else None
    if ui_event is not None:
        await emit(ui_event)

async def on_tool_item_done(active: ActiveDiff | None, full_item: ResponseItem):
    if active and active.consumer:
        tail_event = active.consumer.finish()
        if tail_event:
            await emit(tail_event)
    await route_complete_tool_call(full_item)

失败、取消与恢复

Custom Tool 预览完成与实际调用解析的分离
图 5.16-2:consumer 丢失或拒绝半截输入不会改变 Done Item 的最终执行内容。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
Delta call_id 不匹配活动 consumer 保持忽略该 Delta不适用等待同 call_id 事件
Handler 无 consumer完整调用仍会到达无预览事件不适用Done 后正常执行
consumer partial parse 失败预览状态可能不更新通常不发事件不影响执行以完整 Done Item 为准
finish 返回 RespondToModel完整调用尚未执行工具错误路径由模型修正记录调用并反馈

设计取舍与验证

可选 consumer 避免给每种工具强加一套半成品 JSON 语义。最终执行不依赖 Delta,可抵抗丢帧和 parser 差异;代价是预览层与执行层会分别解析输入,必须接受预览可能保守或短暂落后。

可验证契约证据方式预期结果
Custom input delta 保留 call_idwire mapper 单元测试ToolCallInputDelta 字段正确
标准 Function 不建立 consumer事件循环分支审计参数只在 Done 处理
不匹配 call_id 不污染活动预览consumer 路由测试无客户端事件

Mini Codex 对照

Mini Codex 应把 ArgumentDiffConsumer 定义为可选 Protocol。标准函数先只支持 Done 时 json.loads(arguments);不要为了“看起来流式”而让执行依赖客户端拼接的半截 JSON。

本节边界

本节证明了 Diff Consumer 是 Custom Tool 的可选预览扩展,不是通用参数事实源。5.17 具体追踪 apply_patch 的 StreamingPatchParser 与 500ms 缓冲。

评论


← 返回文章列表