Skip to content

pydantic_ai.realtime

Support for realtime, bidirectional speech-to-speech models (OpenAI Realtime, OpenAI GPT-Live, Azure OpenAI, Gemini Live, xAI Grok Voice, and any other provider that streams audio in and out over a persistent connection).

Unlike Model, which is request-response, a realtime model opens a long-lived connection: you stream audio (or text/images) in, and consume audio, transcripts, and tool calls as they arrive. The high-level entry point is Agent.realtime(), followed by AgentRealtime.session(), which wires the agent’s tools and instructions into a session and runs the tool loop for you. See the Realtime guide for a walkthrough.

The flow of a session:

graph LR
    App -- "send(): RealtimeSessionInput" --> S[RealtimeSession]
    S -- "send(): RealtimeInput" --> C[RealtimeConnection]
    M[RealtimeModel] -- "connect()" --> C
    C -- "RealtimeCodecEvent" --> S
    S -- "RealtimeEvent" --> App

A RealtimeModel opens a RealtimeConnection (the provider-specific transport). A RealtimeSession wraps that connection: it translates the low-level codec events into the shared message/part event vocabulary from pydantic_ai.messages, builds ordinary ModelMessage history, and executes tools automatically — intercepting each ToolCall, running it, sending the ToolResult back, and emitting a FunctionToolCallEvent then a FunctionToolResultEvent. Every tool runs in the background, so a slow one never blocks the session; whether the model keeps speaking meanwhile is provider-specific (see Concurrent tool execution).

Provider abstractions & session

ObjectRole
RealtimeModelProvider ABC; connect() opens a connection.
RealtimeModelSettingsSettings shared by realtime providers.
TurnDetectionCross-provider automatic VAD sensitivity, padding, and silence configuration.
KnownRealtimeModelName / infer_realtime_modelProvider-prefixed model IDs and inference.
RealtimeConnectionProvider ABC; send() content in, iterate events out.
RealtimeSessionWraps a connection with automatic concurrent tool dispatch.

Browser / WebRTC — for browser voice agents, the media flows browser ↔ provider directly while the backend runs a control-plane sideband (OpenAI and Azure OpenAI; see Connecting a frontend):

ObjectRole
RealtimeModel.answer_webrtc_offerRelay the browser’s SDP offer; return the SDP answer and a WebRTCAnswer / WebRTCSession.
RealtimeModel.create_client_secretMint an ephemeral RealtimeClientSecret for a browser client.
AgentRealtime.session(provider_session=…)Attach the sideband session to a RealtimeProviderSession (e.g. a WebRTCSession) and run the agent.

Inputs — RealtimeSession.send accepts session content only, in the shared message vocabulary (RealtimeSessionInput): plain str, image/audio BinaryContent (including BinaryImage and BinaryAudio), or a sequence of these. A string solicits a response, while an image is context-only by default; use respond to override either behavior. Turn-taking and interruption go through the dedicated RealtimeSession methods (commit_audio(), clear_audio(), create_response(), interrupt()), not send(). Use RealtimeSession.enqueue() to queue text or system prompt parts for delivery after the active response, or once the model is idle.

Consumption views — RealtimeSession.stream_audio() yields model audio chunks ready for playback, while RealtimeSession.stream_transcripts() yields finalized speech from both speakers or live deltas with delta=True. These bounded views can run concurrently with each other and with the session’s raw event iterator. RealtimeSession.wait_for_reply() waits until the model has finished the reply it owes, spanning the whole of a tool-calling turn, and runs alongside an active event iterator rather than competing with it. RealtimeSession.wait_for_playback() waits until the single audio view has accounted for all audio emitted so far, whether it was played or discarded. RealtimeSession.close() ends the session and every live view; RealtimeSession.closed exposes its state. The session starts receiving at whichever comes first: the first outbound call, the first stream_audio() or stream_transcripts() view, or the first async for over the session. It does not start on entry, so a view created inside the session block — as every example here does — cannot miss output the model produced before it subscribed. A fatal receive or tool failure is raised from active event iteration; without an active iterator, the views end and context exit raises it. The next outbound session method raises an already-ended receive side’s failure first, and the same failure is never delivered twice.

The low-level RealtimeConnection.send accepts the normalized RealtimeInput — a str text turn, a TextContext item added without soliciting a reply, a raw-PCM BinaryAudio chunk, or a BinaryImage frame — which additionally includes the turn-control verbs (CommitAudio, ClearAudio, CreateResponse, CancelResponse, and TruncateOutput) that those session methods emit, plus ToolResult — which the session sends itself as each tool completes.

Connection events — RealtimeCodecEvent, the low-level codec vocabulary yielded by a connection: AudioDelta, OutputTranscript, InputTranscript, ToolCall, ToolCallCancelled, ResponseDone, RealtimeInputSpeechStartEvent, RealtimeInputSpeechEndEvent, RealtimeOutputSpeechStartEvent, RealtimeOutputSpeechEndEvent, RealtimeInputTranscriptionErrorEvent, RealtimeResponseInterruptedEvent, RealtimeSessionReconnectEvent, ConversationCreated, ConversationItemCreated, InputRejected, PartStartEvent, PartEndEvent, SessionUsage, and RealtimeSessionErrorEvent.

Session events — RealtimeEvent, yielded by a session. The session translates codec events into the shared vocabulary from pydantic_ai.messages: content streams as PartStartEvent / PartDeltaEvent / PartEndEvent (carrying shared message parts including SpeechPart, TextPart, ToolCallPart, and NativeToolReturnPart), tool execution as FunctionToolCallEvent / FunctionToolResultEvent, deferred calls and their inline resolution as DeferredToolRequestsEvent / DeferredToolResultsEvent, enqueued-message delivery as EnqueuedMessagesEvent, and the rest as the control-plane events above (RealtimeInputSpeechStartEvent, RealtimeInputSpeechEndEvent, RealtimeOutputSpeechStartEvent, RealtimeOutputSpeechEndEvent, RealtimeInputTranscriptionErrorEvent, RealtimeResponseInterruptedEvent, RealtimeSessionReconnectEvent, and RealtimeSessionErrorEvent), plus RealtimeTurnCompleteEvent, which the session synthesizes rather than reading off the wire. Usage updates are accumulated on the session and are not yielded.

The lower-level codec vocabulary is documented in pydantic_ai.realtime.codec, and each provider in its own module: pydantic_ai.realtime.openai, pydantic_ai.realtime.openai_live, pydantic_ai.realtime.google, pydantic_ai.realtime.xai, and pydantic_ai.realtime.azure.

Realtime multimodal session support for bidirectional streaming models.

This package adds support for native speech-to-speech models (OpenAI Realtime, Azure OpenAI, Gemini Live, and xAI Grok Voice) which use a persistent bidirectional connection rather than the request-response pattern of the standard Model interface.

The provider-agnostic pieces mirror the request-response layout: pydantic_ai.realtime.model holds RealtimeModel and model inference, pydantic_ai.realtime.settings the settings vocabulary, pydantic_ai.realtime.profiles the model profiles, and pydantic_ai.realtime.codec the low-level connection vocabulary; concrete providers live in submodules (e.g. pydantic_ai.realtime.openai). The high-level entry point is Agent.realtime, followed by AgentRealtime.session.

A session translates the low-level codec events (the connection-facing RealtimeCodecEvent vocabulary) into the shared message/part event vocabulary from pydantic_ai.messages (PartStartEvent, FunctionToolCallEvent, …), plus the realtime control-plane events defined below.

RealtimeModelProfile

Bases: TypedDict

Describes what a RealtimeModel supports, so a session can tailor its behavior to the model.

Mirrors the shape and supports_-prefixed naming of ModelProfile for the standard request-response Model, which realtime models don’t share a hierarchy with.

A RealtimeSession reads these flags to reject unsupported operations with a clear error before sending them, rather than letting the provider fail mid-session. Read a model’s via RealtimeModel.profile; each flag maps to the session methods a provider may not support.

