Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,17 @@ def _oai_session_to_azure(session: RealtimeSessionCreateRequest) -> AzureSession


def _normalize_azure_client_event(event: dict[str, Any]) -> None:
"""In-place normalization of client event dicts for Azure compatibility.
"""In-place normalization of client event dicts for legacy Azure compatibility.

Azure uses "input_text" for all text content parts (including assistant messages),
while the new OpenAI API uses "output_text" for assistant content.
The legacy Azure Realtime API uses "text" for assistant content parts,
while the newer OpenAI API uses "output_text".
"""
item = event.get("item")
if item is None:
return
for content_part in item.get("content", ()):
if content_part.get("type") == "output_text":
content_part["type"] = "input_text"
content_part["type"] = "text"


@dataclass
Expand Down Expand Up @@ -344,10 +344,10 @@ def __init__(
http_session: aiohttp.ClientSession | None = None,
azure_deployment: str | None = None,
entra_token: str | None = None,
api_version: str | None = None,
max_session_duration: NotGivenOr[float | None] = NOT_GIVEN,
conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
temperature: NotGivenOr[float] = NOT_GIVEN, # deprecated, unused in v1
**kwargs: Any,
) -> None:
"""
Initialize a Realtime model client for OpenAI or Azure OpenAI.
Expand All @@ -368,7 +368,6 @@ def __init__(
http_session (aiohttp.ClientSession | None): Optional shared HTTP session.
azure_deployment (str | None): Azure deployment name. Presence of any Azure-specific option enables Azure mode.
entra_token (str | None): Azure Entra token auth (alternative to api_key).
api_version (str | None): Azure OpenAI API version appended as query parameter.
max_session_duration (float | None | NotGiven): Seconds before recycling the connection.
conn_options (APIConnectOptions): Retry/backoff and connection settings.
temperature (float | NotGiven): Deprecated; ignored by Realtime v1.
Expand Down Expand Up @@ -400,6 +399,17 @@ def __init__(
session = AgentSession(llm=model)
```
"""
api_version: str | None = kwargs.get("api_version") or os.getenv("OPENAI_API_VERSION")
if kwargs.get("api_version"):
logger.warning(
"The `api_version` parameter is deprecated and will be removed on April 30, 2026."
)
elif os.getenv("OPENAI_API_VERSION"):
logger.warning(
"The OPENAI_API_VERSION environment variable is deprecated and will be removed "
"on April 30, 2026."
)
Comment thread
tinalenguyen marked this conversation as resolved.

modalities = modalities if is_given(modalities) else ["text", "audio"]
super().__init__(
capabilities=llm.RealtimeCapabilities(
Expand Down Expand Up @@ -485,7 +495,6 @@ def with_azure(
*,
azure_deployment: str,
azure_endpoint: str | None = None,
api_version: str | None = None,
api_key: str | None = None,
entra_token: str | None = None,
base_url: str | None = None,
Expand All @@ -503,14 +512,14 @@ def with_azure(
http_session: aiohttp.ClientSession | None = None,
max_session_duration: NotGivenOr[float | None] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN, # deprecated, unused in v1
**kwargs: Any,
) -> RealtimeModel:
"""
Create a RealtimeModel configured for Azure OpenAI.

Args:
azure_deployment (str): Azure OpenAI deployment name.
azure_endpoint (str | None): Azure endpoint URL; if None, taken from AZURE_OPENAI_ENDPOINT.
api_version (str | None): Azure API version; if None, taken from OPENAI_API_VERSION.
api_key (str | None): Azure API key; if None, taken from AZURE_OPENAI_API_KEY. Omit if using `entra_token`.
entra_token (str | None): Azure Entra token for AAD auth. Provide instead of `api_key`.
base_url (str | None): Explicit base URL. Mutually exclusive with `azure_endpoint`. If provided, used as-is.
Expand All @@ -529,7 +538,7 @@ def with_azure(
RealtimeModel: Configured client for Azure OpenAI Realtime.

Raises:
ValueError: If credentials are missing, `api_version` is not provided, Azure endpoint cannot be determined, or both `base_url` and `azure_endpoint` are provided.
ValueError: If credentials are missing, Azure endpoint cannot be determined, or both `base_url` and `azure_endpoint` are provided.

Examples:
Azure usage with api-version 2024-10-01-preview:
Expand Down Expand Up @@ -582,19 +591,25 @@ def with_azure(
)
```
"""
if kwargs.get("api_version"):
logger.warning(
"The `api_version` parameter in `with_azure` is deprecated and will be removed "
"on April 30, 2026."
)
elif os.getenv("OPENAI_API_VERSION"):
logger.warning(
"The OPENAI_API_VERSION environment variable is deprecated and will be removed "
"on April 30, 2026."
)

api_key = api_key or os.getenv("AZURE_OPENAI_API_KEY")
if api_key is None and entra_token is None:
raise ValueError(
"Missing credentials. Please pass one of `api_key`, `entra_token`, "
"or the `AZURE_OPENAI_API_KEY` environment variable."
)

api_version = api_version or os.getenv("OPENAI_API_VERSION")
if api_version is None:
raise ValueError(
"Must provide either the `api_version` argument or the "
"`OPENAI_API_VERSION` environment variable"
)
api_version: str | None = kwargs.get("api_version") or os.getenv("OPENAI_API_VERSION")

if base_url is None:
azure_endpoint = azure_endpoint or os.getenv("AZURE_OPENAI_ENDPOINT")
Expand Down Expand Up @@ -724,17 +739,32 @@ def process_base_url(
parsed_url = urlparse(url)
query_params = parse_qs(parsed_url.query)

# ensure "/realtime" is added if the path is empty OR "/v1"
if not parsed_url.path or parsed_url.path.rstrip("/") in ["", "/v1", "/openai", "/openai/v1"]:
path = parsed_url.path.rstrip("/") + "/realtime"
path_stripped = parsed_url.path.rstrip("/")
if is_azure:
if path_stripped in ["", "/openai"]:
# newer Azure API (no api_version) uses /v1/realtime; legacy uses /realtime
path = path_stripped + ("/v1/realtime" if not api_version else "/realtime")
elif path_stripped == "/openai/v1":
path = "/openai/v1/realtime"
else:
path = parsed_url.path
else:
path = parsed_url.path
if not parsed_url.path or path_stripped in ["", "/v1", "/openai", "/openai/v1"]:
path = path_stripped + "/realtime"
else:
path = parsed_url.path

if is_azure:
query_params.pop("api-version", None) # remove from endpoint URL if present
if api_version:
# legacy Azure API: use api-version + deployment query params
query_params["api-version"] = [api_version]
if azure_deployment:
query_params["deployment"] = [azure_deployment]
if azure_deployment:
query_params["deployment"] = [azure_deployment]
else:
# newer Azure API: use azure_deployment as model query param
if "model" not in query_params and azure_deployment:
query_params["model"] = [azure_deployment]

else:
if "model" not in query_params:
Expand Down Expand Up @@ -832,7 +862,10 @@ async def _reconnect() -> None:
by_alias=True, exclude_unset=True, exclude_defaults=False
)

if self._realtime_model._opts.is_azure:
if (
self._realtime_model._opts.is_azure
and self._realtime_model._opts.api_version
):
_normalize_azure_client_event(ev)

self.emit("openai_client_event_queued", ev)
Expand Down Expand Up @@ -946,9 +979,12 @@ async def _send_task() -> None:
by_alias=True, exclude_unset=True, exclude_defaults=False
)

# Azure uses "input_text" for all content parts, while
# Azure uses "text" for assistant content parts, while
# the new API uses "output_text" for assistant content.
if self._realtime_model._opts.is_azure:
if (
self._realtime_model._opts.is_azure
and self._realtime_model._opts.api_version
):
_normalize_azure_client_event(msg)

self.emit("openai_client_event_queued", msg)
Expand Down Expand Up @@ -1110,7 +1146,8 @@ def _wrap_session_update(
and returns a dict (since AzureSessionUpdateEvent is not part of
the RealtimeClientEvent union).
"""
if self._realtime_model._opts.is_azure:
if self._realtime_model._opts.is_azure and self._realtime_model._opts.api_version:
# legacy Azure API: convert to old flat format
return AzureSessionUpdateEvent(
type="session.update",
session=_oai_session_to_azure(session),
Expand Down
Loading