mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(speech): forward a top-level ttl and pass deployment headers as is through the TTS bridge
Picked from 9dafd85341 without its awaited aspeech path. 1.108.x has no internal sub-call scope around the bridge's inner chat call, so awaiting acompletion there writes the speech spend row under the provider's response id instead of the call id
This commit is contained in:
parent
d18b0d8289
commit
176808e268
4 changed files with 61 additions and 4 deletions
|
|
@ -90,7 +90,7 @@ class SpeechToCompletionBridgeHandler:
|
||||||
logging_obj: "LiteLLMLoggingObj",
|
logging_obj: "LiteLLMLoggingObj",
|
||||||
custom_llm_provider: str,
|
custom_llm_provider: str,
|
||||||
api_base: str | None,
|
api_base: str | None,
|
||||||
extra_headers: Mapping[str, str] | None,
|
extra_headers: Mapping[str, object] | None,
|
||||||
timeout: "float | httpx.Timeout | None",
|
timeout: "float | httpx.Timeout | None",
|
||||||
cache_controls: "CacheControlsHint",
|
cache_controls: "CacheControlsHint",
|
||||||
) -> "HttpxBinaryResponseContent":
|
) -> "HttpxBinaryResponseContent":
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ class SpeechToCompletionBridgeTransformationHandler:
|
||||||
headers: dict,
|
headers: dict,
|
||||||
litellm_logging_obj: "LiteLLMLoggingObj",
|
litellm_logging_obj: "LiteLLMLoggingObj",
|
||||||
custom_llm_provider: str,
|
custom_llm_provider: str,
|
||||||
) -> dict:
|
) -> Mapping[str, object]:
|
||||||
self._validate_response_format(model, custom_llm_provider, optional_params)
|
self._validate_response_format(model, custom_llm_provider, optional_params)
|
||||||
user_message: Final[ChatCompletionUserMessage] = {"role": "user", "content": input}
|
user_message: Final[ChatCompletionUserMessage] = {"role": "user", "content": input}
|
||||||
return_kwargs: Final = {
|
return_kwargs: Final = {
|
||||||
|
|
|
||||||
|
|
@ -4874,7 +4874,8 @@ class BaseUrlRoutingHint(TypedDict, total=False):
|
||||||
class CacheControlsHint(TypedDict, total=False):
|
class CacheControlsHint(TypedDict, total=False):
|
||||||
cache: ReadOnly[OnErrorOmit[Mapping[str, object] | None]]
|
cache: ReadOnly[OnErrorOmit[Mapping[str, object] | None]]
|
||||||
caching: ReadOnly[OnErrorOmit[JsonValue]]
|
caching: ReadOnly[OnErrorOmit[JsonValue]]
|
||||||
|
ttl: ReadOnly[OnErrorOmit[JsonValue]]
|
||||||
|
|
||||||
|
|
||||||
class DeploymentHeadersHint(TypedDict, total=False):
|
class DeploymentHeadersHint(TypedDict, total=False):
|
||||||
extra_headers: ReadOnly[OnErrorOmit[Mapping[str, str] | None]]
|
extra_headers: ReadOnly[OnErrorOmit[Mapping[str, object] | None]]
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ from litellm import (
|
||||||
from litellm import main as litellm_main
|
from litellm import main as litellm_main
|
||||||
from litellm.caching.base_cache import BaseCache
|
from litellm.caching.base_cache import BaseCache
|
||||||
from litellm.caching.caching import Cache
|
from litellm.caching.caching import Cache
|
||||||
|
from litellm.caching.in_memory_cache import InMemoryCache
|
||||||
from litellm.constants import CONTROL_OPTIONS_KEY
|
from litellm.constants import CONTROL_OPTIONS_KEY
|
||||||
from litellm.integrations.custom_logger import CustomLogger
|
from litellm.integrations.custom_logger import CustomLogger
|
||||||
from litellm.integrations.custom_prompt_management import CustomPromptManagement
|
from litellm.integrations.custom_prompt_management import CustomPromptManagement
|
||||||
|
|
@ -4905,7 +4906,7 @@ def test_speech_gemini_tts_honors_the_request_cache_controls(
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"malformed_controls",
|
"malformed_controls",
|
||||||
[{"caching": "yes"}, {"caching": 1}, {"cache": {1: True}}, {"extra_headers": {"x-gateway-key": 5}}],
|
[{"caching": "yes"}, {"caching": 1}, {"cache": {1: True}}],
|
||||||
)
|
)
|
||||||
def test_speech_gemini_tts_without_a_cache_ignores_malformed_controls(
|
def test_speech_gemini_tts_without_a_cache_ignores_malformed_controls(
|
||||||
respx_mock: respx.MockRouter,
|
respx_mock: respx.MockRouter,
|
||||||
|
|
@ -4929,6 +4930,61 @@ def test_speech_gemini_tts_without_a_cache_ignores_malformed_controls(
|
||||||
assert response.content.startswith(b"RIFF")
|
assert response.content.startswith(b"RIFF")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ttl", [60, "60"])
|
||||||
|
def test_speech_gemini_tts_keeps_cached_audio_only_for_the_request_ttl(
|
||||||
|
respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch, ttl: int | str
|
||||||
|
):
|
||||||
|
now: Final = [0.0]
|
||||||
|
cache: Final = Cache(type="local")
|
||||||
|
cache.cache = InMemoryCache(clock=lambda: now[0])
|
||||||
|
monkeypatch.setattr(litellm, "cache", cache)
|
||||||
|
route: Final = respx_mock.post(url__regex=GEMINI_DEFAULT_GENERATE_CONTENT_URL).mock(
|
||||||
|
return_value=httpx.Response(200, json=_gemini_tts_generate_content_response())
|
||||||
|
)
|
||||||
|
|
||||||
|
def speak() -> None:
|
||||||
|
litellm.speech(
|
||||||
|
model=f"gemini/{GEMINI_TTS_MODEL}", input="hello", voice="Kore", api_key="fake-gemini-key", ttl=ttl
|
||||||
|
)
|
||||||
|
|
||||||
|
speak()
|
||||||
|
now[0] = 30.0
|
||||||
|
speak()
|
||||||
|
assert route.call_count == 1
|
||||||
|
now[0] = 61.0
|
||||||
|
speak()
|
||||||
|
assert route.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_speech_gemini_tts_fails_like_chat_on_a_non_string_deployment_header(respx_mock: respx.MockRouter):
|
||||||
|
route: Final = respx_mock.post(url__regex=GEMINI_DEFAULT_GENERATE_CONTENT_URL).mock(
|
||||||
|
return_value=httpx.Response(200, json=_gemini_tts_generate_content_response())
|
||||||
|
)
|
||||||
|
deployment_headers: Final = {"x-gateway-key": "gateway-secret", "x-org-id": 12345}
|
||||||
|
|
||||||
|
with pytest.raises(litellm.APIConnectionError) as chat_error:
|
||||||
|
litellm.completion(
|
||||||
|
model=f"gemini/{GEMINI_TTS_MODEL}",
|
||||||
|
messages=[{"role": "user", "content": "hello"}],
|
||||||
|
modalities=["audio"],
|
||||||
|
audio={"voice": "Kore", "format": "pcm16"},
|
||||||
|
api_key="fake-gemini-key",
|
||||||
|
extra_headers=deployment_headers,
|
||||||
|
)
|
||||||
|
with pytest.raises(litellm.APIConnectionError) as speech_error:
|
||||||
|
litellm.speech(
|
||||||
|
model=f"gemini/{GEMINI_TTS_MODEL}",
|
||||||
|
input="hello",
|
||||||
|
voice="Kore",
|
||||||
|
api_key="fake-gemini-key",
|
||||||
|
extra_headers=deployment_headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Header value must be str or bytes" in str(chat_error.value)
|
||||||
|
assert "Header value must be str or bytes" in str(speech_error.value)
|
||||||
|
assert route.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
FOUNDRY_HOST: Final = "https://my-project.services.ai.azure.com"
|
FOUNDRY_HOST: Final = "https://my-project.services.ai.azure.com"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue