mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(gemini realtime): empty toolCall must not terminate the WebSocket
If Gemini sends a toolCall whose functionCalls list is empty (or absent), the previous `continue` left returned_message empty and the "Unknown message type" guard fired, killing the WebSocket session. Return a normal (empty) result instead so the session keeps going.
This commit is contained in:
parent
c59260fc70
commit
0780e5f69f
2 changed files with 45 additions and 5 deletions
|
|
@ -1238,12 +1238,24 @@ class GeminiRealtimeConfig(BaseRealtimeConfig):
|
|||
)
|
||||
returned_message.append(transformed_message)
|
||||
elif openai_event == ResponsesAPIStreamEvents.FUNCTION_CALL_ARGUMENTS_DONE:
|
||||
# Handle toolCall from Gemini. Skip entirely if there are no
|
||||
# function calls in the payload — emitting an orphaned
|
||||
# response.created/response.done pair with no output items
|
||||
# would confuse OpenAI-compatible clients.
|
||||
# Handle toolCall from Gemini. If the payload has no function
|
||||
# calls, emit nothing — an orphaned response.created/done pair
|
||||
# with no output items would confuse OpenAI-compatible clients.
|
||||
# Return rather than ``continue`` so a toolCall that is the
|
||||
# only key in the message doesn't leave ``returned_message``
|
||||
# empty and trip the "Unknown message type" guard below
|
||||
# (which would terminate the WebSocket session).
|
||||
if not value.get("functionCalls"):
|
||||
continue
|
||||
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 current_conversation_id is None:
|
||||
current_conversation_id = f"conv_{uuid.uuid4()}"
|
||||
|
|
|
|||
|
|
@ -826,6 +826,34 @@ def test_gemini_tool_call_resets_ids_for_post_tool_model_turn():
|
|||
)
|
||||
|
||||
|
||||
def test_gemini_empty_tool_call_does_not_crash_websocket():
|
||||
"""A toolCall payload with no functionCalls must not raise the
|
||||
'Unknown message type' guard — that would terminate the WebSocket session
|
||||
on what is at worst a benign no-op from Gemini."""
|
||||
config = GeminiRealtimeConfig()
|
||||
logging_obj = MagicMock()
|
||||
logging_obj.litellm_trace_id = "trace_empty_tool_call"
|
||||
|
||||
result = config.transform_realtime_response(
|
||||
json.dumps({"toolCall": {"functionCalls": []}}),
|
||||
"gemini-2.5-flash",
|
||||
logging_obj,
|
||||
realtime_response_transform_input={
|
||||
"session_configuration_request": None,
|
||||
"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_response_id"] is None
|
||||
assert result["current_output_item_id"] is None
|
||||
|
||||
|
||||
def test_gemini_function_call_output_includes_name():
|
||||
"""Verify function_call_output includes name field from stored mapping."""
|
||||
config = GeminiRealtimeConfig()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue