From d3490859a41f8c5088d6bfc4a9a6e7a7164a338c Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 23 May 2026 01:52:45 +0000 Subject: [PATCH] fix(ci): restore guardrail injection on duplicate session.created and cast realtime delta event - Re-enable the one-time guardrail turn_detection update on duplicate session.created. `_maybe_send_guardrail_turn_detection_update` is already idempotent via `_guardrail_turn_detection_update_sent`, so the previous guard was unnecessary and broke the deferred-setup path where the synthetic session.created is emitted by llm_http_handler outside this loop (no prior chance to inject). - Cast the response.function_call_arguments.delta dict appended to `returned_message: List[OpenAIRealtimeEvents]` so mypy is satisfied. --- .../litellm_core_utils/realtime_streaming.py | 20 ++++++++---------- .../llms/gemini/realtime/transformation.py | 21 +++++++++++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/litellm/litellm_core_utils/realtime_streaming.py b/litellm/litellm_core_utils/realtime_streaming.py index 585a9bedf25..3d7871eac41 100644 --- a/litellm/litellm_core_utils/realtime_streaming.py +++ b/litellm/litellm_core_utils/realtime_streaming.py @@ -533,7 +533,6 @@ class RealTimeStreaming: is_session_created_event = ( isinstance(event, dict) and event.get("type") == "session.created" ) - is_first_session_created_event = False if is_session_created_event: if self._session_created_sent_to_client: # A synthetic session.created (with placeholder defaults) was @@ -546,17 +545,16 @@ class RealTimeStreaming: event = {**event, "type": "session.updated"} else: self._session_created_sent_to_client = True - is_first_session_created_event = True event_str = json.dumps(event) - ## For audio/VAD guardrail path: forward the first session.created - ## first, then inject the guardrail turn-detection update. Skip - ## this path for duplicate session.created events that we just - ## retyped to `session.updated` above — the guardrail injection - ## was already handled on the original session.created. - if ( - is_first_session_created_event - and self._has_audio_transcription_guardrails() - ): + ## For audio/VAD guardrail path: forward the (possibly retyped) + ## session.created first, then invoke the one-time guardrail + ## turn-detection update. ``_maybe_send_guardrail_turn_detection_update`` + ## is idempotent (gated by ``_guardrail_turn_detection_update_sent``), + ## so duplicate session.created events — including those emitted + ## after a synthetic session.created from ``llm_http_handler`` in + ## deferred-setup mode — still get a single chance to inject the + ## update if a prior attempt was dropped by the provider transform. + if is_session_created_event and self._has_audio_transcription_guardrails(): self.store_message(event_str) await self.websocket.send_text(event_str) await self._maybe_send_guardrail_turn_detection_update() diff --git a/litellm/llms/gemini/realtime/transformation.py b/litellm/llms/gemini/realtime/transformation.py index abeb849b05b..c31bf4578a0 100644 --- a/litellm/llms/gemini/realtime/transformation.py +++ b/litellm/llms/gemini/realtime/transformation.py @@ -1418,15 +1418,18 @@ class GeminiRealtimeConfig(BaseRealtimeConfig): # clients accumulate ``delta.delta`` and rely on at least # one delta before ``.done``. returned_message.append( - { - "type": "response.function_call_arguments.delta", - "event_id": f"event_{uuid.uuid4()}", - "response_id": current_response_id, - "item_id": item_id, - "output_index": idx, - "call_id": tool_call["call_id"], - "delta": tool_call["arguments"], - } + cast( + OpenAIRealtimeEvents, + { + "type": "response.function_call_arguments.delta", + "event_id": f"event_{uuid.uuid4()}", + "response_id": current_response_id, + "item_id": item_id, + "output_index": idx, + "call_id": tool_call["call_id"], + "delta": tool_call["arguments"], + }, + ) ) # response.function_call_arguments.done returned_message.append(tool_call)