All fields are optional. Consumers treat absent boolean flags as False — except the handful documented below as defaulting to True, which describe a capability every provider has unless it says otherwise — absent supported_native_tools as empty, absent sample rates as the values in DEFAULT_REALTIME_PROFILE, and an absent context_window as unknown.

Attributes

supports_image_input

Whether the model accepts discrete image/video frames via image BinaryContent passed to send.

Type: bool

image_input_requires_response

Whether an image is only taken as the start of a response, rather than as context.

When True, send accepts an image only with respond=True, and does so without the model supporting general manual turn control; a context-only image raises. OpenAI GPT-Live sets it: its voice model sees no images, and an image reaches the delegated backend only when that backend runs on it. Default: False.

Type: bool

supports_manual_turn_control

Whether the model supports manual turn-taking — commit_audio, clear_audio, and create_response (push-to-talk). When False the model drives turn-taking itself via automatic voice activity detection.

Type: bool

supports_interruption

Whether the model supports server-side interruption — cancelling the model’s in-progress response via interrupt.

Type: bool

supports_output_truncation

Whether the model can truncate its in-progress audio output to the point the user actually heard, via the played_ms argument of interrupt.

Distinct from supports_interruption: a provider may support cancelling a response (barge-in) without supporting output truncation. OpenAI supports both; xAI Grok Voice supports cancellation, but its truncation only lands after a response has ended, and unreliably, so it reports False.

Type: bool

supports_text_output

Whether the model can generate text instead of speech, via output_modality='text'.

Defaults to True: a realtime model that only speaks is the exception, not the rule. When False, Agent.realtime rejects output_modality='text' with a UserError before connecting, rather than letting the provider fail the handshake (Gemini Live answers 1007 The requested combination of response modalities (TEXT) is not supported by the model) or, worse, silently produce speech anyway (xAI).

Type: bool

supports_session_seeding

Whether the model can seed a session with prior conversation (message_history).

Type: bool

supports_webrtc

Whether the model supports browser WebRTC signaling, ephemeral client secrets, and a server-side control-plane sideband via answer_webrtc_offer, create_client_secret, and connect_webrtc.

Supported by OpenAI and Azure OpenAI. OpenAI GPT-Live supports the offer relay and the sideband but has no client secrets. Gemini Live and xAI Grok Voice are WebSocket-only.

Type: bool

supports_seeding_images

Whether prior images can be included when seeding a session with message_history.

Type: bool

supports_seeding_audio

Whether retained user audio can be included when seeding a session with message_history.

Type: bool

supports_thinking

Whether the model supports reasoning/thinking configuration via the thinking setting — OpenAI’s gpt-realtime-2* reasoning models, Gemini’s native-audio models, xAI’s grok-voice-latest and grok-voice-think-* models, and OpenAI GPT-Live, whose delegated backend does the reasoning. When False (the default), a thinking setting is silently ignored rather than sent to a model that would reject it.

Type: bool

async_tool_call_mode

Whether the model keeps the conversation going while a tool call runs. Default: 'never'.

With async tool calls, the model can keep speaking (typically saying what it’s doing) and answer the user while a tool runs, and the result reaches it when it’s ready. Without them, the model goes quiet until the result is back.

'optional' models run tool calls asynchronously when the session asks for it with the async_tool_calls setting, and 'never' and 'always' models ignore that setting. OpenAI Realtime, Azure OpenAI, OpenAI GPT-Live, xAI Grok Voice, and gemini-3.8-live-extended-thinking are 'always'; the Gemini native-audio models and gemini-3.8-live are 'optional'; other Gemini Live models are 'never', since they accept an async declaration and then wait for the result anyway.

Type: AsyncToolCallMode

supports_async_tool_calls

Deprecated: use async_tool_call_mode instead.

A resolved RealtimeModel.profile still carries it, as async_tool_call_mode != 'never'. Setting it in a profile= override is translated (with a deprecation warning): True to 'optional' and False to 'never', except that it never changes an 'always' model, which it never could.

Type: bool

supports_tool_return_schema

Whether the model natively renders a tool’s return_schema (Gemini Live’s function-declaration response schema). Where it can’t, a tool that opted in via include_return_schema gets the schema injected into its description instead, exactly as on a standard Model.

Type: bool

supported_native_tools

The native tools the model runs server-side, e.g. WebSearchTool.

Agent.realtime validates the session’s native tools against this set before connecting, raising a UserError that names any the model doesn’t support — mirroring the classic Model.supported_native_tools check.

Type: frozenset[type[AbstractNativeTool]]

emits_input_speech_events

Whether the provider reports when the user starts and stops speaking, as RealtimeInputSpeechStartEvent and RealtimeInputSpeechEndEvent.

emits_ rather than supports_ because this describes events that appear in the stream, not an operation the session can invoke. The OpenAI-protocol providers (OpenAI, Azure OpenAI, xAI) emit them; Gemini Live does not — a UI that shows a “listening” indicator should read this flag rather than wait for events that will never arrive.

Type: bool

synthesizes_turn_boundary

Whether RealtimeTurnCompleteEvent is inferred by Pydantic AI rather than reported by the provider.

synthesizes_ rather than supports_ for the same reason as emits_input_speech_events: it describes how a stream is produced, not an operation the session can invoke.

False (the default) means the provider sends an explicit end-of-response frame, so the turn boundary is a protocol fact. True means the protocol has no such frame and the adapter derives the boundary from output timing, so it is a good guess rather than a guarantee: a long pause mid-sentence can end a turn early, and an application that must not act on a partial reply should confirm against the transcript. OpenAI GPT-Live is the only model that sets it.

Type: bool

responses_are_requests

Whether each response the session records is one request to the model, for usage.requests and the request_limit that bounds it.

True (the default) counts every recorded ModelResponse, as a standard run does, and checks the limit before each response starts. False is for a model whose recorded responses aren’t what it is asked for: OpenAI GPT-Live’s spoken replies are turns the session infers, while the requests that spend tokens go to the backend it delegates to. There, each response-scoped usage report counts as one request, and the limit is checked as each arrives, so the report that would go past it ends the session.

Type: bool

response_usage_covers_context

Whether a response’s reported token usage covers everything the model holds in context.

RealtimeSession.context_window_used divides the latest response’s total_tokens by context_window when the provider doesn’t report the fraction itself, which is only meaningful when those tokens are the whole context. Defaults to True. False makes it None instead of a misleading ratio: xAI Grok Voice reports only the input a response added, and OpenAI GPT-Live’s token usage belongs to the backend it delegates to rather than to the voice model’s own context.

Type: bool

audio_input_sample_rate

The sample rate, in Hz, expected for raw PCM audio input.

Read it via RealtimeSession.audio_input_sample_rate (or RealtimeModel.audio_input_sample_rate before a session exists), which fall back to the default when a profile omits it.

Type: int

audio_output_sample_rate

The sample rate, in Hz, produced in raw PCM audio output deltas.

Read it via RealtimeSession.audio_output_sample_rate (or RealtimeModel.audio_output_sample_rate before a session exists), which fall back to the default when a profile omits it.

Type: int

context_window

The maximum number of tokens the model can hold in a session, input and output combined. Default: None (unknown).

When no profile layer sets this, RealtimeModel.profile fills it in from genai-prices data if the model is known there. Set it explicitly for models it doesn’t know, e.g. profile={'context_window': 128_000}. Read it via RealtimeModel.context_window.

Type: int | None

RealtimeError

Bases: ModelAPIError

A realtime connection or protocol failure: the session could not be opened, or is over.

Raised when the handshake fails, the provider closes the session, a send fails, or reconnecting gives up. A rejected WebSocket upgrade is the exception: it carries an HTTP status, so it raises ModelHTTPError like a regular request.

A subclass of ModelAPIError, since losing the connection to a realtime provider is the same kind of failure as a request-response call that couldn’t reach the API. Catch it specifically to separate the session’s own failures from those of any text agent the session delegates to.

TurnDetection

Bases: TypedDict

Cross-provider automatic voice-activity detection (VAD) knobs.

