Skip to content

[Platform][OpenAi] Route commentary phase to ThinkingDelta in streaming responses#2006

Open
Amoifr wants to merge 1 commit intosymfony:mainfrom
Amoifr:fix_ai_1979
Open

[Platform][OpenAi] Route commentary phase to ThinkingDelta in streaming responses#2006
Amoifr wants to merge 1 commit intosymfony:mainfrom
Amoifr:fix_ai_1979

Conversation

@Amoifr
Copy link
Copy Markdown
Contributor

@Amoifr Amoifr commented Apr 27, 2026

Q A
Bug fix? yes
New feature? no
Docs? no
Issues Fix #1979
License MIT

The OpenAI Responses streaming API emits both commentary and final_answer as response.output_text.delta events. The current str_contains($type, 'output_text') guard in Gpt\ResultConverter::convertStream() treats them identically, so the visible assistant text gets duplicated whenever the model emits a commentary that mirrors the final answer (the bug reported by @wimwinterberg).

Phases are now tracked per item_id via response.output_item.added, and response.output_text.delta is routed accordingly:

  • commentaryThinkingStart / ThinkingDelta, finalized by ThinkingComplete on response.output_item.done
  • final_answer / unknown phase / missing item_idTextDelta (backward compatible)

The response.reasoning_summary_text.* path now uses its own buffer ($currentReasoningSummary) to avoid colliding with the commentary buffer.

The fix follows the approach @wimwinterberg sketched in the issue thread; this PR formalizes it and adds an exhaustive test suite (9 tests) covering:

  • commentary routed to thinking deltas, never to TextDelta
  • final_answer still emits TextDelta as before
  • delta with no item_id keeps its TextDelta (backward compat)
  • multi-delta accumulation into a single ThinkingComplete
  • empty commentary item emits nothing
  • reasoning_summary regression guard
  • the real-world case (commentary + final_answer carrying the same content)
  • truly interleaved streams (both items open, deltas alternating by item_id)
  • aborted stream (response.output_item.done never arrives)

Thanks @OskarStark for the nudge on the issue.

…ng responses

The OpenAI Responses API emits both `commentary` and `final_answer` as
`response.output_text.delta` events. The previous `str_contains($type, 'output_text')`
guard treated them identically, causing the visible assistant text to be
duplicated whenever the model produced a commentary that mirrored the
final answer.

Phases are now tracked per `item_id` via `response.output_item.added`,
and `response.output_text.delta` is routed accordingly:

 * commentary → ThinkingStart / ThinkingDelta, finalized by ThinkingComplete
   on `response.output_item.done`
 * final_answer / unknown phase / missing `item_id` → TextDelta (backward
   compatible with the previous behavior)

The reasoning_summary_text.* path now uses its own buffer to avoid colliding
with commentary tracking.

Closes symfony#1979
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working Platform Issues & PRs about the AI Platform component Status: Needs Review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OpenAI Bridge] commentary phase is treated as TextDelta, causing duplicate assistant output in streaming responses

2 participants