diff --git a/litellm/llms/text_to_speech/vertex_ai.py b/litellm/llms/text_to_speech/vertex_ai.py index 03120a8698..168a9eeb09 100644 --- a/litellm/llms/text_to_speech/vertex_ai.py +++ b/litellm/llms/text_to_speech/vertex_ai.py @@ -57,7 +57,7 @@ class VertexTextToSpeechAPI(VertexLLM): voice: Optional[dict] = None, _is_async: Optional[bool] = False, optional_params: Optional[dict] = None, - **kwargs, + kwargs: Optional[dict] = None, ): import base64 @@ -87,10 +87,11 @@ class VertexTextToSpeechAPI(VertexLLM): vertex_input = VertexInput(text=input) # required param optional_params = optional_params or {} + kwargs = kwargs or {} if voice is not None: vertex_voice = VertexVoice(**voice) - elif "voice" in optional_params: - vertex_voice = VertexVoice(**optional_params["voice"]) + elif "voice" in kwargs: + vertex_voice = VertexVoice(**kwargs["voice"]) else: # use defaults to not fail the request vertex_voice = VertexVoice( @@ -98,8 +99,8 @@ class VertexTextToSpeechAPI(VertexLLM): name="en-US-Studio-O", ) - if "audioConfig" in optional_params: - vertex_audio_config = VertexAudioConfig(**optional_params["audioConfig"]) + if "audioConfig" in kwargs: + vertex_audio_config = VertexAudioConfig(**kwargs["audioConfig"]) else: # use defaults to not fail the request vertex_audio_config = VertexAudioConfig( diff --git a/litellm/main.py b/litellm/main.py index 9c8f0a2d35..62ad78e80c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -4867,6 +4867,7 @@ def speech( input=input, voice=voice, optional_params=optional_params, + kwargs=kwargs, logging_obj=logging_obj, ) diff --git a/litellm/tests/test_audio_speech.py b/litellm/tests/test_audio_speech.py index 129f4ea76b..5de996fa18 100644 --- a/litellm/tests/test_audio_speech.py +++ b/litellm/tests/test_audio_speech.py @@ -18,6 +18,7 @@ sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch import openai import pytest @@ -123,6 +124,7 @@ async def test_audio_speech_router(mode): "sync_mode", [False, True], ) +@pytest.mark.skip(reason="local only test - we run testing using MockRequests below") @pytest.mark.asyncio async def test_audio_speech_litellm_vertex(sync_mode): litellm.set_verbose = True @@ -147,3 +149,97 @@ async def test_audio_speech_litellm_vertex(sync_mode): from litellm.llms.openai import HttpxBinaryResponseContent response.stream_to_file(speech_file_path) + + +@pytest.mark.asyncio +async def test_speech_litellm_vertex_async(): + # Mock the response + mock_response = AsyncMock() + + def return_val(): + return { + "audioContent": "dGVzdCByZXNwb25zZQ==", + } + + mock_response.json = return_val + mock_response.status_code = 200 + + # Set up the mock for asynchronous calls + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_async_post: + mock_async_post.return_value = mock_response + model = "vertex_ai/test" + + response = await litellm.aspeech( + model=model, + input="async hello what llm guardrail do you have", + ) + + # Assert asynchronous call + mock_async_post.assert_called_once() + _, kwargs = mock_async_post.call_args + print("call args", kwargs) + + assert kwargs["url"] == "https://texttospeech.googleapis.com/v1/text:synthesize" + + assert "x-goog-user-project" in kwargs["headers"] + assert kwargs["headers"]["Authorization"] is not None + + assert kwargs["json"] == { + "input": {"text": "async hello what llm guardrail do you have"}, + "voice": {"languageCode": "en-US", "name": "en-US-Studio-O"}, + "audioConfig": {"audioEncoding": "LINEAR16", "speakingRate": "1"}, + } + + +@pytest.mark.asyncio +async def test_speech_litellm_vertex_async_with_voice(): + # Mock the response + mock_response = AsyncMock() + + def return_val(): + return { + "audioContent": "dGVzdCByZXNwb25zZQ==", + } + + mock_response.json = return_val + mock_response.status_code = 200 + + # Set up the mock for asynchronous calls + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_async_post: + mock_async_post.return_value = mock_response + model = "vertex_ai/test" + + response = await litellm.aspeech( + model=model, + input="async hello what llm guardrail do you have", + voice={ + "languageCode": "en-UK", + "name": "en-UK-Studio-O", + }, + audioConfig={ + "audioEncoding": "LINEAR22", + "speakingRate": "10", + }, + ) + + # Assert asynchronous call + mock_async_post.assert_called_once() + _, kwargs = mock_async_post.call_args + print("call args", kwargs) + + assert kwargs["url"] == "https://texttospeech.googleapis.com/v1/text:synthesize" + + assert "x-goog-user-project" in kwargs["headers"] + assert kwargs["headers"]["Authorization"] is not None + + assert kwargs["json"] == { + "input": {"text": "async hello what llm guardrail do you have"}, + "voice": {"languageCode": "en-UK", "name": "en-UK-Studio-O"}, + "audioConfig": {"audioEncoding": "LINEAR22", "speakingRate": "10"}, + }