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.
This commit is contained in:
mateo-berri 2026-05-22 22:10:57 +00:00
parent d56d875ea6
commit 459c1973b4
No known key found for this signature in database
2 changed files with 60 additions and 1 deletions

View file

@ -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"
)

View file

@ -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