From 459c1973b4a191aa319248c67e48622587cdc412 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 22 May 2026 22:10:57 +0000 Subject: [PATCH] fix(gemini realtime): deep-merge automaticActivityDetection on follow-up session.update The follow-up setup merge already deep-merged generationConfig and realtimeInputConfig, but realtimeInputConfig.automaticActivityDetection itself is a nested dict. A partial VAD update (e.g. the guardrail-injected disabled=True from create_response=False) silently dropped unrelated knobs such as silenceDurationMs and prefixPaddingMs from the original setup. Deep-merge that block too so partial overrides only touch the fields they specify. --- .../llms/gemini/realtime/transformation.py | 21 +++++++++- .../test_gemini_realtime_transformation.py | 40 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/litellm/llms/gemini/realtime/transformation.py b/litellm/llms/gemini/realtime/transformation.py index 12b7f877579..eef6c503dbd 100644 --- a/litellm/llms/gemini/realtime/transformation.py +++ b/litellm/llms/gemini/realtime/transformation.py @@ -368,10 +368,29 @@ class GeminiRealtimeConfig(BaseRealtimeConfig): if isinstance(original_realtime_input_config, dict) and isinstance( new_realtime_input_config, dict ): - follow_up_setup["realtimeInputConfig"] = { + merged_realtime_input_config = { **original_realtime_input_config, **new_realtime_input_config, } + # Deep-merge ``automaticActivityDetection`` so a partial VAD + # update (e.g. the guardrail-injected ``disabled: True`` from + # ``create_response: False``) does not silently drop unrelated + # knobs like ``silenceDurationMs`` / ``prefixPaddingMs`` from + # the original setup. + original_automatic_activity_detection = original_realtime_input_config.get( + "automaticActivityDetection" + ) + new_automatic_activity_detection = new_realtime_input_config.get( + "automaticActivityDetection" + ) + if isinstance(original_automatic_activity_detection, dict) and isinstance( + new_automatic_activity_detection, dict + ): + merged_realtime_input_config["automaticActivityDetection"] = { + **original_automatic_activity_detection, + **new_automatic_activity_detection, + } + follow_up_setup["realtimeInputConfig"] = merged_realtime_input_config verbose_logger.debug( "Gemini Realtime: Forwarding session.update as follow-up setup" ) diff --git a/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py b/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py index 9db244834c8..c5f10075406 100644 --- a/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py +++ b/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py @@ -1047,3 +1047,43 @@ def test_gemini_follow_up_session_update_preserves_response_modalities_on_partia assert follow_up["generationConfig"]["responseModalities"] == ["AUDIO"] assert follow_up["generationConfig"]["maxOutputTokens"] == 2048 assert follow_up["generationConfig"]["temperature"] == 0.7 + + +def test_gemini_subsequent_session_update_preserves_automatic_activity_detection_subfields(): + """A follow-up turn_detection update that only sets ``create_response`` + (mapped to ``disabled``) must not drop ``silenceDurationMs`` / + ``prefixPaddingMs`` from the original ``automaticActivityDetection`` + block.""" + config = GeminiRealtimeConfig() + + original_setup = { + "setup": { + "model": "models/gemini-2.5-flash-native-audio", + "generationConfig": {"responseModalities": ["AUDIO"]}, + "realtimeInputConfig": { + "automaticActivityDetection": { + "disabled": False, + "silenceDurationMs": 500, + "prefixPaddingMs": 100, + } + }, + } + } + + session_update = { + "type": "session.update", + "session": {"turn_detection": {"create_response": False}}, + } + + messages = config.transform_realtime_request( + json.dumps(session_update), + "gemini-2.5-flash-native-audio", + session_configuration_request=json.dumps(original_setup), + ) + + automatic_activity_detection = json.loads(messages[0])["setup"][ + "realtimeInputConfig" + ]["automaticActivityDetection"] + assert automatic_activity_detection["disabled"] is True + assert automatic_activity_detection["silenceDurationMs"] == 500 + assert automatic_activity_detection["prefixPaddingMs"] == 100