@@ -478,3 +478,39 @@ def test_format_request_messages_cache_point_support():
478478 ]
479479
480480 assert result == expected
481+
482+
483+ @pytest .mark .asyncio
484+ async def test_stream_generates_tool_call_id_when_null (litellm_acompletion , model , agenerator , alist ):
485+ """Test that stream generates a tool call ID when LiteLLM returns null."""
486+ mock_tool_call = unittest .mock .Mock (index = 0 )
487+ mock_tool_call .id = None
488+ mock_tool_call .function .name = "test_tool"
489+ mock_tool_call .function .arguments = '{"arg": "value"}'
490+
491+ mock_delta = unittest .mock .Mock (content = None , tool_calls = [mock_tool_call ], reasoning_content = None )
492+
493+ mock_event_1 = unittest .mock .Mock (choices = [unittest .mock .Mock (finish_reason = None , delta = mock_delta )])
494+ mock_event_2 = unittest .mock .Mock (
495+ choices = [
496+ unittest .mock .Mock (
497+ finish_reason = "tool_calls" ,
498+ delta = unittest .mock .Mock (content = None , tool_calls = None , reasoning_content = None ),
499+ )
500+ ]
501+ )
502+
503+ litellm_acompletion .side_effect = unittest .mock .AsyncMock (return_value = agenerator ([mock_event_1 , mock_event_2 ]))
504+
505+ messages = [{"role" : "user" , "content" : [{"text" : "test" }]}]
506+ response = model .stream (messages )
507+ tru_events = await alist (response )
508+
509+ tool_start_event = next (
510+ e for e in tru_events if "contentBlockStart" in e and "toolUse" in e ["contentBlockStart" ]["start" ]
511+ )
512+
513+ tool_id = tool_start_event ["contentBlockStart" ]["start" ]["toolUse" ]["toolUseId" ]
514+ assert tool_id is not None
515+ assert tool_id .startswith ("call_" )
516+ assert len (tool_id ) > 5
0 commit comments