Reasoning 流有两条不同合同:可展示的 Summary 带 summary_index 和分节事件,原始 Reasoning Content 带 content_index。并发摘要 Feature 还会把逐 Delta 模式切换为按完整 Summary Part 交付。
具体问题与启用条件
本节研究 Reasoning 的事件路由,不讨论模型是否应显示推理内容;请求侧能力门控已在 5.4、5.6 说明。
| 条件来源 | 决定字段或状态 | 对本机制的影响 |
|---|---|---|
| 模型 | supports_reasoning_summaries | 决定请求是否要求 summary |
| Feature + Provider | ConcurrentReasoningSummaries 且 OpenAI | 使用 sequential cutoff 的 Done 模式 |
| 活动项 | Reasoning TurnItem 正在 stream | 允许发摘要/原始内容 Delta |
协议、类型与状态所有权
| 状态或协议 | 所有者 | 生命周期 | 关键不变量 |
|---|---|---|---|
| summary_index | 服务端事件 | Reasoning Item 内 | 标识摘要分节 |
| content_index | 服务端事件 | Reasoning Item 内 | 标识原始内容块 |
| ReasoningContentDelta Event | Session event stream | 暂态 UI | 摘要文本,不写 History |
| 完整 Reasoning ResponseItem | OutputItemDone | History/Rollout | 含 summary/content/encrypted_content |
机制调用链如下:
OutputItemAdded(Reasoning)
→ 普通模式:summary delta + part added
→ 或 cutoff 模式:summary done(full part)
→ reasoning_text.delta(content_index)
→ UI summary/raw events
→ OutputItemDone(full Reasoning)
→ History/Rollout
机制怎样工作
普通模式把 reasoning_summary_text.delta 映射为带 summary_index 的 ReasoningContentDelta,reasoning_summary_part.added 映射为 section break。启用 ConcurrentReasoningSummaries 且 Provider 是 OpenAI 时,Core忽略这两种事件,改等 reasoning_summary_text.done;当 summary_index>0 时先发 section break,再把完整 part 文本作为一次 Delta 发出。这样避免并发生成摘要时逐 token 截断顺序不稳定。
reasoning_text.delta 是另一条原始内容通道,映射为 ReasoningRawContentDelta 并保留 content_index。两条流都要求 active item 存在且允许 stream;最终 History 仍只记录 Done 的完整 Reasoning Item,其中 encrypted content 还可用于后续请求。UI 摘要是否出现不改变模型上下文事实。
Python 风格伪代码
这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。
async def on_reasoning_event(event: ResponseEvent, state: ReasoningState):
item = state.active_reasoning_item()
if item is None or not state.stream_to_client:
return
if isinstance(event, ReasoningSummaryDelta):
if not state.sequential_cutoff:
await emit_summary_delta(item.id, event.summary_index, event.delta)
elif isinstance(event, ReasoningSummaryPartAdded):
if not state.sequential_cutoff:
await emit_section_break(item.id, event.summary_index)
elif isinstance(event, ReasoningSummaryDone):
if state.sequential_cutoff and event.item_id == item.id:
if event.summary_index > 0:
await emit_section_break(item.id, event.summary_index)
await emit_summary_delta(item.id, event.summary_index, event.text)
elif isinstance(event, ReasoningContentDelta):
await emit_raw_reasoning_delta(item.id, event.content_index, event.delta)
失败、取消与恢复
| 故障或边界 | 已发生的状态 | 对上层的结果 | 能否直接重试 | 恢复动作 |
|---|---|---|---|---|
| Reasoning Delta 无 active item | 无法归属 Item | 内部错误/忽略特定 Done | 否 | 检查事件序列 |
| Done 的 item_id 与 active 不同 | 可能是迟到事件 | cutoff summary 被忽略 | 不适用 | 等待正确项 |
| 切换模式却双重消费 | 摘要会重复 | 实现通过互斥分支避免 | 不适用 | 只选 delta 或 done |
| 流中断 | UI 有部分摘要 | 完整 Reasoning 不提交 | 是 | 重试不带暂态片段 |
设计取舍与验证
两种摘要交付模式增加了条件分支,却允许普通模型获得低延迟、并发摘要模型获得稳定分段。保留 index 而不是只拼字符串,使客户端可以画分节和并行内容;代价是客户端也要理解 Item ID 与索引。
| 可验证契约 | 证据方式 | 预期结果 |
|---|---|---|
| 普通 summary delta 保留 index | SSE 映射测试 | 事件文本和 summary_index 相同 |
| Summary Done 携带 item_id/full text | parser 测试 | cutoff 模式可一次交付 part |
| Reasoning Item 最终进入生命周期 | items 测试 | Started/Delta/Completed 使用同一 ID |
Mini Codex 对照
Mini Codex 可以只实现 Summary Delta,但事件类型仍应保留 summary_index。若加入并发摘要,使用显式模式枚举,不能同时消费 delta 与 done。原始 content 应单独通道,不与 summary 混写。
本节边界
Reasoning 的增量协议已经拆开。5.16 转向工具参数,并纠正一个关键误区:并非所有 Function Call 都有客户端 Diff 累加器。
评论
登录后即可评论