Skip to content

Commit 2cf367f

Browse files
committed
Fix Azure realtime beta session shape
1 parent 4629100 commit 2cf367f

4 files changed

Lines changed: 62 additions & 4 deletions

File tree

litellm/litellm_core_utils/realtime_streaming.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(
4646
model: str = "",
4747
user_api_key_dict: Optional[Any] = None,
4848
request_data: Optional[Dict] = None,
49+
backend_uses_beta_protocol: Optional[bool] = None,
4950
):
5051
self.websocket = websocket
5152
self.backend_ws = backend_ws
@@ -58,6 +59,11 @@ def __init__(
5859

5960
# Detect whether the client is explicitly opting into the beta protocol.
6061
self._client_wants_beta = self._detect_beta_header(websocket)
62+
self._backend_uses_beta_protocol = (
63+
self._client_wants_beta
64+
if backend_uses_beta_protocol is None
65+
else backend_uses_beta_protocol
66+
)
6167

6268
_logged_real_time_event_types = litellm.logged_real_time_event_types
6369

@@ -261,7 +267,7 @@ async def _send_to_backend(self, message: str) -> None:
261267

262268
def _make_disable_auto_response_message(self) -> str:
263269
"""Return a session.update that disables VAD auto-response."""
264-
if self._client_wants_beta:
270+
if self._backend_uses_beta_protocol:
265271
session: Dict[str, Any] = {
266272
"turn_detection": {"create_response": False},
267273
}
@@ -821,7 +827,10 @@ async def client_ack_messages(self):
821827
# GA compatibility: remap beta-style session fields only when
822828
# the upstream is in GA mode. Beta upstreams expect the flat
823829
# session shape unchanged.
824-
if msg_type == "session.update" and not self._client_wants_beta:
830+
if (
831+
msg_type == "session.update"
832+
and not self._backend_uses_beta_protocol
833+
):
825834
session = msg_obj.get("session", {})
826835
if isinstance(session, dict):
827836
session = self._remap_beta_session_to_ga(session)

litellm/llms/azure/realtime/handler.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ async def async_realtime(
8989

9090
if api_base is None:
9191
raise ValueError("api_base is required for Azure OpenAI calls")
92-
if api_version is None and (
92+
backend_uses_beta_protocol = (
9393
realtime_protocol is None or realtime_protocol.upper() not in ("GA", "V1")
94-
):
94+
)
95+
if api_version is None and backend_uses_beta_protocol:
9596
raise ValueError("api_version is required for Azure OpenAI calls")
9697

9798
url = self._construct_url(
@@ -114,6 +115,7 @@ async def async_realtime(
114115
logging_obj,
115116
user_api_key_dict=user_api_key_dict,
116117
request_data={"litellm_metadata": litellm_metadata or {}},
118+
backend_uses_beta_protocol=backend_uses_beta_protocol,
117119
)
118120
await realtime_streaming.bidirectional_forward()
119121

tests/test_litellm/litellm_core_utils/test_realtime_streaming.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,45 @@ async def test_client_ack_messages_keeps_beta_session_shape_for_beta_clients():
189189
assert "audio" not in session
190190

191191

192+
@pytest.mark.asyncio
193+
async def test_client_ack_messages_keeps_beta_session_shape_for_beta_backend():
194+
client_ws = MagicMock()
195+
session_update = json.dumps(
196+
{
197+
"type": "session.update",
198+
"session": {
199+
"modalities": ["audio", "text"],
200+
"voice": "alloy",
201+
"turn_detection": {"create_response": False},
202+
},
203+
}
204+
)
205+
client_ws.receive_text = AsyncMock(
206+
side_effect=[
207+
session_update,
208+
Exception("connection closed"),
209+
]
210+
)
211+
backend_ws = MagicMock()
212+
backend_ws.send = AsyncMock()
213+
logging_obj = MagicMock()
214+
logging_obj.pre_call = MagicMock()
215+
streaming = RealTimeStreaming(
216+
client_ws, backend_ws, logging_obj, backend_uses_beta_protocol=True
217+
)
218+
219+
await streaming.client_ack_messages()
220+
221+
sent_to_backend = json.loads(backend_ws.send.call_args_list[0].args[0])
222+
session = sent_to_backend["session"]
223+
assert session["modalities"] == ["audio", "text"]
224+
assert session["voice"] == "alloy"
225+
assert session["turn_detection"] == {"create_response": False}
226+
assert "type" not in session
227+
assert "output_modalities" not in session
228+
assert "audio" not in session
229+
230+
192231
def test_translate_event_to_beta_renames_delta_types():
193232
ev = RealTimeStreaming._translate_event_to_beta(
194233
{"type": "response.output_audio.delta", "delta": "abc", "event_id": "e1"}

tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ async def __aexit__(self, exc_type, exc, tb):
247247
assert "model=gpt-4o-realtime-preview" in called_url
248248
assert "api-version" not in called_url
249249
assert "deployment" not in called_url
250+
assert (
251+
mock_realtime_streaming.call_args.kwargs["backend_uses_beta_protocol"]
252+
is False
253+
)
250254

251255

252256
@pytest.mark.asyncio
@@ -419,3 +423,7 @@ async def __aexit__(self, exc_type, exc, tb):
419423
called_url = mock_ws_connect.call_args[0][0]
420424
assert "/openai/realtime?" in called_url
421425
assert "/openai/v1/realtime" not in called_url
426+
assert (
427+
mock_realtime_streaming.call_args.kwargs["backend_uses_beta_protocol"]
428+
is True
429+
)

0 commit comments

Comments
 (0)