From 4ca26e88a5d27a35e4fdf566b652611674ad39d8 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 23 May 2026 01:29:30 +0000 Subject: [PATCH] fix(gemini realtime): emit function_call_arguments.delta before .done Gemini delivers the full function-call arguments in a single toolCall frame. The OpenAI Realtime spec orders the streaming events as output_item.added -> function_call_arguments.delta(+) -> function_call_arguments.done -> output_item.done. Emit a single delta carrying the complete arguments string before the matching .done so spec-compliant SDK clients that accumulate deltas and gate finalisation on at least one delta arriving do not stall on Gemini tool calls. --- .../llms/gemini/realtime/transformation.py | 18 +++++++++++++ .../test_gemini_realtime_transformation.py | 25 +++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/litellm/llms/gemini/realtime/transformation.py b/litellm/llms/gemini/realtime/transformation.py index 2a40bab7657..abeb849b05b 100644 --- a/litellm/llms/gemini/realtime/transformation.py +++ b/litellm/llms/gemini/realtime/transformation.py @@ -1410,6 +1410,24 @@ class GeminiRealtimeConfig(BaseRealtimeConfig): }, ) ) + # response.function_call_arguments.delta — Gemini delivers + # the full arguments string in a single toolCall frame + # rather than streaming partial chunks, so emit one delta + # carrying the complete payload before the matching + # ``.done`` event. Spec-compliant OpenAI Realtime SDK + # clients accumulate ``delta.delta`` and rely on at least + # one delta before ``.done``. + returned_message.append( + { + "type": "response.function_call_arguments.delta", + "event_id": f"event_{uuid.uuid4()}", + "response_id": current_response_id, + "item_id": item_id, + "output_index": idx, + "call_id": tool_call["call_id"], + "delta": tool_call["arguments"], + } + ) # response.function_call_arguments.done returned_message.append(tool_call) # response.output_item.done diff --git a/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py b/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py index 1d0ae30f275..d1e596f1359 100644 --- a/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py +++ b/tests/test_litellm/llms/gemini/realtime/test_gemini_realtime_transformation.py @@ -735,8 +735,8 @@ def test_gemini_tool_call_emits_response_created_preamble(): ) responses = result["response"] - # Should have: response.created, output_item.added, function_call_arguments.done, output_item.done, conversation.item.created, response.done - assert len(responses) >= 6 + # Should have: response.created, output_item.added, function_call_arguments.delta, function_call_arguments.done, output_item.done, conversation.item.created, response.done + assert len(responses) >= 7 assert responses[0]["type"] == "response.created" assert "response" in responses[0] assert responses[0]["response"]["status"] == "in_progress" @@ -749,17 +749,20 @@ def test_gemini_tool_call_emits_response_created_preamble(): assert responses[1]["type"] == "response.output_item.added" assert responses[1]["item"]["type"] == "function_call" assert responses[1]["item"]["status"] == "in_progress" - assert responses[2]["type"] == "response.function_call_arguments.done" - assert responses[3]["type"] == "response.output_item.done" - assert responses[3]["item"]["type"] == "function_call" - assert responses[3]["item"]["status"] == "completed" - assert responses[4]["type"] == "conversation.item.created" + assert responses[2]["type"] == "response.function_call_arguments.delta" + assert responses[2]["call_id"] == "call_123" + assert responses[2]["delta"] == responses[3]["arguments"] + assert responses[3]["type"] == "response.function_call_arguments.done" + assert responses[4]["type"] == "response.output_item.done" assert responses[4]["item"]["type"] == "function_call" assert responses[4]["item"]["status"] == "completed" - assert responses[5]["type"] == "response.done" - assert responses[5]["response"]["status"] == "completed" - assert len(responses[5]["response"]["output"]) == 1 - assert responses[5]["response"]["output"][0]["type"] == "function_call" + assert responses[5]["type"] == "conversation.item.created" + assert responses[5]["item"]["type"] == "function_call" + assert responses[5]["item"]["status"] == "completed" + assert responses[6]["type"] == "response.done" + assert responses[6]["response"]["status"] == "completed" + assert len(responses[6]["response"]["output"]) == 1 + assert responses[6]["response"]["output"][0]["type"] == "function_call" assert result["current_output_item_id"] is None assert result["current_response_id"] is None