diff --git a/litellm/endpoints/speech/speech_to_completion_bridge/handler.py b/litellm/endpoints/speech/speech_to_completion_bridge/handler.py index 45cdd4b68d2..4c1036be930 100644 --- a/litellm/endpoints/speech/speech_to_completion_bridge/handler.py +++ b/litellm/endpoints/speech/speech_to_completion_bridge/handler.py @@ -2,7 +2,6 @@ Handler for transforming /chat/completions api requests to litellm.responses requests """ -from collections.abc import Mapping from typing import TYPE_CHECKING, Final from typing_extensions import TypedDict @@ -12,6 +11,7 @@ if TYPE_CHECKING: from litellm import LiteLLMLoggingObj from litellm.types.llms.openai import HttpxBinaryResponseContent + from litellm.types.utils import CacheControlsHint class SpeechToCompletionBridgeHandlerInputKwargs(TypedDict): @@ -90,7 +90,7 @@ class SpeechToCompletionBridgeHandler: custom_llm_provider: str, api_base: str | None, timeout: "float | httpx.Timeout | None", - cache: Mapping[str, object] | None, + cache_controls: "CacheControlsHint", ) -> "HttpxBinaryResponseContent": received_args: Final = locals() from litellm import completion @@ -110,7 +110,7 @@ class SpeechToCompletionBridgeHandler: model=model, input=input, optional_params=optional_params, - litellm_params={**litellm_params, "api_base": api_base, "timeout": timeout, "cache": cache}, + litellm_params={**litellm_params, "api_base": api_base, "timeout": timeout, **cache_controls}, headers=headers, litellm_logging_obj=logging_obj, custom_llm_provider=custom_llm_provider, diff --git a/litellm/main.py b/litellm/main.py index d0d7920df3f..035e0628d8c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -8755,7 +8755,7 @@ def speech( custom_llm_provider=custom_llm_provider, api_base=api_base, timeout=timeout, - cache=_CACHE_CONTROLS_HINT.validate_python(kwargs).get("cache"), + cache_controls=_CACHE_CONTROLS_HINT.validate_python(kwargs), ) # Vertex AI Text-to-Speech (Google Cloud TTS) @@ -8809,7 +8809,7 @@ def speech( custom_llm_provider=custom_llm_provider, api_base=api_base, timeout=timeout, - cache=_CACHE_CONTROLS_HINT.validate_python(kwargs).get("cache"), + cache_controls=_CACHE_CONTROLS_HINT.validate_python(kwargs), ) elif custom_llm_provider == "runwayml": from litellm.llms.runwayml.text_to_speech.transformation import ( diff --git a/litellm/types/utils.py b/litellm/types/utils.py index d3e71412341..6215bf4d3c7 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -40,8 +40,10 @@ from pydantic import ( Field, FieldSerializationInfo, JsonValue, + OnErrorOmit, PrivateAttr, SkipValidation, + StrictBool, field_serializer, field_validator, ) @@ -4871,4 +4873,5 @@ class BaseUrlRoutingHint(TypedDict, total=False): class CacheControlsHint(TypedDict, total=False): - cache: ReadOnly[Mapping[str, object] | None] + cache: ReadOnly[OnErrorOmit[Mapping[str, object] | None]] + caching: ReadOnly[OnErrorOmit[StrictBool | None]] diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index dea7835120a..7a76ed590aa 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -4844,13 +4844,14 @@ def test_speech_gemini_tts_without_a_timeout_waits_as_long_as_the_same_chat_comp ({}, {}, 1), ({}, {"cache": {"no-cache": True}}, 2), ({"cache": {"no-store": True}}, {}, 2), + ({}, {"caching": False}, 2), ], ) def test_speech_gemini_tts_honors_the_request_cache_controls( respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch, - first_controls: Mapping[str, Mapping[str, bool]], - second_controls: Mapping[str, Mapping[str, bool]], + first_controls: Mapping[str, object], + second_controls: Mapping[str, object], expected_upstream_calls: int, ): monkeypatch.setattr(litellm, "cache", Cache(type="local")) @@ -4870,6 +4871,29 @@ def test_speech_gemini_tts_honors_the_request_cache_controls( assert route.call_count == expected_upstream_calls +@pytest.mark.parametrize("malformed_controls", [{"caching": "yes"}, {"caching": 1}, {"cache": {1: True}}]) +def test_speech_gemini_tts_without_a_cache_ignores_malformed_cache_controls( + respx_mock: respx.MockRouter, + monkeypatch: pytest.MonkeyPatch, + malformed_controls: Mapping[str, object], +): + monkeypatch.setattr(litellm, "cache", None) + route: Final = respx_mock.post(url__regex=GEMINI_DEFAULT_GENERATE_CONTENT_URL).mock( + return_value=httpx.Response(200, json=_gemini_tts_generate_content_response()) + ) + + response: Final = litellm.speech( + model=f"gemini/{GEMINI_TTS_MODEL}", + input="hello", + voice="Kore", + api_key="fake-gemini-key", + **malformed_controls, + ) + + assert route.call_count == 1 + assert response.content.startswith(b"RIFF") + + FOUNDRY_HOST: Final = "https://my-project.services.ai.azure.com"