mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(gemini realtime): tolerate sibling-only frames (e.g. standalone usageMetadata)
A Gemini Live frame that contains only metadata keys outside
_KNOWN_GEMINI_TOP_LEVEL_KEYS (e.g. a bare {"usageMetadata": {...}}
emitted between turns) leaves returned_message empty after the
transform loop and was tripping the 'Unknown message type' guard,
which raised ValueError and terminated the WebSocket session.
Treat such frames as no-ops and return the unchanged state instead.
This commit is contained in:
parent
1e4f86e19a
commit
84791fc3f5
2 changed files with 57 additions and 0 deletions
|
|
@ -1528,6 +1528,23 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
else:
|
||||
raise ValueError(f"Unknown openai event: {openai_event}")
|
||||
if len(returned_message) == 0:
|
||||
# A frame whose only top-level keys are sibling metadata (e.g.
|
||||
# a standalone ``{"usageMetadata": {...}}`` emitted by Gemini
|
||||
# Live between turns) is not an error — there is just nothing
|
||||
# to forward to the OpenAI-shaped client. Returning the
|
||||
# unchanged state keeps the WebSocket alive; raising would
|
||||
# terminate the session for a benign no-op frame.
|
||||
if not any(key in _KNOWN_GEMINI_TOP_LEVEL_KEYS for key in json_message):
|
||||
return {
|
||||
"response": returned_message,
|
||||
"current_output_item_id": current_output_item_id,
|
||||
"current_response_id": current_response_id,
|
||||
"current_delta_chunks": current_delta_chunks,
|
||||
"current_conversation_id": current_conversation_id,
|
||||
"current_item_chunks": current_item_chunks,
|
||||
"current_delta_type": current_delta_type,
|
||||
"session_configuration_request": session_configuration_request,
|
||||
}
|
||||
if isinstance(message, bytes):
|
||||
message_str = message.decode("utf-8", errors="replace")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1145,3 +1145,43 @@ def test_gemini_tool_call_id_to_name_evicts_oldest_when_capped():
|
|||
"call_6",
|
||||
"call_7",
|
||||
]
|
||||
|
||||
|
||||
def test_gemini_standalone_usage_metadata_does_not_crash_websocket():
|
||||
"""A Gemini frame containing only sibling metadata (e.g. a standalone
|
||||
``usageMetadata`` block emitted between turns) must not trip the
|
||||
``Unknown message type`` guard — that would terminate the WebSocket
|
||||
session on a benign no-op frame."""
|
||||
config = GeminiRealtimeConfig()
|
||||
logging_obj = MagicMock()
|
||||
logging_obj.litellm_trace_id = "trace_usage_only"
|
||||
|
||||
result = config.transform_realtime_response(
|
||||
json.dumps(
|
||||
{
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 12,
|
||||
"responseTokenCount": 34,
|
||||
"totalTokenCount": 46,
|
||||
}
|
||||
}
|
||||
),
|
||||
"gemini-2.5-flash",
|
||||
logging_obj,
|
||||
realtime_response_transform_input={
|
||||
"session_configuration_request": None,
|
||||
"current_output_item_id": "item_existing",
|
||||
"current_response_id": "resp_existing",
|
||||
"current_conversation_id": "conv_existing",
|
||||
"current_delta_chunks": [],
|
||||
"current_item_chunks": [],
|
||||
"current_delta_type": None,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["response"] == []
|
||||
# State must be returned unchanged so subsequent frames continue the
|
||||
# in-flight response correctly.
|
||||
assert result["current_output_item_id"] == "item_existing"
|
||||
assert result["current_response_id"] == "resp_existing"
|
||||
assert result["current_conversation_id"] == "conv_existing"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue