feat(streaming): emit ReasoningDeltaEvent for reasoning/thinking deltas (#825)#3
Open
adityasingh2400 wants to merge 230 commits into
Open
feat(streaming): emit ReasoningDeltaEvent for reasoning/thinking deltas (#825)#3adityasingh2400 wants to merge 230 commits into
adityasingh2400 wants to merge 230 commits into
Conversation
…enai#2714) Co-authored-by: Kazuhiro Sera <seratch@openai.com>
**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.
…er reasoning removal (openai#2728)
Co-authored-by: Kazuhiro Sera <seratch@openai.com>
Co-authored-by: Kazuhiro Sera <seratch@openai.com>
…esource on MCPServer (openai#2721)
…when a model is unset) (openai#3147)
…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.
3a823e4 to
5c36515
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When models like o3 or DeepSeek-R1 produce reasoning/thinking tokens during streaming, those deltas currently only surface as raw
RawResponsesStreamEventwrappers around low-levelresponse.reasoning_summary_text.deltaorresponse.reasoning_text.deltaevents. To consume them, callers have to inspect.data.typeand cast the event themselves — there's no clean signal in theStreamEventunion.This PR adds
ReasoningDeltaEventtoStreamEventand emits it alongside the existing raw event so reasoning deltas are as easy to consume as message deltas.Closes openai#825
What changed
ReasoningDeltaEventdataclass tostream_events.pywithdelta,snapshot, andtypefieldsStreamEventtype alias to includeReasoningDeltaEventagents/__init__.pyrun_internal/run_loop.py, therun_single_turn_streamedloop now emits aReasoningDeltaEventafter eachResponseReasoningSummaryTextDeltaEvent(o-series) andResponseReasoningTextDeltaEvent(DeepSeek/LiteLLM)snapshotfield accumulates the full reasoning text so far in the turn, so callers don't have to maintain their own bufferUsage example
Tests
Added
tests/test_reasoning_delta_stream_event.pycovering:ReasoningDeltaEventis emitted for reasoning itemsagentsAlso updated
tests/test_stream_events.py::test_complete_streaming_eventsto account for the new event in the event sequence (count goes from 27 → 28).Summary by CodeRabbit
New Features
Tests