Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9d32577

Browse files
authored
[4/n] Transport interface + events (openai#1071)
Transport interface for actually connecting to the backend and managing realtime conversations. The transport emits events. --- [//]: # (BEGIN SAPLING FOOTER) * openai#1074 * openai#1073 * openai#1072 * __->__ openai#1071
1 parent 78675ff commit 9d32577

File tree

4 files changed

+270
-1
lines changed

4 files changed

+270
-1
lines changed

src/agents/realtime/items.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class RealtimeToolCallItem(BaseModel):
9292
model_config = ConfigDict(extra="allow")
9393

9494

95-
RealtimeItem = RealtimeMessageItem | RealtimeToolCallItem
95+
RealtimeItem = Union[RealtimeMessageItem, RealtimeToolCallItem]
9696

9797

9898
class RealtimeResponse(BaseModel):

src/agents/realtime/transport.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import abc
2+
from typing import Any, Literal, Union
3+
4+
from typing_extensions import NotRequired, TypeAlias, TypedDict
5+
6+
from .config import APIKeyOrKeyFunc, RealtimeClientMessage, RealtimeSessionConfig, RealtimeUserInput
7+
from .transport_events import RealtimeTransportEvent, RealtimeTransportToolCallEvent
8+
9+
RealtimeModelName: TypeAlias = Union[
10+
Literal[
11+
"gpt-4o-realtime-preview",
12+
"gpt-4o-mini-realtime-preview",
13+
"gpt-4o-realtime-preview-2025-06-03",
14+
"gpt-4o-realtime-preview-2024-12-17",
15+
"gpt-4o-realtime-preview-2024-10-01",
16+
"gpt-4o-mini-realtime-preview-2024-12-17",
17+
],
18+
str,
19+
]
20+
"""The name of a realtime model."""
21+
22+
23+
class RealtimeTransportListener(abc.ABC):
24+
"""A listener for realtime transport events."""
25+
26+
@abc.abstractmethod
27+
async def on_event(self, event: RealtimeTransportEvent) -> None:
28+
"""Called when an event is emitted by the realtime transport."""
29+
pass
30+
31+
32+
class RealtimeTransportConnectionOptions(TypedDict):
33+
"""Options for connecting to a realtime transport."""
34+
35+
api_key: NotRequired[APIKeyOrKeyFunc]
36+
"""The API key to use for the transport. If unset, the transport will attempt to use the
37+
`OPENAI_API_KEY` environment variable.
38+
"""
39+
40+
model: NotRequired[str]
41+
"""The model to use."""
42+
43+
url: NotRequired[str]
44+
"""The URL to use for the transport. If unset, the transport will use the default OpenAI
45+
WebSocket URL.
46+
"""
47+
48+
initial_session_config: NotRequired[RealtimeSessionConfig]
49+
50+
51+
class RealtimeSessionTransport(abc.ABC):
52+
"""A transport layer for realtime sessions."""
53+
54+
@abc.abstractmethod
55+
async def connect(self, options: RealtimeTransportConnectionOptions) -> None:
56+
"""Establish a connection to the model and keep it alive."""
57+
pass
58+
59+
@abc.abstractmethod
60+
def add_listener(self, listener: RealtimeTransportListener) -> None:
61+
"""Add a listener to the transport."""
62+
pass
63+
64+
@abc.abstractmethod
65+
async def remove_listener(self, listener: RealtimeTransportListener) -> None:
66+
"""Remove a listener from the transport."""
67+
pass
68+
69+
@abc.abstractmethod
70+
async def send_event(self, event: RealtimeClientMessage) -> None:
71+
"""Send an event to the model."""
72+
pass
73+
74+
@abc.abstractmethod
75+
async def send_message(
76+
self, message: RealtimeUserInput, other_event_data: dict[str, Any] | None = None
77+
) -> None:
78+
"""Send a message to the model."""
79+
pass
80+
81+
@abc.abstractmethod
82+
async def send_audio(self, audio: bytes, *, commit: bool = False) -> None:
83+
"""Send a raw audio chunk to the model.
84+
85+
Args:
86+
audio: The audio data to send.
87+
commit: Whether to commit the audio buffer to the model. If the model does not do turn
88+
detection, this can be used to indicate the turn is completed.
89+
"""
90+
pass
91+
92+
@abc.abstractmethod
93+
async def send_tool_output(
94+
self, tool_call: RealtimeTransportToolCallEvent, output: str, start_response: bool
95+
) -> None:
96+
"""Send tool output to the model."""
97+
pass
98+
99+
@abc.abstractmethod
100+
async def interrupt(self) -> None:
101+
"""Interrupt the model. For example, could be triggered by a guardrail."""
102+
pass
103+
104+
@abc.abstractmethod
105+
async def close(self) -> None:
106+
"""Close the session."""
107+
pass
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import Any, Literal, Union
5+
6+
from typing_extensions import TypeAlias
7+
8+
from .items import RealtimeItem
9+
10+
RealtimeConnectionStatus: TypeAlias = Literal["connecting", "connected", "disconnected"]
11+
12+
13+
@dataclass
14+
class RealtimeTransportErrorEvent:
15+
"""Represents a transport‑layer error."""
16+
17+
error: Any
18+
19+
type: Literal["error"] = "error"
20+
21+
22+
@dataclass
23+
class RealtimeTransportToolCallEvent:
24+
"""Model attempted a tool/function call."""
25+
26+
name: str
27+
call_id: str
28+
arguments: str
29+
30+
id: str | None = None
31+
previous_item_id: str | None = None
32+
33+
type: Literal["function_call"] = "function_call"
34+
35+
36+
@dataclass
37+
class RealtimeTransportAudioEvent:
38+
"""Raw audio bytes emitted by the model."""
39+
40+
data: bytes
41+
response_id: str
42+
43+
type: Literal["audio"] = "audio"
44+
45+
46+
@dataclass
47+
class RealtimeTransportAudioInterruptedEvent:
48+
"""Audio interrupted."""
49+
50+
type: Literal["audio_interrupted"] = "audio_interrupted"
51+
52+
53+
@dataclass
54+
class RealtimeTransportAudioDoneEvent:
55+
"""Audio done."""
56+
57+
type: Literal["audio_done"] = "audio_done"
58+
59+
60+
@dataclass
61+
class RealtimeTransportInputAudioTranscriptionCompletedEvent:
62+
"""Input audio transcription completed."""
63+
64+
item_id: str
65+
transcript: str
66+
67+
type: Literal["conversation.item.input_audio_transcription.completed"] = (
68+
"conversation.item.input_audio_transcription.completed"
69+
)
70+
71+
72+
@dataclass
73+
class RealtimeTransportTranscriptDelta:
74+
"""Partial transcript update."""
75+
76+
item_id: str
77+
delta: str
78+
response_id: str
79+
80+
type: Literal["transcript_delta"] = "transcript_delta"
81+
82+
83+
@dataclass
84+
class RealtimeTransportItemUpdatedEvent:
85+
"""Item added to the history or updated."""
86+
87+
item: RealtimeItem
88+
89+
type: Literal["item_updated"] = "item_updated"
90+
91+
92+
@dataclass
93+
class RealtimeTransportItemDeletedEvent:
94+
"""Item deleted from the history."""
95+
96+
item_id: str
97+
98+
type: Literal["item_deleted"] = "item_deleted"
99+
100+
101+
@dataclass
102+
class RealtimeTransportConnectionStatusEvent:
103+
"""Connection status changed."""
104+
105+
status: RealtimeConnectionStatus
106+
107+
type: Literal["connection_status"] = "connection_status"
108+
109+
110+
@dataclass
111+
class RealtimeTransportTurnStartedEvent:
112+
"""Triggered when the model starts generating a response for a turn."""
113+
114+
type: Literal["turn_started"] = "turn_started"
115+
116+
117+
@dataclass
118+
class RealtimeTransportTurnEndedEvent:
119+
"""Triggered when the model finishes generating a response for a turn."""
120+
121+
type: Literal["turn_ended"] = "turn_ended"
122+
123+
124+
@dataclass
125+
class RealtimeTransportOtherEvent:
126+
"""Used as a catchall for vendor-specific events."""
127+
128+
data: Any
129+
130+
type: Literal["other"] = "other"
131+
132+
133+
# TODO (rm) Add usage events
134+
135+
136+
RealtimeTransportEvent: TypeAlias = Union[
137+
RealtimeTransportErrorEvent,
138+
RealtimeTransportToolCallEvent,
139+
RealtimeTransportAudioEvent,
140+
RealtimeTransportAudioInterruptedEvent,
141+
RealtimeTransportAudioDoneEvent,
142+
RealtimeTransportInputAudioTranscriptionCompletedEvent,
143+
RealtimeTransportTranscriptDelta,
144+
RealtimeTransportItemUpdatedEvent,
145+
RealtimeTransportItemDeletedEvent,
146+
RealtimeTransportConnectionStatusEvent,
147+
RealtimeTransportTurnStartedEvent,
148+
RealtimeTransportTurnEndedEvent,
149+
RealtimeTransportOtherEvent,
150+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from typing import get_args
2+
3+
from agents.realtime.transport_events import RealtimeTransportEvent
4+
5+
6+
def test_all_events_have_type() -> None:
7+
"""Test that all events have a type."""
8+
events = get_args(RealtimeTransportEvent)
9+
assert len(events) > 0
10+
for event in events:
11+
assert event.type is not None
12+
assert isinstance(event.type, str)

0 commit comments

Comments
 (0)