mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
fix(gemini): tolerate generationComplete with no content deltas in realtime bridge
A Gemini Live generationComplete arriving before any modelTurn deltas (toolCall-only
or empty generations) raised ValueError('Unexpected delta type: None'), and the
caller dropped the entire backend frame. This fired on every gemini/vertex realtime
tool-call turn and intermittently stalled the vertex server-VAD path (LIT-4482).
map_openai_event now returns None for that case and the transform marks the frame
handled instead of raising.
This commit is contained in:
parent
5961c173e1
commit
154aeeba27
2 changed files with 43 additions and 1 deletions
|
|
@ -1103,7 +1103,11 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
key: str,
|
||||
value: Any,
|
||||
current_delta_type: Optional[ALL_DELTA_TYPES],
|
||||
) -> Union[OpenAIRealtimeEventTypes, ResponsesAPIStreamEvents]:
|
||||
) -> Union[OpenAIRealtimeEventTypes, ResponsesAPIStreamEvents, None]:
|
||||
"""Map a Gemini top-level key to the OpenAI event it produces. Returns None
|
||||
for a ``generationComplete`` with no preceding content deltas (a toolCall-only
|
||||
or empty generation): there is no text/audio "done" to emit for it, and
|
||||
raising here used to make the caller drop the entire backend frame."""
|
||||
if isinstance(value, dict):
|
||||
model_turn_event = value.get("modelTurn")
|
||||
generation_complete_event = value.get("generationComplete")
|
||||
|
|
@ -1114,6 +1118,8 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
if model_turn_event: # check if model turn event
|
||||
openai_event = self.map_model_turn_event(model_turn_event)
|
||||
elif generation_complete_event:
|
||||
if current_delta_type is None:
|
||||
return None
|
||||
openai_event = self.map_generation_complete_event(delta_type=current_delta_type)
|
||||
else:
|
||||
# Check if this key or any nested key matches our mapping. Use a
|
||||
|
|
@ -1237,6 +1243,7 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
server_content_handled = False
|
||||
|
||||
tool_call_handled = False
|
||||
generation_complete_handled = False
|
||||
for key, value in list(json_message.items()): # snapshot: handlers may mutate json_message
|
||||
# Skip sibling metadata keys (e.g. ``usageMetadata``) that can
|
||||
# accompany a primary payload like ``toolCall`` or ``serverContent``.
|
||||
|
|
@ -1255,6 +1262,9 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
value=value,
|
||||
current_delta_type=current_delta_type,
|
||||
)
|
||||
if openai_event is None:
|
||||
generation_complete_handled = True
|
||||
continue
|
||||
|
||||
if openai_event == OpenAIRealtimeEventTypes.SESSION_CREATED:
|
||||
transformed_message = self.transform_session_created_event(
|
||||
|
|
@ -1498,6 +1508,7 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
for key in json_message
|
||||
if key in _KNOWN_GEMINI_TOP_LEVEL_KEYS
|
||||
and not (key == "serverContent" and server_content_handled)
|
||||
and not (key == "serverContent" and generation_complete_handled)
|
||||
and not (key == "toolCall" and tool_call_handled)
|
||||
]
|
||||
standalone_usage_metadata = json_message.get("usageMetadata")
|
||||
|
|
|
|||
|
|
@ -311,6 +311,37 @@ def test_gemini_realtime_transformation_generation_complete():
|
|||
assert contains_audio_done_event, "Expected audio done event"
|
||||
|
||||
|
||||
def test_gemini_realtime_transformation_generation_complete_without_deltas():
|
||||
config = GeminiRealtimeConfig()
|
||||
|
||||
session_configuration_request_str = json.dumps(
|
||||
{
|
||||
"setup": {
|
||||
"model": "gemini-1.5-flash",
|
||||
"generationConfig": {"responseModalities": ["AUDIO"]},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
result = config.transform_realtime_response(
|
||||
json.dumps({"serverContent": {"generationComplete": True}}),
|
||||
"gemini-1.5-flash",
|
||||
MagicMock(),
|
||||
realtime_response_transform_input={
|
||||
"session_configuration_request": session_configuration_request_str,
|
||||
"current_output_item_id": None,
|
||||
"current_response_id": None,
|
||||
"current_conversation_id": None,
|
||||
"current_delta_chunks": [],
|
||||
"current_item_chunks": [],
|
||||
"current_delta_type": None,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["response"] == []
|
||||
assert result["current_delta_type"] is None
|
||||
|
||||
|
||||
def test_gemini_3_1_flash_live_preview_model_cost_map_entry():
|
||||
for key in (
|
||||
"gemini-3.1-flash-live-preview",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue