OpenAI Responses API — SSE Streaming Internals
Source: OpenAI Python SDK openai-python (Stainless-generated)
Scope: SSE (Server-Sent Events) only. WebSocket mode excluded.
Last updated: 2026-06-20
1. High-Level Architecture
flowchart TB subgraph User["User Code"] A["client.responses.create(stream=True)"] end subgraph HTTP["HTTP POST /v1/responses"] B["POST /v1/responses<br/>headers: text/event-stream<br/>body: { stream: true }"] end subgraph SSE["SSE Decoding"] C["SSEDecoder.decode(line)"] D["ServerSentEvent"] E["construct_type(discriminator=type)"] end subgraph SDK["SDK Layer"] F["ResponseStreamState<br/>(accumulate + handle)"] G["ResponseStreamEvent[]"] end A --> B B -->|chunked transfer| C C -->|line by line| D D -->|JSON dict| E E -->|typed event| F F -->|yields| Gor via the convenience API:
with client.responses.stream(input="...", model="gpt-4o") as stream: for event in stream: ...which uses ResponseStreamManager → ResponseStream internally.
2. SSE Wire Format
Unlike the Assistants API (which uses named SSE event: fields like thread.message.delta), the Responses API embeds the event type directly in the JSON payload as a type field.
Each SSE chunk looks like:
data: {"type":"response.created","response":{...},"sequence_number":0}
data: {"type":"response.in_progress","response":{...},"sequence_number":1}
data: {"type":"response.output_item.added","output_index":0,"item":{...},"sequence_number":2}
data: {"type":"response.content_part.added","output_index":0,"item_id":"...","content_index":0,"part":{...},"sequence_number":3}
data: {"type":"response.output_text.delta","output_index":0,"item_id":"...","content_index":0,"delta":"Hello","sequence_number":4}...- event: line is not set for Responses API SSE events.
- The type field inside the JSON body is the discriminator.
- Every event has a sequence_number: int for ordering.
- The stream ends when the connection closes (no [DONE] sentinel for Responses; the [DONE] check in Stream._iter_events is only for legacy Chat Completions).
3. Full Event Catalog (53 types)
3.1 Response Lifecycle Events
| # | type literal | Class | Fields |
|---|---|---|---|
| 1 | response.created | ResponseCreatedEvent | response: Response, sequence_number: int |
| 2 | response.queued | ResponseQueuedEvent | response: Response, sequence_number: int |
| 3 | response.in_progress | ResponseInProgressEvent | response: Response, sequence_number: int |
| 4 | response.completed | ResponseCompletedEvent | response: Response, sequence_number: int |
| 5 | response.failed | ResponseFailedEvent | response: Response, sequence_number: int |
| 6 | response.incomplete | ResponseIncompleteEvent | response: Response, sequence_number: int |
| 7 | error | ResponseErrorEvent | code: Optional[str], message: str, param: Optional[str], sequence_number: int |
3.2 Output Items — Structural
| # | type literal | Class | Fields |
|---|---|---|---|
| 8 | response.output_item.added | ResponseOutputItemAddedEvent | item: ResponseOutputItem, output_index: int, sequence_number: int |
| 9 | response.output_item.done | ResponseOutputItemDoneEvent | item: ResponseOutputItem, output_index: int, sequence_number: int |
| 10 | response.content_part.added | ResponseContentPartAddedEvent | part: Part, content_index: int, item_id: str, output_index: int, sequence_number: int |
| 11 | response.content_part.done | ResponseContentPartDoneEvent | part: Part, content_index: int, item_id: str, output_index: int, sequence_number: int |
Part is a discriminated union of:
- ResponseOutputText (type=output_text)
- ResponseOutputRefusal (type=refusal)
- PartReasoningText (type=reasoning_text)
3.3 Text Content
| # | type literal | Class | Fields |
|---|---|---|---|
| 12 | response.output_text.delta | ResponseTextDeltaEvent | delta: str, content_index: int, item_id: str, output_index: int, logprobs: List[Logprob], sequence_number: int |
| 13 | response.output_text.done | ResponseTextDoneEvent | text: str, content_index: int, item_id: str, output_index: int, logprobs: List[Logprob], sequence_number: int |
| 14 | response.output_text.annotation.added | ResponseOutputTextAnnotationAddedEvent | annotation: object, annotation_index: int, content_index: int, item_id: str, output_index: int, sequence_number: int |
logprob structure:
Logprob: token: str logprob: float top_logprobs: Optional[List[LogprobTopLogprob]] # up to 20 alternatives
LogprobTopLogprob: token: Optional[str] logprob: Optional[float]3.4 Refusal
| # | type literal | Class | Fields |
|---|---|---|---|
| 15 | response.refusal.delta | ResponseRefusalDeltaEvent | delta: str, content_index: int, item_id: str, output_index: int, sequence_number: int |
| 16 | response.refusal.done | ResponseRefusalDoneEvent | refusal: str, content_index: int, item_id: str, output_index: int, sequence_number: int |
3.5 Function Calling
| # | type literal | Class | Fields |
|---|---|---|---|
| 17 | response.function_call_arguments.delta | ResponseFunctionCallArgumentsDeltaEvent | delta: str, item_id: str, output_index: int, sequence_number: int |
| 18 | response.function_call_arguments.done | ResponseFunctionCallArgumentsDoneEvent | arguments: str, name: str, item_id: str, output_index: int, sequence_number: int |
3.6 MCP (Model Context Protocol)
| # | type literal | Class | Fields |
|---|---|---|---|
| 19 | response.mcp_call.in_progress | ResponseMcpCallInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 20 | response.mcp_call_arguments.delta | ResponseMcpCallArgumentsDeltaEvent | delta: str (JSON partial), item_id: str, output_index: int, sequence_number: int |
| 21 | response.mcp_call_arguments.done | ResponseMcpCallArgumentsDoneEvent | arguments: str (JSON full), item_id: str, output_index: int, sequence_number: int |
| 22 | response.mcp_call.completed | ResponseMcpCallCompletedEvent | item_id: str, output_index: int, sequence_number: int |
| 23 | response.mcp_call.failed | ResponseMcpCallFailedEvent | item_id: str, output_index: int, sequence_number: int |
| 24 | response.mcp_list_tools.in_progress | ResponseMcpListToolsInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 25 | response.mcp_list_tools.completed | ResponseMcpListToolsCompletedEvent | item_id: str, output_index: int, sequence_number: int |
| 26 | response.mcp_list_tools.failed | ResponseMcpListToolsFailedEvent | item_id: str, output_index: int, sequence_number: int |
3.7 Audio
| # | type literal | Class | Fields |
|---|---|---|---|
| 27 | response.audio.delta | ResponseAudioDeltaEvent | delta: str (Base64 audio bytes), sequence_number: int |
| 28 | response.audio.done | ResponseAudioDoneEvent | sequence_number: int |
| 29 | response.audio.transcript.delta | ResponseAudioTranscriptDeltaEvent | delta: str, sequence_number: int |
| 30 | response.audio.transcript.done | ResponseAudioTranscriptDoneEvent | sequence_number: int |
3.8 Web Search Tool
| # | type literal | Class | Fields |
|---|---|---|---|
| 31 | response.web_search_call.in_progress | ResponseWebSearchCallInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 32 | response.web_search_call.searching | ResponseWebSearchCallSearchingEvent | item_id: str, output_index: int, sequence_number: int |
| 33 | response.web_search_call.completed | ResponseWebSearchCallCompletedEvent | item_id: str, output_index: int, sequence_number: int |
3.9 File Search Tool
| # | type literal | Class | Fields |
|---|---|---|---|
| 34 | response.file_search_call.in_progress | ResponseFileSearchCallInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 35 | response.file_search_call.searching | ResponseFileSearchCallSearchingEvent | item_id: str, output_index: int, sequence_number: int |
| 36 | response.file_search_call.completed | ResponseFileSearchCallCompletedEvent | item_id: str, output_index: int, sequence_number: int |
3.10 Code Interpreter Tool
| # | type literal | Class | Fields |
|---|---|---|---|
| 37 | response.code_interpreter_call.in_progress | ResponseCodeInterpreterCallInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 38 | response.code_interpreter_call.interpreting | ResponseCodeInterpreterCallInterpretingEvent | item_id: str, output_index: int, sequence_number: int |
| 39 | response.code_interpreter_call_code.delta | ResponseCodeInterpreterCallCodeDeltaEvent | delta: str, item_id: str, output_index: int, sequence_number: int |
| 40 | response.code_interpreter_call_code.done | ResponseCodeInterpreterCallCodeDoneEvent | code: str, item_id: str, output_index: int, sequence_number: int |
| 41 | response.code_interpreter_call.completed | ResponseCodeInterpreterCallCompletedEvent | item_id: str, output_index: int, sequence_number: int |
3.11 Reasoning
| # | type literal | Class | Fields |
|---|---|---|---|
| 42 | response.reasoning_text.delta | ResponseReasoningTextDeltaEvent | delta: str, content_index: int, item_id: str, output_index: int, sequence_number: int |
| 43 | response.reasoning_text.done | ResponseReasoningTextDoneEvent | text: str, content_index: int, item_id: str, output_index: int, sequence_number: int |
| 44 | response.reasoning_summary_part.added | ResponseReasoningSummaryPartAddedEvent | part: Part (summary_text), summary_index: int, item_id: str, output_index: int, sequence_number: int |
| 45 | response.reasoning_summary_part.done | ResponseReasoningSummaryPartDoneEvent | part: Part (summary_text), summary_index: int, item_id: str, output_index: int, sequence_number: int |
| 46 | response.reasoning_summary_text.delta | ResponseReasoningSummaryTextDeltaEvent | delta: str, summary_index: int, item_id: str, output_index: int, sequence_number: int |
| 47 | response.reasoning_summary_text.done | ResponseReasoningSummaryTextDoneEvent | text: str, summary_index: int, item_id: str, output_index: int, sequence_number: int |
3.12 Image Generation
| # | type literal | Class | Fields |
|---|---|---|---|
| 48 | response.image_generation_call.in_progress | ResponseImageGenCallInProgressEvent | item_id: str, output_index: int, sequence_number: int |
| 49 | response.image_generation_call.generating | ResponseImageGenCallGeneratingEvent | item_id: str, output_index: int, sequence_number: int |
| 50 | response.image_generation_call.partial_image | ResponseImageGenCallPartialImageEvent | partial_image_b64: str, partial_image_index: int (0-based), item_id: str, output_index: int, sequence_number: int |
| 51 | response.image_generation_call.completed | ResponseImageGenCallCompletedEvent | item_id: str, output_index: int, sequence_number: int |
3.13 Custom Tool Call
| # | type literal | Class | Fields |
|---|---|---|---|
| 52 | response.custom_tool_call_input.delta | ResponseCustomToolCallInputDeltaEvent | delta: str, item_id: str, output_index: int, sequence_number: int |
| 53 | response.custom_tool_call_input.done | ResponseCustomToolCallInputDoneEvent | input: str, item_id: str, output_index: int, sequence_number: int |
4. SSE Parsing Pipeline (Wire to Typed Event)
flowchart TB subgraph HTTP["HTTP Response Stream"] A["httpx.Response.iter_bytes()"] end subgraph SSE["SSE Decoder (WHATWG SSE)"] B["SSEDecoder.decode(line)"] C["ServerSentEvent<br/>.event - None for Responses<br/>.data - raw JSON string"] end subgraph DISPATCH["Stream Dispatcher"] D{"sse.data starts with [DONE]?"} E{"sse.event starts with thread.?"} F["construct_type(discriminator=type)"] end subgraph OUT["Typed Event"] G["ResponseCreatedEvent |<br/>ResponseTextDeltaEvent |<br/>... (52 more types)"] end A --> B B --> C C --> D D -->|yes| H["break (legacy)"] D -->|no| E E -->|yes| I["thread. events (Assistants)"] E -->|no| F F --> GKey decoder details
- SSEDecoder accumulates lines until a blank line (\n\n, \r\r, or \r\n\r\n).
- Each SSE field (event:, data:, id:, retry:) is processed per spec.
- For Responses, event: is absent →
ServerSentEvent.event is None. - The raw JSON data: is deserialized and passed to
construct_type(). ResponseStreamEventis Annotated[Union[…], PropertyInfo(discriminator=“type”)], soconstruct_typeexamines the “type” key to dispatch to the correct subclass.
5. SDK Convenience API: ResponseStream / ResponseStreamState
5.1 ResponseStreamManager (entry point)
Usage:
with client.responses.stream( input="Hello", model="gpt-4o") as stream: # returns ResponseStream[TextFormatT] for event in stream: ...The manager:
- Calls client.responses.create(stream=True, …) to get the raw Stream[
ResponseStreamEvent]. - Wraps it in
ResponseStream(raw_stream=…, text_format=…, input_tools=…). - Exposes the stream as an iterator of
ResponseStreamEvent[TextFormatT].
5.2 ResponseStream → ResponseStreamState
ResponseStream.__stream__() delegates to ResponseStreamState.handle_event(sse_event) for each raw event.
handle_event() produces 1 or 2 events per raw SSE event:
| Raw SSE type | Emitted events |
|---|---|
| response.output_text.delta | ResponseTextDeltaEvent (with snapshot field added — accumulated text so far) |
| response.output_text.done | ResponseTextDoneEvent (with parsed field if text_format is set) |
| response.function_call_arguments.delta | ResponseFunctionCallArgumentsDeltaEvent (with snapshot — accumulated args so far) |
| response.completed | ResponseCompletedEvent (with response being the full parsed ParsedResponse) |
| All others | Passed through as-is |
5.3 accumulate_event() — State Machine
ResponseStreamState maintains a __current_snapshot (partial ParsedResponse):
- response.created → Creates initial snapshot from
event.response. - response.output_item.added → Appends item to
snapshot.output[](handles function_call, message, and generic types). - response.content_part.added → Appends content part to the indexed output message.
- response.output_text.delta → Concatenates delta to the matching output_text content’s text.
- response.function_call_arguments.delta → Concatenates delta to the matching function call’s arguments.
- response.completed → Stores the fully parsed response via
parse_response() (also handles structured output parsing).
5.4 Convenience methods
- stream.
get_final_response() → Consumes entire stream, returns the accumulatedParsedResponse. - stream.
until_done() → Blocks until stream fully consumed. - stream.close() → Closes underlying httpx response.
5.5 ResponseStreamEvent[TextFormatT] (SDK wrapper types)
These subclass or wrap the raw generated types with extra fields:
| Raw type | SDK wrapper | Extra fields |
|---|---|---|
ResponseTextDeltaEvent | lib.streaming.responses.ResponseTextDeltaEvent | snapshot: str (accumulated text) |
ResponseTextDoneEvent | lib.streaming.responses.ResponseTextDoneEvent | parsed: Optional[TextFormatT] |
ResponseFunctionCallArgumentsDeltaEvent | lib.streaming.responses.ResponseFunctionCallArgumentsDeltaEvent | snapshot: str (accumulated args) |
ResponseCompletedEvent | lib.streaming.responses.ResponseCompletedEvent | response: ParsedResponse[TextFormatT] |
The composite ResponseStreamEvent type alias used by the stream is:
ResponseStreamEvent = Annotated[Union[ ResponseTextDeltaEvent, # SDK wrapper ResponseTextDoneEvent, # SDK wrapper ResponseFunctionCallArgumentsDeltaEvent, # SDK wrapper ResponseCompletedEvent, # SDK wrapper ResponseAudioDeltaEvent, # raw ... (all other raw events) # raw], PropertyInfo(discriminator="type")]6. Background Mode Streaming
When background=True is passed to responses.stream():
- The API request includes “background”: true.
- The server immediately returns a
response.createdevent with the response id. - The stream stays open but yields events asynchronously as the background processing progresses.
- The stream can be interrupted and resumed: note the response_id from response.created, then later call responses.stream(response_id=id, starting_after=N) to resume from sequence number N.
Usage pattern:
with client.responses.stream(input="...", model="gpt-4o", background=True) as stream: for event in stream: if event.type == "response.created": id = event.response.id if event.sequence_number == 10: break
# Later...with client.responses.stream(response_id=id, starting_after=10) as stream: for event in stream: ... response = stream.get_final_response()7. Data Flow Summary
flowchart TB subgraph CLIENT["Client"] A["HTTP POST /v1/responses<br/>{ stream: true }"] end subgraph PROXY["HTTP Transport"] B["Content-Type: text/event-stream<br/>chunked transfer"] end subgraph PARSE["SSE Parser"] C["SSEDecoder<br/>- buffers \n\n chunks<br/>- parses event:/data:"] end subgraph DISP["Type Dispatch"] D["construct_type()<br/>via type discriminator"] end subgraph ACC["Accumulator"] E["ResponseStreamState<br/>- maintains snapshot<br/>- adds snapshot/parsed"] end subgraph USER["User"] F["for event in stream:"] end A --> B B --> C C --> D D --> E E --> F8. Wire-Level Example (Synthetic)
data: {"type":"response.created","response":{"id":"resp_abc123","object":"response","status":"queued","created_at":1718000000,"model":"gpt-4o","output":[],"tools":[],"tool_choice":"auto","parallel_tool_calls":true,"top_p":1,"temperature":1},"sequence_number":0}
data: {"type":"response.queued","response":{"id":"resp_abc123",...},"sequence_number":1}
data: {"type":"response.in_progress","response":{"id":"resp_abc123","status":"in_progress",...},"sequence_number":2}
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"item_1","type":"message","role":"assistant","content":[],"status":"in_progress"},"sequence_number":3}
data: {"type":"response.content_part.added","output_index":0,"item_id":"item_1","content_index":0,"part":{"type":"output_text","text":"","annotations":[]},"sequence_number":4}
data: {"type":"response.output_text.delta","output_index":0,"item_id":"item_1","content_index":0,"delta":"Hello","sequence_number":5,"logprobs":[{"token":"Hello","logprob":-0.01}]}
data: {"type":"response.output_text.delta","output_index":0,"item_id":"item_1","content_index":0,"delta":" world","sequence_number":6,"logprobs":[...]}
data: {"type":"response.output_text.done","output_index":0,"item_id":"item_1","content_index":0,"text":"Hello world","sequence_number":7,"logprobs":[...]}
data: {"type":"response.content_part.done","output_index":0,"item_id":"item_1","content_index":0,"part":{"type":"output_text","text":"Hello world","annotations":[]},"sequence_number":8}
data: {"type":"response.output_item.done","output_index":0,"item":{"id":"item_1","type":"message","role":"assistant","content":[{"type":"output_text","text":"Hello world","annotations":[]}],"status":"completed"},"sequence_number":9}
data: {"type":"response.completed","response":{"id":"resp_abc123","status":"completed","output":[{"id":"item_1",...}],...},"sequence_number":10}9. Implementation Reference (Key Files in SDK)
| File | Purpose |
|---|---|
| src/openai/_streaming.py | Stream[T], AsyncStream[T], SSEDecoder, SSEBytesDecoder, ServerSentEvent |
| src/openai/_response.py | APIResponse, _parse() → constructs Stream[T] |
| src/openai/_base_client.py | request(), _process_response(), _process_response_data(), _make_sse_decoder(), make_request_options() |
| src/openai/types/responses/response_stream_event.py | ResponseStreamEvent discriminated union |
| src/openai/lib/streaming/responses/_responses.py | ResponseStream, AsyncResponseStream, ResponseStreamManager, ResponseStreamState |
| src/openai/lib/streaming/responses/_events.py | SDK-enhanced wrapper types + ResponseStreamEvent (lib version) |
| src/openai/resources/responses/responses.py | Responses.create(), Responses.stream(), WebSocket connection code |
10. Event Taxonomy Diagram
graph LR subgraph LIFECYCLE["Response Lifecycle"] RC["response.created"] --> RQ["response.queued"] RQ --> RI["response.in_progress"] RI --> RC2["response.completed"] RI --> RF["response.failed"] RI --> RINC["response.incomplete"] end subgraph CONTENT["Content Delta"] OT["output_text.delta"] --> OTD["output_text.done"] OTD --> OTA["output_text.annotation"] RD["refusal.delta"] --> RD2["refusal.done"] AD["audio.delta"] --> ADD["audio.done"] ATD["audio.transcript.delta"] --> ATDD["audio.transcript.done"] RT["reasoning_text.delta"] --> RTD["reasoning_text.done"] RS["reasoning_summary_part<br/>reasoning_summary_text"] end subgraph STRUCT["Output Structure"] OIA["output_item.added"] --> OID["output_item.done"] CPA["content_part.added"] --> CPD["content_part.done"] end subgraph IMAGE["Image Generation"] IGI["image_gen_call.in_progress"] --> IGG["image_gen_call.generating"] IGG --> IGP["image_gen_call.partial_image"] IGP --> IGC["image_gen_call.completed"] end subgraph TOOLS["Tool Calls"] FC["function_call_arguments.delta/done"] MCP["mcp_call.* (8 events)"] WS["web_search_call.* (3 events)"] FS["file_search_call.* (3 events)"] CI["code_interpreter_call.* (5 events)"] CT["custom_tool_call_input.delta/done"] end LIFECYCLE --> CONTENT LIFECYCLE --> STRUCT LIFECYCLE --> TOOLS LIFECYCLE --> IMAGEPro tip: The SDK docstrings only document response.created. Use the type definitions from src/openai/types/responses/response_
_event.py as the canonical source. Every event has the type literal (matching the type field in the JSON), sequence_number: int, and event-specific fields.