Skip to content

feat(streaming): emit ReasoningDeltaEvent for reasoning/thinking deltas (#825)#3

Open
adityasingh2400 wants to merge 230 commits into
mainfrom
feat/reasoning-delta-stream-event-825
Open

feat(streaming): emit ReasoningDeltaEvent for reasoning/thinking deltas (#825)#3
adityasingh2400 wants to merge 230 commits into
mainfrom
feat/reasoning-delta-stream-event-825

Conversation

@adityasingh2400
Copy link
Copy Markdown
Owner

@adityasingh2400 adityasingh2400 commented Apr 14, 2026

Summary

When models like o3 or DeepSeek-R1 produce reasoning/thinking tokens during streaming, those deltas currently only surface as raw RawResponsesStreamEvent wrappers around low-level response.reasoning_summary_text.delta or response.reasoning_text.delta events. To consume them, callers have to inspect .data.type and cast the event themselves — there's no clean signal in the StreamEvent union.

This PR adds ReasoningDeltaEvent to StreamEvent and emits it alongside the existing raw event so reasoning deltas are as easy to consume as message deltas.

Closes openai#825

What changed

  • Added ReasoningDeltaEvent dataclass to stream_events.py with delta, snapshot, and type fields
  • Updated StreamEvent type alias to include ReasoningDeltaEvent
  • Exported from agents/__init__.py
  • In run_internal/run_loop.py, the run_single_turn_streamed loop now emits a ReasoningDeltaEvent after each ResponseReasoningSummaryTextDeltaEvent (o-series) and ResponseReasoningTextDeltaEvent (DeepSeek/LiteLLM)
  • The snapshot field accumulates the full reasoning text so far in the turn, so callers don't have to maintain their own buffer
  • Raw events are still emitted unchanged — fully backwards compatible

Usage example

from agents import Agent, Runner
from agents.stream_events import ReasoningDeltaEvent

agent = Agent(name="thinker", model="o3-mini")
result = Runner.run_streamed(agent, "prove P != NP")

async for event in result.stream_events():
    if isinstance(event, ReasoningDeltaEvent):
        print(event.delta, end="", flush=True)

print()  # reasoning complete

Tests

Added tests/test_reasoning_delta_stream_event.py covering:

  • ReasoningDeltaEvent is emitted for reasoning items
  • Snapshot grows monotonically and ends with full text
  • No event emitted for plain text responses
  • Raw events still emitted alongside
  • Importable directly from agents
  • Correct dataclass fields

Also updated tests/test_stream_events.py::test_complete_streaming_events to account for the new event in the event sequence (count goes from 27 → 28).

Summary by CodeRabbit

  • New Features

    • Streamed reasoning deltas are now emitted as discrete events carrying incremental delta text and an accumulated snapshot.
  • Tests

    • Added and updated tests to verify reasoning-delta emission, snapshot accumulation across deltas, event ordering adjustments, and continued emission of existing stream event types.

elainegan-openai and others added 30 commits March 19, 2026 14:30
**Default guidance in public documentation**
https://openai.github.io/openai-agents-python/

**Before**
The public Agents SDK docs still mostly pointed realtime users at
gpt-realtime, so new users were landing on stale guidance instead of the
recommended gpt-realtime-1.5 path. This showed up in the top-level docs
entry points and the English realtime quickstart/guide.

**After**
The public documentation now points new realtime users to
gpt-realtime-1.5 in the top-level discovery pages and the English
realtime quickstart/guide. The SDK’s implicit runtime default was
intentionally left unchanged, so this updates guidance without changing
behavior for existing integrations that omit model_name.
Co-authored-by: Kazuhiro Sera <seratch@openai.com>
Co-authored-by: Kazuhiro Sera <seratch@openai.com>
seratch and others added 29 commits May 6, 2026 21:14
…enai#3176)

Co-authored-by: Aphroq <37263590+Aphroq@users.noreply.github.com>
### Summary

Fix a false duplicate-argument error when a Responses request parameter
is supplied through `ModelSettings.extra_args` and the first-class
request field is only present as OpenAI's `omit` sentinel.

### Test plan

- `make format`
- `make lint`
- `uv run pytest
tests/models/test_openai_responses.py::test_build_response_create_kwargs_allows_extra_arg_when_explicit_arg_is_omitted
tests/models/test_openai_responses.py::test_build_response_create_kwargs_rejects_duplicate_context_management_extra_args
tests/models/test_openai_responses.py::test_build_response_create_kwargs_rejects_duplicate_extra_args_keys
-q`
- `uv run mypy src/agents/models/openai_responses.py
tests/models/test_openai_responses.py`
- `uv run pyright src/agents/models/openai_responses.py
tests/models/test_openai_responses.py`
- `git diff --check`

### Issue number

N/A

### Checks

- [x] I've added new tests (if relevant)
- [ ] I've added/updated the relevant documentation
- [x] I've run `make lint` and `make format`
- [x] I've made sure tests pass where not blocked by unrelated
local-only failures
…as (openai#825)

Add a new ReasoningDeltaEvent to StreamEvent so callers can react to
reasoning/thinking tokens in real time without unpacking low-level raw
response events.

The event is emitted whenever a ResponseReasoningSummaryTextDeltaEvent
(o-series extended thinking via the Responses API) or a
ResponseReasoningTextDeltaEvent (third-party models like DeepSeek-R1
via LiteLLM) passes through the stream.  The underlying
RawResponsesStreamEvent is still emitted as well, so nothing breaks for
consumers that already inspect raw events.

Fields:
  delta    - the incremental text fragment from this chunk
  snapshot - full accumulated reasoning text so far in this turn
  type     - always 'reasoning_delta'

Closes openai#825
- Import ResponseCreatedEvent and reset _reasoning_snapshot to "" when
  a ResponseCreatedEvent is received inside the retry stream loop, fixing
  the bug where snapshot text would be duplicated across retries
- In test_reasoning_delta_event_type_field: add found=False flag and
  assert found after the loop so the test properly fails when no
  ReasoningDeltaEvent is emitted
The two stream-event tests were only asserting on data conditional on a
ReasoningDeltaEvent being emitted at all, so a regression that stopped
emitting the event entirely would have passed silently.

  * test_reasoning_delta_snapshot_accumulates: assert that snapshots is
    non-empty before checking monotonic length and the "Hello world"
    inclusion (previously gated on `if snapshots:`).
  * test_no_reasoning_delta_event_without_reasoning: count yielded events
    and assert the stream produced at least one, so the negative
    not-isinstance assertion can't pass on an empty event stream.

Picked up the remaining nitpicks from the CodeRabbit review of PR #3.
@adityasingh2400 adityasingh2400 force-pushed the feat/reasoning-delta-stream-event-825 branch from 3a823e4 to 5c36515 Compare May 8, 2026 00:06
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.

Yield reasoning delta in the response or add hooks to handle