feat(realtime): add typed create_response helper to RealtimeSession#3722
feat(realtime): add typed create_response helper to RealtimeSession#3722Skyline-9 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8eef197b9c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| response = ( | ||
| OpenAIRealtimeResponseCreateParams(**response_params) if response_params else None | ||
| ) | ||
| return OpenAIResponseCreateEvent(type="response.create", response=response) |
There was a problem hiding this comment.
When session.create_response() is called without instructions or metadata, this still passes response=None into the event constructor. Pydantic treats explicitly supplied None as a set field, so _send_raw_message(...exclude_unset=True) serializes it as "response": null rather than the minimal {"type":"response.create"} payload used by the existing raw path. For no-argument response creation, that can be rejected by the Realtime schema even though the helper is documented as equivalent to an empty response.create; construct the event without the response keyword when there are no params.
Useful? React with 👍 / 👎.
Omit the response field entirely when no per-response params are provided so the serialized payload is a bare response.create instead of {"response": null}, which the Realtime schema can reject.
|
@codex review |
8eef197 to
564ac50
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 564ac50d8a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| instructions: str | None = None | ||
| """Optional instructions that override the session instructions for this response only.""" | ||
|
|
||
| metadata: dict[str, Any] | None = None |
There was a problem hiding this comment.
Use string-valued response metadata
When a caller passes create_response(metadata={"turn": 1}) or any other non-string value, this new typed event says the value is valid and then forwards it unchanged into RealtimeResponseCreateParams; the Realtime API/OpenAI SDK schema defines response metadata as string key/value pairs, so typed callers do not get checker feedback and instead hit validation or API errors at runtime. Please match the API surface with dict[str, str] here and on the public RealtimeSession.create_response parameter.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 564ac50d8a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| instructions: str | None = None | ||
| """Optional instructions that override the session instructions for this response only.""" | ||
|
|
||
| metadata: dict[str, Any] | None = None |
There was a problem hiding this comment.
Narrow response metadata values to strings
The Realtime response-create payload this is converted into expects platform metadata as dict[str, str], but the new event type permits arbitrary values. In practice session.create_response(metadata={"turn": 1}) type-checks through this API and then fails during OpenAIRealtimeResponseCreateParams conversion before any event is sent; expose dict[str, str] here and in the session helper so invalid metadata is caught at the call site.
Useful? React with 👍 / 👎.
|
Thanks for sharing this. I traced the underlying use case through the existing raw response.create path, and that path is already public, documented, converted through the typed OpenAI realtime event adapter, and routed through the SDK response-create sequencer. It also supports per-response fields through other_data, including response.instructions and response.metadata. Because the PR does not show a concrete supported workflow that the existing path cannot handle, this looks like an ergonomics improvement rather than a demonstrated capability gap. Adding a new RealtimeSession method and a new RealtimeModelSendEvent variant creates a permanent contract for custom realtime model implementations, which is not justified by the current evidence. I am going to close this PR for now. If you can provide a specific scenario where the existing raw path is insufficient, we can revisit the narrowest design from that evidence. |
Summary
Adds a typed
RealtimeSession.create_response(*, instructions=None, metadata=None)helper so applications can request a model response with per-response overrides without hand-rolling a rawresponse.createmessage.Today the only way to trigger a response with custom
instructions/metadatais to send a raw protocol event through the low-level model escape hatch, which bypasses the SDK's response-create sequencing and is easy to get wrong (doubleresponse.create, "conversation already has an active response" errors). This adds a small, typed, well-sequenced entry point that mirrors the ergonomics of the rest of the realtime API (send_message,interrupt).What changed:
RealtimeSession.create_response(*, instructions, metadata)(keyword-only, appended afterinterrupt()so no existing API is reordered).RealtimeModelSendResponseCreatedataclass added to theRealtimeModelSendEventunion (optional fields, appended at the end of the union).OpenAIRealtimeWebSocketModel.send_eventroutes it through the existing response-create sequencer (_reserve_response_create_request(manual=True)+_start_response_create(..., manual=True)->wait_for_response_create_slot), so it cannot collide with an in-flight response and never emits a doubleresponse.create.instructions,metadata). With no arguments it serializes to a bareresponse.create(noresponseobject), viamodel_dump_json(exclude_unset=True).agents.realtime.__init__(__all__), consistent withRealtimeModelSendInterrupt; not added to the top-levelagentsnamespace.docs/realtime/guide.mddocuments thatinstructions/metadataare per-response overrides that do not mutate the agent.Test plan
tests/realtime/test_session.py,tests/realtime/test_openai_realtime.py, andtests/realtime/test_openai_realtime_conversions.py:create_responsedefers while a response is active and sends exactly oneresponse.createafter the active response completes (sequencer cooperation).response.create; only-instructionsomitsmetadata; conversion includes only provided fields.RealtimeModelSendResponseCreate.make format,make lint,make typecheck— passuv run pytest -k realtime— pass (307 passed, 1 skipped)make build-docs— passIssue number
N/A. Fits the broader direction of making the realtime API a typed first-class surface rather than requiring raw protocol messages. Happy to link a tracking issue if preferred.
Checks
.agents/skills/code-change-verification/scripts/run.shmake format,make lint,make typecheck, realtime tests, andmake build-docs)/reviewbefore submitting this PRCompatibility notes
Additive. Adding
RealtimeModelSendResponseCreateto theRealtimeModelSendEventunion means a third-partyRealtimeModel.send_eventimplementation gains a new union member it does not yet handle; the built-in OpenAI WebSocket/SIP models handle it. The dataclass fields are optional and appended at the end of the union, and the public method is keyword-only and appended after existing methods.