Set as RealtimeModelSettings.turn_detection to turn automatic detection on with these settings. (Pass True for the provider defaults, or False to disable it entirely for push-to-talk.) Each field maps to the closest knob on each provider; for finer, provider-specific control use the provider-prefixed escape hatch (openai_turn_detection, xai_turn_detection, google_vad), which fully overrides this when set.

Attributes

sensitivity

How readily the provider detects turn boundaries (speech start/end). Higher is snappier but more prone to false triggers. Defaults to the provider default. Maps per provider: OpenAI / Azure / xAI → server-VAD threshold (low≈0.7, medium≈0.5, high≈0.3); Gemini → both start and end sensitivity (low→low, high→high, medium leaves the provider default).

Type: Literal[‘low’, ‘medium’, ‘high’]

prefix_padding_ms

Audio retained before detected speech onset, in milliseconds. Honored by OpenAI, xAI, and Gemini. Defaults to the provider default.

Type: int

silence_duration_ms

Silence required to mark the end of speech, in milliseconds. Honored by OpenAI, xAI, and Gemini. Defaults to the provider default.

Type: int

RealtimeModelSettings

Bases: TypedDict

Settings to configure a realtime model session.

Defines the common settings vocabulary used across realtime model providers. Unsupported settings are silently ignored. Providers with additional generation parameters extend it, e.g. GoogleRealtimeModelSettings.

Attributes

max_tokens

The maximum number of tokens to generate per response before stopping.

Supported by: OpenAI, Azure OpenAI, and Gemini. xAI accepts it but ignores it.

Type: int

parallel_tool_calls

Whether to allow parallel tool calls.

Supported by: OpenAI, Azure OpenAI, Azure AI Voice Live (where True leaves the model’s default, since some models can’t call tools in parallel), and OpenAI GPT-Live, where it applies to the delegated backend unless openai_live_delegation sets its own parallel_tool_calls. xAI accepts it but ignores it.

Type: bool

async_tool_calls

Whether the model keeps the conversation going while a tool call runs. None (the default) leaves it to the model.

With async tool calls, the model can keep speaking (typically saying what it’s doing) and answer the user while a tool runs, and the result reaches it when it’s ready. Without them, the model goes quiet until the result is back. This pays off for tools that take a noticeable moment; see Concurrent tool execution for the tradeoffs.

Only models whose profile’s async_tool_call_mode is 'optional' offer a choice, and every one of them defaults to off. The others ignore this setting, since they either always or never run tool calls asynchronously.

Supported by: the Gemini native-audio models and gemini-3.8-live.

Type: bool | None

tool_choice

Control which function tools the model can use.

See the Tool Choice guide for detailed documentation. Every form is resolved exactly as it is for a standard run, including the error a name that matches no tool raises; a session has no output tools, so ToolOrOutput restricts the function tools while leaving the model free to just speak.

It is applied once, when the session is created, and holds for every response. 'none' and function-tool allow-lists are enforced on every provider by restricting the tools advertised to the model. OpenAI, Azure OpenAI, and xAI send the mode too, so they raise UserError before connecting for a choice that forces a tool call ('required' or a list of tool names): applied to every response, including the one after a tool result, it would never let the model answer. Use ToolOrOutput to restrict the tools instead. Gemini has no declarative tool-choice configuration, so 'required' is ignored and allow-lists restrict availability without requiring a tool call.

Supported by: OpenAI, Azure OpenAI, Gemini ('none' and function-tool allow-lists only), xAI, and OpenAI GPT-Live, which raises for 'required' and lists of tool names: a session applies the choice to every response, including the one after a tool result, so a forced call never lets the model answer.

Type: ToolChoice

input_transcription_model

Model used to transcribe the user’s audio input, so their turns are captured into history.

'auto' (the default) uses the provider’s recommended realtime transcription model; pass a specific id (e.g. 'gpt-live-transcribe') to pin one, or None to disable transcription (see audio_retention to retain the raw audio instead).

None turns transcription off on every provider. A pinned id applies only to the providers that transcribe with a separate model — Gemini transcribes natively, with no model to point at, and ignores it (google_input_transcription configures Gemini’s own transcription).

Supported by: OpenAI, Azure OpenAI, Gemini (None only), and xAI.

Type: KnownRealtimeTranscriptionModelName | str | None

output_modality

The single modality generated by the model. Defaults to 'audio'.

Unlike the other settings here, an unsupported value is not silently ignored: a model whose profile reports supports_text_output=False raises UserError before connecting, because a session that quietly spoke instead of writing would be worse than one that didn’t start.

Supported by: OpenAI and Azure OpenAI. Gemini Live and xAI always generate audio — read the spoken answer from the transcript on the SpeechPart instead.

Type: Literal[‘audio’, ‘text’]

thinking

Enable or configure reasoning/thinking, mirroring the unified thinking setting on the request-response models.

True enables it at the provider default, and 'minimal'/'low'/'medium'/'high'/'xhigh' selects an effort level. False disables thinking (sent as effort 'none' on OpenAI, Azure OpenAI, Azure AI Voice Live, and xAI). OpenAI and Gemini apply it only to models whose profile reports supports_thinking. Other models silently ignore it. Providers with a richer native config expose it separately (e.g. Gemini’s google_thinking_config), which takes precedence.

Supported by: OpenAI gpt-realtime-2* models (also on Azure), reasoning chat models like gpt-5 on Azure AI Voice Live, Gemini native-audio models, xAI’s reasoning Grok Voice models (grok-voice-latest and the grok-voice-think-* family), and OpenAI GPT-Live, where it sets the reasoning effort of the delegated backend model if that model reasons, unless openai_live_delegation sets its own reasoning_effort.

Type: ThinkingLevel

turn_detection

Automatic voice-activity detection (VAD) / turn-taking. Modeled on thinking:

  • Absent (the default) or True: automatic turn detection on, at the provider’s defaults.
  • False: disable it — push-to-talk, drive turns manually with commit_audio() / create_response() (only on providers whose model profile reports supports_manual_turn_control).
  • TurnDetection: on, with specific cross-provider knobs.

For finer, provider-specific control use the provider-prefixed setting documented on each provider’s settings type (openai_turn_detection, xai_turn_detection, google_vad); when present it fully overrides this field.

Type: bool | TurnDetection

handshake_timeout

Seconds to wait for the realtime protocol handshake to complete. Defaults to 30.0.

On OpenAI, Azure OpenAI, and xAI this bounds the wait for each handshake event; on Gemini it bounds opening the socket and waiting for the session setup to complete.

Supported by: OpenAI, Azure OpenAI, Gemini, and xAI.

Type: float

reconnect

ReconnectPolicy to transparently recover from a dropped connection. Without a policy, an unexpectedly closed connection is fatal: the low-level connection reports a non-recoverable session error and RealtimeSession raises RealtimeError from iteration.

What server-side state survives a reconnect depends on the provider (see ReconnectPolicy). Setting a policy enables Gemini’s native session resumption unless google_enable_session_resumption=False is set explicitly — that combination raises UserError at connect time, since a re-dial without resumption would lose the conversation.

Supported by: OpenAI, Azure OpenAI, Gemini, xAI, and OpenAI GPT-Live, which forks a session stored with openai_live_store=True and otherwise replays the local history into a new one.

Type: ReconnectPolicy

RealtimeClientSecret

An ephemeral client secret (short-lived token) that a browser can use to talk to a provider directly.

Minted server-side by create_client_secret so a long-lived API key never reaches the browser. The token is bound to a session configuration (instructions, tools, voice, VAD) and expires quickly (OpenAI: about a minute).

Attributes

value

The ephemeral secret to hand to the browser client.

Kept out of the repr so logging or inspecting the object doesn’t leak the live token into logs.

Type: str Default: field(repr=False)

expires_at

When the secret expires (timezone-aware UTC).

Type: datetime

provider_details

Raw provider fields returned alongside the secret (e.g. the resolved session object).

Kept out of the repr alongside value: the resolved session carries the instructions and tool definitions, so a logged repr would otherwise expose them. Access the field explicitly to read it.

Type: dict[str, Any] | None Default: field(default=None, repr=False)

