Skip to content

fix(streaming): accumulate response.reasoning_text.delta in ResponseStreamState#3397

Open
yen0304 wants to merge 4 commits into
openai:mainfrom
yen0304:fix-responses-reasoning-text-delta-accumulation
Open

fix(streaming): accumulate response.reasoning_text.delta in ResponseStreamState#3397
yen0304 wants to merge 4 commits into
openai:mainfrom
yen0304:fix-responses-reasoning-text-delta-accumulation

Conversation

@yen0304

@yen0304 yen0304 commented Jun 12, 2026

Copy link
Copy Markdown

Summary

When streaming a response that includes a reasoning output item (type "reasoning"), the Python SDK was silently discarding two events:

  1. response.content_part.added — when a reasoning_text part is added to a reasoning output item, the SDK only handled output.type == "message" and fell through without appending anything for output.type == "reasoning".
  2. response.reasoning_text.delta — this event type was entirely unhandled in accumulate_event, so text deltas were never accumulated.

The result: after streaming a response with reasoning, snapshot.output[i].content remains None and no text is ever built up — even though the wire events arrived correctly.

The TypeScript SDK handles both cases correctly (see _events.ts):

  • response.content_part.added appends a ReasoningItemContent when output.type === "reasoning"
  • response.reasoning_text.delta accumulates content.text += event.delta

This PR mirrors that logic in the Python SDK.

Changes

  • src/openai/lib/streaming/responses/_responses.py: extend accumulate_event to handle both cases
  • tests/lib/responses/test_responses.py: add test_stream_state_accumulates_reasoning_text_delta which exercises the full sequence (response.createdoutput_item.addedcontent_part.added → three reasoning_text.delta events) and asserts the accumulated text

Test

def test_stream_state_accumulates_reasoning_text_delta() -> None:
    # drive ResponseStreamState through a reasoning stream sequence
    # and assert that content[0].text == "Let me think carefully."

yen0304 added 3 commits June 5, 2026 01:24
send_raw() was missing the try/except that send() has — if the
WebSocket disconnects mid-send, the data is silently lost instead of
being enqueued for retry after reconnection.
send_raw() accepts bytes | str, but the reconnect/retry path decoded
bytes to UTF-8 before enqueueing. That turned binary WebSocket frames
into text frames and raised UnicodeDecodeError for arbitrary binary
payloads (e.g. audio chunks containing 0xff) before the original
connection failure could surface.

SendQueue now stores bytes | str natively and counts byte length per
type, so send_raw() can enqueue the original payload unchanged on both
the reconnecting and send-failure paths (sync + async, realtime +
responses). Addresses Codex review feedback on openai#3363.
Add an end-to-end test that drives the real _reconnect() + flush path:
a send_raw() with non-UTF-8 bytes fails on a dropped socket, gets
queued, and is replayed byte-for-byte to the new connection after
reconnect. Verified this fails (UnicodeDecodeError) against the prior
decode-to-UTF-8 implementation.
@yen0304 yen0304 requested a review from a team as a code owner June 12, 2026 13:52
…treamState

The Python SDK was silently ignoring `response.content_part.added` events for
reasoning output items and `response.reasoning_text.delta` events, so reasoning
text was never accumulated into the snapshot. The TypeScript SDK handles both
correctly; this brings the Python SDK to parity.
@yen0304 yen0304 force-pushed the fix-responses-reasoning-text-delta-accumulation branch from d10e407 to 4d4b0e0 Compare June 12, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant