ResponseItem 不是“模型输出的一段文本”。它是模型响应中的类型化 item;完成事件到达后,Runtime 先尝试把它解析成 ToolCall,解析结果有三条路:执行工具、按普通 item 完成、或把可恢复错误回送给模型。致命路由错误才终止采样。
Added 与 Done 承担不同工作
OutputItemAdded 主要建立流式 UI 状态:补齐缺失 item id,为 custom tool 参数建立 diff consumer,把非工具 item 投影成 TurnItem started。文本 delta 随后更新 active item。
OutputItemDone 才拥有完整 item。它完成参数 diff、处理 plan-mode 特殊投影,然后调用 handle_output_item_done。因此不能在 Added 时就执行工具;参数可能尚未完整,item id 也可能需从 active projection 回填。
async def handle_output_item_done(item):
parsed = ToolRouter.build_tool_call(item)
match parsed:
case ToolCall(call):
await record_response_item_immediately(item)
future = tool_runtime.handle_tool_call(call, cancel.child())
return OutputItemResult(needs_follow_up=True, tool_future=future)
case NotATool():
turn_item, facts = await finalize_non_tool_item(item)
await emit_started_if_needed(turn_item)
await emit_completed(turn_item)
await record_with_finalized_facts(item, facts)
return OutputItemResult(last_agent_message=facts.last_message)
case RespondToModel(message):
await record(item, synthetic_function_output(message))
return OutputItemResult(needs_follow_up=True)
case Fatal(message):
raise CodexFatal(message)
工具 future 为什么先排队、后按序收集
一次 response 可包含多个 tool item。每个完成 item 生成 future 并推入 FuturesOrdered。底层工具可以并发推进,但结果按加入顺序交回历史,保持模型 call/output 序列稳定。每个 future 使用当前 StepContext 的 ToolCallRuntime 和 child cancellation token。
工具 call item 会立即写历史和 Rollout,即使稍后 Turn 被取消也不会失去“模型曾请求这个调用”的事实。取消后的 output 则由工具层生成配对的 aborted result或由历史规范化处理,避免留下无法解释的孤儿 call。
普通消息怎样决定 last_agent_message
普通 Message/AgentMessage/Reasoning 等先经 contributor 完成投影,再写历史。只有非空 AgentMessage 文本成为 last_agent_message。最终回答 phase 还可能把非 trigger mailbox 推迟到下一 Turn;commentary 不做这个推迟,因为它本来就是中间进度。
Response completed 才结束当前 stream。即使已经收到 assistant item,若没有 completed,连接关闭会被视为 stream closed before response.completed,进入 retry/error,而不是擅自把半截输出当成功。
可恢复与致命路由错误
模型调用了不可用或被拒绝但可以解释的工具时,RespondToModel 写一条 synthetic FunctionCallOutput,让模型看到错误并自行修正。只有破坏 Runtime 不变量的 Fatal 才直接返回 CodexErr::Fatal。这个边界让“工具用错”成为 Agent 可恢复问题,而不是一律杀死 Thread。
评论
登录后即可评论