RealtimeProviderSession

Bases: Protocol

A handle to a provider-side realtime session that a server sideband connection can attach to.

The transport-neutral contract that Agent.realtime accepts as provider_session: it needs only the owning provider (to check the attaching model matches) and an opaque session identifier (to address the control-plane connection). Different transports satisfy it with their own handle types — a WebRTC HTTP relay yields a WebRTCSession; a provider that negotiates over a WebSocket can supply its own.

Attributes

provider_name

The provider that owns the session (e.g. 'openai' or 'azure'); must match the model attaching to it.

Type: str

session_id

The provider-assigned identifier used to address the session’s control-plane connection.

Type: str

WebRTCSession

A RealtimeProviderSession for a WebRTC call.

Produced by answer_webrtc_offer and passed as provider_session to Agent.realtime to run the agent loop over the call’s control plane while the browser owns the audio.

Attributes

provider_name

The provider that owns the call (e.g. 'openai' or 'azure'); must match the model attaching to it.

Type: str

session_id

The provider-assigned call identifier (OpenAI/Azure return it as the call_id in the Location header).

Type: str

provider_details

Raw provider details about the call (e.g. the original Location header).

Type: dict[str, Any] | None Default: None

call_id

Alias for session_id under the OpenAI/Azure wire name.

Type: str

TranscriptUpdate

One incremental transcript update, carrying everything needed to render it.

Yielded by RealtimeSession.stream_transcripts(delta=True). A realtime session is duplex, so both speakers’ transcripts stream at the same time and a caption UI needs to know not just what was said but which turn to put it in — otherwise two consecutive turns by the same speaker run together.

Attributes

index

Identifies the turn this update belongs to, stable for the life of the session.

Use it as the key for whatever you render a turn into: every update with the same index belongs to the same speech part.

Type: int

speaker

Who is speaking.

Type: Literal[‘user’, ‘assistant’]

delta

The text this update added, when it added any.

Empty when the provider revised the turn instead of extending it — speech recognition is revisable, and a correction can’t be expressed as an addition. Render transcript and this never matters.

Type: str

transcript

The full transcript of this turn so far.

Render this, keyed on index, and captions are correct whatever the provider does: no accumulating, no special case for a revision, and a dropped update (if a consumer fell behind) self-corrects on the next one.

Type: str

WebRTCAnswer

The provider’s WebRTC SDP answer plus the WebRTCSession to attach to.

Return sdp to the browser to complete the WebRTC handshake, then pass session as provider_session to Agent.realtime to run the sideband session.

Attributes

sdp

The provider’s SDP answer, to send back to the browser as the remote description.

Type: str

session

The call handle the server sideband session attaches to.

Type: WebRTCSession

RealtimeModel

Bases: AbstractModel

Abstract base class for realtime model providers.

RealtimeModel and the request-response Model share AbstractModel. A realtime model opens a persistent bidirectional connection for streaming content in and out.

Like Model, the settings attribute and the model_settings passed to connect are typed as the shared RealtimeModelSettings; each provider narrows to its own TypedDict subclass internally with a cast (as the request-response models do for ModelSettings), rather than the base class being generic over the settings type.

Attributes

settings

Model settings used as defaults for realtime sessions.

Type: RealtimeModelSettings | None Default: settings

model_name

The model name, e.g. gpt-realtime.

Type: str

base_url

The provider API base URL, when this model is backed by a provider.

Type: str | None

profile

The realtime model profile.

Resolution order mirrors Model.profile (later layers override earlier ones):

  1. DEFAULT_REALTIME_PROFILE — base values for every key.
  2. The provider’s realtime_model_profile(model_name) result — provider-specific defaults.
  3. A best-effort context_window value from genai-prices, unless the provider or a partial user profile explicitly set the field (including to None).
  4. The model class’s adjustments for what this instance supports beyond its name, such as the API surface its client talks to (e.g. a flag verified on one surface only).
  5. The user’s profile= argument — a partial dict merged on top, OR a callable (resolved) -> profile for full control.

Then supported_native_tools is intersected with what this model class actually implements, so the resolved profile is the single source of truth for what is usable, and the deprecated supports_async_tool_calls is derived from async_tool_call_mode.

Type: RealtimeModelProfile

context_window

The resolved profile’s context_window.

Type: int | None

audio_input_sample_rate

The sample rate, in Hz, expected for raw PCM audio input.

Also available on the session as RealtimeSession.audio_input_sample_rate; read it here when audio capture must be configured before a session exists.

Type: int

audio_output_sample_rate

The sample rate, in Hz, of the raw PCM audio the model produces.

Also available on the session as RealtimeSession.audio_output_sample_rate; read it here when audio playback must be configured before a session exists.

Type: int

Methods

__init__
def __init__(
    *,
    settings: RealtimeModelSettings | None = None,
    profile: RealtimeModelProfileSpec | None = None,
) -> None

Store the model-level settings and profile= layers, as Model’s constructor does.

Also loads genai-prices’ data snapshot, which profile consults for context_window, so that one-time cost stays off the event loop.

Returns

None

supported_native_tools

@classmethod

def supported_native_tools(cls) -> frozenset[type[AbstractNativeTool]]

Return the native tool types implemented by this realtime model class.

Returns

frozenset[type[AbstractNativeTool]]

connect

@abstractmethod

def connect(
    *,
    messages: Sequence[ModelMessage],
    model_settings: RealtimeModelSettings | None,
    model_request_parameters: ModelRequestParameters,
) -> AbstractAsyncContextManager[RealtimeConnection]

Open a connection to the realtime model.

Returns

AbstractAsyncContextManager[RealtimeConnection] — An async context manager yielding a RealtimeConnection.

Parameters

messages : Sequence[ModelMessage]

Prior conversation and the current request carrying session instructions, projected to the provider’s initial conversation items. Replayable text, transcripts, thinking, tool rounds, images, and retained user audio are seeded according to the model profile; content the provider cannot represent raises UserError.

model_settings : RealtimeModelSettings | None

Optional provider-specific settings.

model_request_parameters : ModelRequestParameters

Function and native tools available to the session.

connect_webrtc
def connect_webrtc(
    session: RealtimeProviderSession,
    *,
    messages: Sequence[ModelMessage],
    model_settings: RealtimeModelSettings | None,
    model_request_parameters: ModelRequestParameters,
) -> AbstractAsyncContextManager[RealtimeConnection]

Attach a control-plane (sideband) connection to an existing provider-side session.

The returned connection runs the agent loop over the session’s control channel while the browser exchanges audio with the provider directly, so the sideband doesn’t own the audio transport. Only realtime models whose provider supports WebRTC server-side controls (OpenAI and Azure OpenAI) implement this; the default raises UserError and points callers to the WebSocket transport.

Returns

AbstractAsyncContextManager[RealtimeConnection]

create_client_secret

@async

def create_client_secret(
    *,
    instructions: str | None = None,
    tools: Sequence[ToolDefinition] | None = None,
    model_settings: RealtimeModelSettings | None = None,
    expires_after_seconds: int | None = None,
) -> RealtimeClientSecret

Mint an ephemeral RealtimeClientSecret for a browser client.

Binds the token to the given session configuration so a browser can open a realtime connection directly without ever holding a long-lived API key. Only implemented by providers that support ephemeral tokens (OpenAI and Azure OpenAI); the default raises UserError and points callers to the WebSocket transport.

Returns

RealtimeClientSecret

answer_webrtc_offer

@async

def answer_webrtc_offer(
    sdp_offer: str,
    *,
    instructions: str | None = None,
    tools: Sequence[ToolDefinition] | None = None,
    model_settings: RealtimeModelSettings | None = None,
    message_history: Sequence[ModelMessage] | None = None,
) -> WebRTCAnswer

Relay a browser’s WebRTC SDP offer to the provider and return the SDP answer plus a WebRTCSession.

