fix(realtime): address P1 issues - guardrail timing and inputAudioTranscription default

- Remove early guardrail turn-detection update that consumed first setup slot
- Add inputAudioTranscription default in Gemini deferred-mode setup
- Add tests for both fixes

Made-with: Cursor
This commit is contained in:
Sameer Kankute 2026-05-22 17:44:04 +05:30
parent 817792c6cc
commit 86f5c21025
No known key found for this signature in database
4 changed files with 72 additions and 1 deletions

View file

@ -5167,7 +5167,6 @@ class BaseLLMHTTPHandler:
if synthetic_session is not None:
await websocket.send_text(json.dumps(synthetic_session))
realtime_streaming._session_created_sent_to_client = True
await realtime_streaming._maybe_send_guardrail_turn_detection_update()
verbose_logger.debug(
"Sent synthetic session.created to client to unblock connection"
)

View file

@ -239,6 +239,7 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
"generationConfig", {}
)
generation_config.setdefault("responseModalities", ["AUDIO"])
client_session_configuration_request.setdefault("inputAudioTranscription", {})
client_session_configuration_request["model"] = f"models/{model}"
gemini_setup_msg = json.dumps({"setup": client_session_configuration_request})
verbose_logger.debug(

View file

@ -1080,3 +1080,40 @@ async def test_duplicate_session_created_still_triggers_guardrail_turn_detection
sent_update = json.loads(streaming._send_to_backend.await_args_list[0].args[0])
assert sent_update["type"] == "session.update"
assert sent_update["session"]["turn_detection"]["create_response"] is False
@pytest.mark.asyncio
async def test_guardrail_update_respects_idempotency_flag():
"""Verify guardrail turn-detection update uses idempotency flag correctly."""
client_ws = AsyncMock()
backend_ws = MagicMock()
backend_ws.send = AsyncMock()
logging_obj = MagicMock()
logging_obj.litellm_trace_id = "trace_1"
logging_obj.async_success_handler = AsyncMock()
logging_obj.success_handler = MagicMock()
provider_config = MagicMock()
provider_config.transform_realtime_request = MagicMock(
side_effect=lambda msg, model, session_config: [msg]
)
streaming = RealTimeStreaming(
websocket=client_ws,
backend_ws=backend_ws,
logging_obj=logging_obj,
provider_config=provider_config,
model="gemini-2.5-flash",
)
streaming._has_audio_transcription_guardrails = MagicMock(return_value=True) # type: ignore[method-assign]
# First call should send the update
assert streaming._guardrail_turn_detection_update_sent is False
await streaming._maybe_send_guardrail_turn_detection_update()
assert streaming._guardrail_turn_detection_update_sent is True
assert backend_ws.send.await_count == 1
# Second call should be a no-op (idempotent)
await streaming._maybe_send_guardrail_turn_detection_update()
assert backend_ws.send.await_count == 1 # Still 1, not 2

View file

@ -584,3 +584,37 @@ def test_gemini_realtime_multi_tool_calls_have_unique_item_ids():
assert responses[0]["item_id"] != responses[1]["item_id"]
assert responses[0]["output_index"] == 0
assert responses[1]["output_index"] == 1
def test_gemini_session_update_includes_input_audio_transcription_default():
"""Verify _handle_session_update includes inputAudioTranscription default."""
config = GeminiRealtimeConfig()
session_update = {
"type": "session.update",
"session": {
"modalities": ["text", "audio"],
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
},
}
],
},
}
result = config.transform_realtime_request(
json.dumps(session_update),
"gemini-2.5-flash",
session_configuration_request=None,
)
assert len(result) == 1
setup = json.loads(result[0])
assert "setup" in setup
assert "inputAudioTranscription" in setup["setup"]
assert setup["setup"]["inputAudioTranscription"] == {}