This is the secure signaling path: the server (holding the API key) negotiates the WebRTC call on the browser’s behalf, so the browser never sees a token. Return WebRTCAnswer.sdp to the browser, then pass WebRTCAnswer.session as provider_session to Agent.realtime. Only implemented by providers that support WebRTC (OpenAI and Azure OpenAI); the default raises UserError and points callers to the WebSocket transport.

message_history is the conversation the call continues. A model whose protocol only takes history when a session starts (OpenAI GPT-Live) seeds it into the session this creates; the others seed it when the sideband attaches, from the history that session is opened with, so pass the same history to both.

Returns

WebRTCAnswer

hang_up

@async

def hang_up(session: RealtimeProviderSession) -> None

End a provider-side call, such as a browser’s WebRTC call, for everyone on it.

Takes the WebRTCAnswer.session that answer_webrtc_offer returned, so a server can end a call with or without a sideband attached to it; a sideband session can use RealtimeSession.hang_up instead. A call that has already ended is not an error; the provider likely answers the same for a call negotiated with a different API key or project, so that reads as already ended too. Only implemented by realtime models whose provider can end a WebRTC call from the server (OpenAI gpt-realtime and GPT-Live); the default raises UserError.

Returns

None

ReconnectPolicy

Bases: TypedDict

How to recover when a realtime connection drops mid-session.

Set as the reconnect key of RealtimeModelSettings, either as a model-level default (settings=) or per session (model_settings=).

On a dropped connection the session is re-dialed and its configuration (instructions, tools, voice, …) re-applied, emitting a RealtimeSessionReconnectEvent event. What server-side state survives depends on the provider: OpenAI Realtime, Azure OpenAI and xAI Grok Voice have the session replay its finalized message history into the new connection (prior turns survive; buffered input audio and a reply in flight do not), while Gemini Live restores prior turns through native session resumption, enabled automatically whenever a reconnect policy is set (Gemini honors an explicit google_enable_session_resumption=False opt-out by refusing the combination with a UserError).

Attributes

max_attempts

Number of re-dial attempts per drop before giving up and raising RealtimeError. Defaults to 3.

Type: int

max_reconnects

Total successful reconnects allowed for the life of the session.

max_attempts bounds the retries for a single drop, and resets once a dial succeeds, so on its own it cannot stop a session that reconnects, drops, and reconnects forever. This bounds the whole session instead.

The default is generous for the case this exists to serve: providers end sessions at a duration cap (OpenAI at 60 minutes) and a long-running session legitimately renews at that boundary, so 50 covers days of continuous conversation. It only bites a server that hangs up as fast as we dial. Defaults to 50.

Type: int

base_delay

Base backoff delay in seconds; doubles each attempt up to max_delay. Defaults to 0.5.

Type: float

max_delay

Maximum backoff delay in seconds. Defaults to 30.0.

Type: float

jitter

Whether to apply random jitter to each backoff delay to avoid thundering herds. Defaults to True.

Type: bool

RealtimeSession

Wraps a RealtimeConnection, building message history and auto-executing tools.

The session translates the connection’s low-level codec events into the shared message/part event vocabulary from pydantic_ai.messages and accumulates ordinary ModelMessage history as the conversation proceeds, so a session can hand off to Agent.run via all_messages:

Tools always run concurrently with the session. The session keeps streaming events while a tool runs, so the model can keep speaking and user speech keeps being processed, then sends the result back over the connection once it is ready. This mirrors how a person can keep talking while work happens.

Tool outcomes use the same normalized history shapes as a classic agent run: retries become RetryPromptParts, denials retain their outcome, and structured returns preserve content, metadata, and typed tool_kind identity. Realtime tool-output channels are string-only, so the structured part is rendered only when it is sent. OpenAI-protocol connections send additional user content as a follow-up conversation item; Gemini includes a text fallback in its tool response. If the provider cancels an in-flight call, the session records a synthetic interrupted return for valid history but does not send that abandoned result to the provider.

History is accumulated in the order events are reported. Provider item IDs keep interleaved input transcripts associated with their correct user turns; providers without item IDs retain arrival-order association. Tool results are the exception: a tool’s FunctionToolResultEvent streams whenever the tool finishes (possibly after later turns), but in all_messages() its result part is placed directly after the response carrying its call — request-response APIs require that adjacency, so the history stays valid for a handoff to a standard Agent.run.

Images and video frames streamed with send are stored as ordinary user image turns by default. Set retain_images_every_n above 1 to sample high-rate frame streams, and retain_images_max (default 100) to bound how many stay in history — the oldest retained image is evicted first, so a long-running stream can’t grow memory without limit. Audio kept by audio_retention is bounded the same way: retain_audio_max_seconds (default 1800, 30 minutes across both speakers) caps how much stays in memory, evicting the oldest retained audio first while keeping its transcript.

When constructing a session directly, use it as an async context manager. The context owns the receive pump, background tool tasks, and instrumentation spans; iteration only reads its event queue. AgentRealtime.session enters the session before yielding it, so the usual agent API remains a single async with block.

Attributes

usage

Cumulative token usage and tool-call counts for the session, updated as events stream in.

Pass usage to Agent.realtime to accumulate into a shared RunUsage; otherwise a fresh one is used.

Default: usage if usage is not None else RunUsage()

closed

Whether the session has been closed.

Type: bool

result

The final result once the session context has exited, otherwise None.

Type: AgentRunResult[str] | None

profile

What the connected model supports, as RealtimeModel.profile.

Available here because the session is what a call actually holds: agent.realtime() accepts a model name and builds the model itself, leaving nothing else to read the profile from. The audio sample rates have their own dedicated properties — audio_input_sample_rate and audio_output_sample_rate — so most code never needs to read the profile directly.

Type: RealtimeModelProfile

audio_input_sample_rate

The sample rate, in Hz, of the raw PCM audio this session expects.

Resample the microphone to this rate before send_audio: audio sent at the wrong rate is heard as a chipmunk (or slow-motion voice) rather than reported as an error.

Type: int

audio_output_sample_rate

The sample rate, in Hz, of the raw PCM audio stream_audio() yields.

Play output at this rate; it can differ from audio_input_sample_rate (Gemini Live, for example, listens at 16 kHz and speaks at 24 kHz).

Type: int

context_window_used

Fraction of the model’s context window occupied, as of the latest report.

When the provider reports it (OpenAI GPT-Live), this is the latest value it reported. Otherwise it is computed as for a standard run’s RunContext.context_window_used: the latest response’s total_tokens over the model’s context_window.

The value can go down as the session continues, when the provider compacts or truncates the conversation server-side. Returns None when the ratio cannot be calculated: when the context window or usage is unknown, before the first response, or when the model’s response usage doesn’t measure what the context holds (see response_usage_covers_context).

Type: float | None

played_audio_bytes

The playback position of the session’s single stream_audio() consumer, in raw PCM bytes.

A chunk counts as played once the consumer comes back for the next one, so under the documented playback pattern — write each chunk to the device and wait until it is consumed before pulling the next — this is the position at the last chunk boundary the device reached, the same value a hand-rolled counter incremented after each write would report. The chunk being written is not counted until it is done, so barge-in attributes at most one chunk less than was really heard, never more. A consumer that instead buffers ahead reads too far; count actual device consumption yourself in that case. Made to be passed to interrupt(played_bytes=...) on barge-in. Requires exactly one active stream_audio() iterator, like that call.

Type: int

conversation

This session’s Conversation, ready to hand to a text run.

The same bundle AgentRunResult.conversation produces, so a spoken conversation can be continued by Agent.run and handed back again, carrying its usage rather than restarting the budget each time it changes modality.

A session opened without a conversation_id resolves one here the way Agent.realtime does up front — continuing the conversation its seeded history belongs to, or starting a new one — and keeps it, so every bundle taken from the session and every message it records shares one identity to store the conversation under.

Type: Conversation

Methods

close

@async

def close() -> None

Close the session and end its live stream views.

This method is idempotent. Concurrent callers wait for the same teardown, which continues if a caller is cancelled; leaving the session context waits for it to finish. Active stream_audio() and stream_transcripts() iterators finish cleanly, with any buffered items discarded. The surrounding model context owns the underlying connection, so it remains open until that context exits.

A tool can call this method through ctx.realtime_session to end the session. The calling tool does not resume, and its call is recorded as interrupted. On a WebRTC sideband this only detaches, and the browser’s call stays up: use hang_up() to end it.

Raises whatever ended the session — a provider hangup, an exceeded usage_limits, or a failed tool — unless it was already raised by event iteration or an outbound session method.

Returns

None

hang_up

@async

def hang_up() -> None

End the call, then close the session.

On a WebRTC sideband session, close() only detaches this server from the call, and the browser stays connected to the provider (and billed) until it hangs up itself. This asks the provider to end the call for everyone. On a session that owns its connection, closing it already ends the call, so this is the same as close(). Either way, the session is closed afterwards, as by close().

A tool can call this method through ctx.realtime_session, as it can close(): the call is still ended after the tool itself is cancelled by the teardown.

Raises ModelHTTPError or ModelAPIError if the provider refuses to end the call, and UserError if the model can’t hang up WebRTC calls.

Returns

None

stream_audio
def stream_audio() -> AsyncIterator[bytes]

Stream model audio chunks ready for playback.

The iterator contains only live model audio, in playback order. It never repeats retained audio from finalized speech parts. On a WebRTC sideband the browser owns the audio path, so this raises UserError; consume the browser’s remote media track instead.

The subscription starts when this method is called, so audio the model produces between the call and the consumer’s first iteration is buffered rather than missed. A view handed to asyncio.create_task therefore misses nothing while waiting for its first turn on the event loop. Models generate speech several times faster than it plays, so each iterator buffers up to five minutes of audio: a consumer that plays each chunk before pulling the next receives a long reply in full. Past that bound (a consumer that stopped iterating, or never started), the oldest chunk is dropped so audio playback cannot stall tool execution, turn tracking, or the main event stream; an unconsumed view keeps that bounded buffer until it is collected. A device-paced consumer also feeds played_audio_bytes, and on barge-in interrupt(played_bytes=...) discards buffered chunks the user will never hear, so a playback loop can iterate one stream for the whole session. Closing the session discards buffered chunks and ends the iterator cleanly. If a tool fails while the event stream has no consumer, this view ends and the error is raised when the session closes.

Returns

AsyncIterator[bytes]

wait_for_playback

@async

def wait_for_playback() -> None

Wait until the session’s single audio view has accounted for all audio emitted so far.

Call this after a reply finishes generating, before closing the session or opening the microphone, so buffered audio is not cut off. Playback advances with the same one-chunk lag as played_audio_bytes: a chunk counts once the consumer requests the next one. Audio the view never plays counts as accounted for rather than played: discarded by a barge-in or by the view’s buffer overflowing, or emitted before the view subscribed. The wait also ends if the view is closed or abandoned, or the session closes. Requires exactly one active stream_audio() iterator.

Returns

None

wait_for_reply

@async

def wait_for_reply() -> None

Wait until the model has finished the reply it owes, if any.

Returns once the exchange in progress reaches the boundary RealtimeTurnCompleteEvent marks — after a tool-calling turn, that is the answer that follows the tool results, not the response that called them. Returns immediately when the model owes nothing, so a reply that finished between the send() and this call is not waited for twice over; it also returns if the session closes, fails (a tool raised, or a usage limit tripped) so the reply can no longer come, or the provider refuses the request for the reply (reported as a RealtimeSessionErrorEvent).

This is the wait async for event in session would otherwise be written out to perform, and unlike that loop it can run while something else is iterating the session, so a caller that only wants the turn boundary does not have to take over the event stream to find it:

from pydantic_ai import Agent

agent = Agent(instructions='You are a helpful voice assistant.')


async def main():
    async with agent.realtime('openai:gpt-realtime').session() as session:
        await session.send('Say hello.')
        await session.wait_for_reply()

The reply can show up in all_messages() later than it ends (by up to 30 seconds), where history waits for the transcript of a spoken turn before it (see History).

Waiting for the model to stop generating is not the same as waiting for the speaker to stop playing: pair it with wait_for_playback() before opening the microphone, so the reply is not cut off and the model does not hear itself.

Returns

None

stream_transcripts
def stream_transcripts(*, delta: Literal[False] = False) -> AsyncIterator[SpeechPart]
def stream_transcripts(*, delta: Literal[True]) -> AsyncIterator[TranscriptUpdate]

Stream speech transcripts for both the user and assistant.

By default, yields finalized SpeechPart instances — one per completed turn, carrying its speaker and full transcript.

Pass delta=True for live captions, which yields TranscriptUpdates carrying the new text, the turn’s full transcript so far, the speaker, and an index identifying the turn. Both speakers stream at once, so that index is what lets a UI keep two turns apart instead of running them together. Empty updates and finalized parts without a transcript are omitted.

Final transcripts and deltas are separate subscriptions, so one never crowds out the other. The subscription starts when this method is called, so nothing said between the call and the consumer’s first iteration is missed. Each iterator buffers up to 512 items; if its consumer falls behind (or never starts), the oldest is dropped, because captioning must not be able to stall tool execution, turn tracking, or the main event stream. Closing the session discards buffered items and ends the iterator cleanly; an unconsumed view otherwise keeps its bounded buffer until it is collected. If a tool fails while the event stream has no consumer, this view ends and the error is raised when the session closes.

Returns

AsyncIterator[SpeechPart | TranscriptUpdate]

all_messages
def all_messages() -> list[ModelMessage]

A snapshot of the seeded history plus messages recorded during this session.

Returns a copy, so the result doesn’t change as the session continues. Feed it into Agent.run(message_history=...) to hand the conversation off to a standard agent run. Images streamed with send() are recorded according to retain_images_every_n, bounded by retain_images_max, and audio retained by audio_retention is bounded by retain_audio_max_seconds.

Returns

list[ModelMessage]

new_messages
def new_messages() -> list[ModelMessage]

A snapshot of the messages created during this session (excluding the seeded history).

Returns

list[ModelMessage]

send

@async

def send(
    content: RealtimeSessionInput | Sequence[RealtimeSessionInput],
    *,
    respond: bool | None = None,
) -> None

Feed content into the session.

A str is a complete text turn that the model replies to. Do not follow send('...') with create_response(): that asks for two responses. An image is added as context without soliciting a response, while audio is streamed into the input buffer.

Set respond=False to add text or an image as context without soliciting a response. Set respond=True to solicit a response to text or an image; this requires manual turn control for images. respond=True cannot be used with audio. For a sequence, an explicit respond value adds every item except the last as context and applies that value to the last item. With the default respond=None, each item keeps its usual behavior.

Accepts the shared message vocabulary: plain text as a str, image/audio BinaryContent (including BinaryImage and BinaryAudio), or a sequence of these inputs, dispatched in order. Text and retained images are recorded in session history; audio is recorded later through its transcript and/or audio_retention. retain_images_every_n=1 records every image, while larger values keep the first image and then one of every N; retain_images_max bounds how many stay recorded, evicting the oldest first. Sending an image is gated on the model profile’s image-input support and raises UserError when it is unsupported.

send() accepts session content only. Turn-control verbs (CommitAudio, ClearAudio, CreateResponse, CancelResponse, TruncateOutput) are driven through the dedicated methods (commit_audio(), clear_audio(), create_response(), interrupt()), and ToolResult is sent by the session itself as each tool completes (see _execute_tool) — neither is accepted here.

Returns

None

enqueue
def enqueue(
    *content: EnqueueContent,
    priority: PendingMessagePriority = 'asap',
) -> str | None

Enqueue content to be delivered to the model when the session is ready for it.

Use this from code driving the session. For tools, use RunContext.enqueue. Unlike send(), which is delivered immediately even while the model is speaking, enqueued content waits for the appropriate turn boundary and triggers a model response when delivered.

Returns

str | None — The enqueue_id of the queued message, or None when there was nothing to enqueue.

Parameters

*content : EnqueueContent Default: ()

One or more EnqueueContent items. Realtime delivery supports text parts and SystemPromptParts, which are joined into one user turn. System parts use the <system>…</system> convention to indicate that the text is not the person speaking; that marks where it came from, not that it should be handled silently. The model still gets a turn and decides whether to reply, call a tool, or move on. To add context without prompting a turn at all, use send(..., respond=False). The delivered turn is recorded in history as a UserPromptPart. Calling with no positional arguments is a no-op.

priority : PendingMessagePriority Default: 'asap'

When to deliver: 'asap' (default) — as soon as no reply is being generated; a reply already in progress is allowed to finish first. 'when_idle' — only once the model is idle: no response is in flight and all 'asap' items have been delivered.

Raises
  • UserError — If the session has not been entered or is closed, or the content cannot be delivered over the live input channel.
send_audio

@async

def send_audio(data: bytes | AsyncIterable[bytes]) -> None

Stream mono PCM16 audio to the model: a single chunk, or an async iterable of chunks.

Given an async iterable — a microphone stream, a WebSocket receive loop — each chunk is forwarded as it arrives and the call returns when the iterable is exhausted, so a whole capture loop can be one task: asyncio.create_task(session.send_audio(microphone)). If the session is closed while consuming that iterable, the call returns cleanly at the next chunk the source yields. Cancel the task in application code if the source can stall indefinitely. Passing a single chunk to a session that is already closed still raises UserError.

Resample the audio to audio_input_sample_rate first (24 kHz on the OpenAI-protocol providers, 16 kHz on Gemini): raw bytes carry no rate, so the wrong one is heard as a chipmunk rather than reported.

With a reconnect policy, a chunk sent while a dropped connection is being re-dialed is discarded rather than raised, so a microphone task survives the reconnect. That includes a one-shot clip passed as a single chunk: resend it after the RealtimeSessionReconnectEvent if it matters.

Returns

None

commit_audio

@async

def commit_audio() -> None

Commit buffered input audio as a user turn (manual turn-taking / push-to-talk).

Returns

None

clear_audio

@async

def clear_audio() -> None

Discard buffered, uncommitted input audio.

Returns

None

create_response

@async

def create_response() -> None

Ask the model to respond now (manual turn-taking, after commit_audio).

If a response is already in flight, the request is held until it completes and is dropped if the user barges in. Returning does not mean the model has started speaking.

Returns

None

interrupt

@async

def interrupt(*, played_ms: int | None = None) -> None
def interrupt(*, played_bytes: int) -> bool

Barge-in: cancel the model’s in-progress response, optionally truncating its audio first.

This is server-side only — it stops generation and (when a playback position is given) syncs the provider’s transcript to what was actually heard. Generation outruns playback, so the reply being heard has often finished generating: the provider’s copy is still truncated, while the reply keeps its full transcript and complete state in history, which is append-only.

With played_ms, the caller owns all playback accounting: flushing locally buffered playback is the caller’s responsibility, and deciding whether to interrupt at all is too.

With played_bytes, the session owns the rest: it attributes the position to the current turn (adjusting for audio the stream dropped or that predates the subscription), discards buffered audio the user will never hear — chunks already queued, and the cancelled response’s stragglers as they arrive — then truncates and cancels, returning True. The cancel is left out while the provider has a speech segment open on a model whose own turn detection stops the response being spoken over, where a client cancel racing it can be applied to the next response; an interruption raised outside a speech segment always cancels. On a model without output truncation the unheard audio is still flushed and the response cancelled; only the provider-side transcript sync is unavailable. When everything emitted was already played there is nothing to cut off: a reply that has not reached its first audio chunk is cancelled untruncated, since it is about to speak over the user, and otherwise nothing is sent and False is returned — reporting an interruption anyway would make the provider discard part of a completed reply. This form needs exactly one stream_audio() iterator, kept for the session’s lifetime; with several (or none) the position is ambiguous and a UserError is raised — keep your own accounting and pass played_ms instead.

Returns

bool | None

Parameters

played_ms : int | None Default: None

Playback position in milliseconds from the start of the model’s current audio output. When given, the model’s output audio and its transcript are truncated to this point before the response is cancelled.

played_bytes : int | None Default: None

Playback position as the total raw PCM bytes actually played from the session’s single stream_audio() iterator. Mutually exclusive with played_ms.

__aiter__

@async

def __aiter__() -> AsyncIterator[RealtimeEvent]

Read translated events from the session queue without owning session resources.

Returns

AsyncIterator[RealtimeEvent]

RealtimeTurnCompleteEvent

The model exchange is over: generation and tool work are complete.

It is synthesized by the session once no tool calls are still running and no further response is in flight. Input transcription can finish after this event. On WebRTC sidebands, provider playback can also continue until RealtimeOutputSpeechEndEvent.

Attributes

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_turn_complete’] Default: 'realtime_turn_complete'

RealtimeInputSpeechStartEvent

The provider detected that the user started speaking.

Useful for barge-in: stop playing any buffered model audio when this arrives, since the model’s in-progress turn is being interrupted.

Reported by OpenAI, Azure OpenAI, and xAI. Gemini Live does not report speech onset.

Attributes

item_id

Provider id of the user input item this speech segment belongs to, when reported.

Type: str | None Default: None

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_input_speech_start’] Default: 'realtime_input_speech_start'

RealtimeResponseInterruptedEvent

The provider cut the model’s in-progress response short.

Arrives as soon as the provider interrupts, ahead of its response terminal, so it’s the point at which to flush buffered model audio.

Reported by Gemini Live, which interrupts server-side when it hears the user speak. The other providers report the user’s speech onset as RealtimeInputSpeechStartEvent and leave the cancellation to interrupt, so they never report this.

Attributes

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_response_interrupted’] Default: 'realtime_response_interrupted'

RealtimeInputSpeechEndEvent

The provider detected that the user stopped speaking.

Useful as a ‘processing’ indicator: the user’s turn has ended and the model is about to respond.

Attributes

item_id

Provider id of the user input item this speech segment belongs to, when reported.

Used to attach retained input audio (audio_retention='input_audio'/'all') to the right user turn when turns overlap, since transcripts for different items can finalize out of order.

Type: str | None Default: None

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_input_speech_end’] Default: 'realtime_input_speech_end'

RealtimeOutputSpeechStartEvent

The provider started playing the model’s audio to the listener.

Only reported where the provider, rather than your code, holds the audio on its way to the listener: on a WebRTC sideband the media flows browser ↔ provider, so the session never sees audio and this is its only signal that the model has become audible. An ordinary session owns the audio and knows when it starts playing it, so no provider reports this there.

This is about playback, not generation: the provider produces audio faster than it plays it, so this can arrive well after the audio itself was generated.

Attributes

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_output_speech_start’] Default: 'realtime_output_speech_start'

RealtimeOutputSpeechEndEvent

The provider stopped playing the model’s audio to the listener.

The counterpart to RealtimeOutputSpeechStartEvent, and the honest end of a spoken turn: because the provider generates audio far ahead of playing it, it is still talking long after RealtimeTurnCompleteEvent reports the response finished. Drive a “speaking” indicator from this pair rather than from turn completion.

Attributes

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_output_speech_end’] Default: 'realtime_output_speech_end'

RealtimeInputTranscriptionErrorEvent

The provider failed to transcribe a user audio input turn, but the session continues.

This is recoverable; item_id and content_index locate the affected user turn.

Attributes

message

Human-readable error message.

Type: str

type

Provider error category, if any.

Type: str | None Default: None

code

Provider error code, if any.

Type: str | None Default: None

item_id

Provider conversation-item ID for the affected user turn, when available.

Type: str | None Default: None

content_index

Content index within the affected user turn, when available.

Type: int | None Default: None

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_input_transcription_error’] Default: 'realtime_input_transcription_error'

RealtimeSessionReconnectEvent

The connection dropped and was automatically re-established; inspect state_restored for continuity.

Session configuration (instructions, tools, voice, …) is restored on every reconnect. Conversation state is restored either by the provider’s native session resumption (Gemini Live when enabled, xAI Grok Voice, a stored OpenAI GPT-Live session) or by the session replaying its local history into the fresh server-side conversation (OpenAI/Azure OpenAI, an unstored GPT-Live session).

Attributes

state_restored

Whether the reconnect carried the conversation through without cutting a turn off, regardless of mechanism — native provider resumption or a local-history replay.

True means nothing in flight was lost: the provider either resumed the in-flight response itself (Gemini Live, xAI Grok Voice) or there was no turn in progress when the connection dropped.

False means a turn the drop interrupted was settled before continuing — its partial reply is recorded as an interrupted response and any running tool calls as cancelled returns — so all_messages() stays a coherent history. Finalized turns from before the drop survive where the provider restores them (the OpenAI/Azure OpenAI local replay) and are lost where it does not; either way, treat the interrupted turn as over and expect the model to stay quiet until the next input.

Type: bool Default: False

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_session_reconnect’] Default: 'realtime_session_reconnect'

RealtimeSessionErrorEvent

A provider-reported error occurred in the session.

Attributes

message

Human-readable error message.

Type: str

type

Provider error category, e.g. invalid_request_error or server_error.

Type: str | None Default: None

code

Provider error code, if any.

Type: str | None Default: None

recoverable

Whether the session can continue. A protocol error is recoverable; a dropped connection is not.

Type: bool Default: True

event_kind

Event type identifier, used as a discriminator.

Type: Literal[‘realtime_session_error’] Default: 'realtime_session_error'

infer_realtime_model

def infer_realtime_model(
    model: KnownRealtimeModelName | str,
    provider_factory: Callable[[str], Provider[Any]] | None = None,
) -> RealtimeModel

Infer a realtime model from a provider:model identifier.

The provider is one of openai, azure, xai, google (the Gemini Developer API), or google-cloud (Vertex AI) — e.g. openai:gpt-realtime — or a Pydantic AI Gateway route (gateway/openai:gpt-realtime, gateway/google:gemini-live-2.5-flash), which connects through the gateway’s built-in provider — the provider string is passed to the realtime model as its provider, so authentication and the base URL come from gateway_provider.

Returns

RealtimeModel

Parameters

model : KnownRealtimeModelName | str

The realtime model identifier, in the format provider:model.

provider_factory : Callable[[str], Provider[Any]] | None Default: None

Function that instantiates the provider, called with the provider prefix exactly as written ('azure', 'gateway/google', …) — the counterpart of the same parameter on infer_model, for credentials that don’t come from the environment. The provider it returns is used as-is. When omitted, each realtime model resolves the prefix itself (e.g. azure through AzureProvider.for_realtime()).

AudioRetention

How much audio a RealtimeSession retains in its history.

Values other than 'transcript_only' are additive: transcripts are always kept, and the named audio is retained alongside them.

  • 'transcript_only' (default): keep only transcripts; drop all audio bytes.
  • 'input_audio': also retain the user’s spoken audio.
  • 'output_audio': also retain the model’s spoken audio.
  • 'all': retain both sides’ audio.

Retained audio is stored on the SpeechPart’s audio as WAV BinaryContent. Live audio deltas remain raw PCM. Retained input audio is split only at boundaries the provider reports. OpenAI, Azure, and xAI report the end of each detected speech segment, so each user part normally contains audio sent since the preceding speech-end boundary through the current one. This can include inter-turn microphone input. Gemini does not report speech-end boundaries, so its user part contains everything sent since the preceding response completed through the current response completion, including silence sent while the model is responding. Retention records the microphone stream only; it does not mix the model’s output audio into the user’s part unless that output is present in the microphone input itself.

How much retained audio a session keeps is bounded by retain_audio_max_seconds on AgentRealtime.session; the oldest is evicted first, keeping its transcript.

Default: TypeAliasType('AudioRetention', Literal['transcript_only', 'input_audio', 'output_audio', 'all'])

AsyncToolCallMode

Whether a realtime model keeps the conversation going while a tool call runs.

  • 'never': the model waits for the tool’s result before it says anything else.
  • 'optional': it’s up to the session, via the async_tool_calls setting.
  • 'always': the model keeps talking, and answering the user, while a tool runs; it has no mode that waits.

Default: TypeAliasType('AsyncToolCallMode', Literal['never', 'optional', 'always'])

RealtimeSessionInput

The content types a caller feeds into RealtimeSession.send.

Session content only, in the shared message vocabulary: a str is a complete text turn, and BinaryContent carries an image frame, WAV audio (unwrapped to raw PCM before it is streamed, matching the history-seeding path), or a raw PCM chunk (media_type='audio/pcm'). The session normalizes these before forwarding them to the connection. Turn-control verbs (CommitAudio, ClearAudio, CreateResponse, CancelResponse, TruncateOutput) are connection-level vocabulary driven through the dedicated RealtimeSession methods (commit_audio(), clear_audio(), create_response(), interrupt()), and ToolResult is sent by the session itself when a tool completes — neither is accepted by send().

Default: TypeAliasType('RealtimeSessionInput', 'str | BinaryContent')

RealtimeEvent

Union of events yielded by RealtimeSession.

This is a strict subset of AgentStreamEvent.

Content is streamed as the shared PartStartEvent / PartDeltaEvent / PartEndEvent events (carrying SpeechParts and other shared message parts, including TextPart, ToolCallPart, and NativeToolReturnPart), tool execution as FunctionToolCallEvent / FunctionToolResultEvent, deferred calls and their resolution as DeferredToolRequestsEvent / DeferredToolResultsEvent, enqueued-message delivery as EnqueuedMessagesEvent, and the rest as realtime control-plane events.

Default: TypeAliasType('RealtimeEvent', PartStartEvent | PartDeltaEvent | PartEndEvent | FunctionToolCallEvent | FunctionToolResultEvent | DeferredToolRequestsEvent | DeferredToolResultsEvent | EnqueuedMessagesEvent | RealtimeTurnCompleteEvent | RealtimeInputSpeechStartEvent | RealtimeResponseInterruptedEvent | RealtimeInputSpeechEndEvent | RealtimeOutputSpeechStartEvent | RealtimeOutputSpeechEndEvent | RealtimeInputTranscriptionErrorEvent | RealtimeSessionReconnectEvent | RealtimeSessionErrorEvent)

KnownRealtimeTranscriptionModelName

Known values for the OpenAI-protocol models’ input_transcription_model, pinned by a provider sync test.

'auto' is the sentinel that resolves to the provider’s recommended transcription model; the rest are concrete model ids. The values span providers, so an id valid for one provider (e.g. 'grok-transcribe' for xAI) is rejected by another at connect time. The field also accepts any other str, so a newer id not listed here still works — this is just an autocomplete aid, like KnownModelName.

Default: TypeAliasType('KnownRealtimeTranscriptionModelName', Literal['auto', 'whisper-1', 'gpt-4o-transcribe', 'gpt-4o-mini-transcribe', 'gpt-realtime-whisper', 'gpt-live-transcribe', 'gpt-transcribe', 'grok-transcribe', 'azure-speech', 'mai-transcribe'])

RealtimeModelProfileSpec

What a user may pass as a realtime model’s profile=, mirroring ModelProfileSpec.

Either a partial RealtimeModelProfile merged over the resolved profile, or a callable taking the resolved profile and returning the one to use, for full control.

Default: TypeAliasType('RealtimeModelProfileSpec', 'RealtimeModelProfile | Callable[[RealtimeModelProfile], RealtimeModelProfile]')

KnownRealtimeModelName

Known realtime model identifiers, surfaced for autocomplete and pinned to provider aliases by a sync test.

Default: TypeAliasType('KnownRealtimeModelName', Literal['openai:gpt-live-1', 'openai:gpt-realtime', 'openai:gpt-realtime-2.1', 'openai:gpt-realtime-2.1-mini', 'azure:gpt-realtime', 'xai:grok-voice-latest', 'xai:grok-voice-think-fast-2.0', 'google:gemini-2.5-flash-native-audio-latest', 'google:gemini-3.1-flash-live-preview', 'google:gemini-3.8-live', 'google:gemini-3.8-live-extended-thinking'])