fix(aws_polly): read the engine from the bare model name (#47963)

* fix(aws_polly): read the engine from the bare model name

speech() strips the aws_polly/ prefix before AWSPollyTextToSpeechConfig sees
the model, so _extract_engine_from_model got a bare "standard", found no
slash and returned the neural default. Every Polly request went out with
Engine: neural whatever the model named.

Take the last path segment instead, which covers both the bare name and the
prefixed one.

* test(aws_polly): move imports to the top of the file

* docs(aws_polly): update the engine comment to the bare model name
This commit is contained in:
Fahima Mokhtari 2026-10-23 21:10:51 -04:00 • committed by GitHub
parent 9fab92672a
commit 428c9f6bf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 58 additions and 11 deletions

View file

@ -178,7 +178,7 @@ class AWSPollyTextToSpeechConfig(BaseTextToSpeechConfig, BaseAWSLLM):
else:
mapped_params["output_format"] = self.DEFAULT_OUTPUT_FORMAT
# Extract engine from model name (e.g., "aws_polly/neural" -> "neural")
# model arrives without the "aws_polly/" prefix, e.g. "standard"
engine: Final = self._extract_engine_from_model(model)
mapped_params["engine"] = engine
@ -196,18 +196,17 @@ class AWSPollyTextToSpeechConfig(BaseTextToSpeechConfig, BaseAWSLLM):
"""
Extract engine from model name.
speech() strips the "aws_polly/" prefix before the config is called,
so the bare engine name is what normally arrives here.
Examples:
- aws_polly/neural -> neural
- aws_polly/standard -> standard
- standard -> standard
- aws_polly/long-form -> long-form
- aws_polly -> neural (default)
"""
if "/" in model:
parts: Final = model.split("/")
if len(parts) >= 2:
engine: Final = parts[1].lower()
if engine in self.VALID_ENGINES:
return engine
engine: Final = model.rsplit("/", 1)[-1].lower()
if engine in self.VALID_ENGINES:
return engine
return self.DEFAULT_ENGINE
def validate_environment(

View file

@ -1,6 +1,10 @@
import litellm
import json
from unittest.mock import MagicMock, patch
import httpx
import pytest
from unittest.mock import MagicMock
import litellm
@pytest.mark.asyncio
@ -134,6 +138,34 @@ async def test_aws_polly_tts_with_native_voice():
assert request_body.get("TextType", "text") == "text"
@pytest.mark.asyncio
@pytest.mark.usefixtures("fake_provider_credentials")
@pytest.mark.parametrize("engine", ["standard", "neural", "long-form", "generative"])
async def test_aws_polly_tts_sends_engine_from_model(engine: str) -> None:
"""
The engine in the model name must reach Polly. speech() strips the
provider prefix before the config reads it, which used to turn every
engine into the neural default.
"""
mock_httpx_response = MagicMock(spec=httpx.Response)
mock_httpx_response.content = b"fake_audio_data"
mock_httpx_response.status_code = 200
mock_httpx_response.headers = {"content-type": "audio/mpeg"}
with patch("litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post") as mock_post:
mock_post.return_value = mock_httpx_response
await litellm.aspeech(
model=f"aws_polly/{engine}",
voice="Joanna",
input="Hello",
aws_region_name="us-east-1",
)
request_body = json.loads(mock_post.call_args.kwargs["data"])
assert request_body["Engine"] == engine
@pytest.mark.asyncio
@pytest.mark.usefixtures("fake_provider_credentials")
async def test_aws_polly_tts_with_openai_voice_mapping():

View file

@ -1,6 +1,8 @@
import json
from typing import Final
import pytest
from litellm.llms.aws_polly.text_to_speech.transformation import AWSPollyTextToSpeechConfig
@ -17,3 +19,17 @@ def test_installed_botocore_signs_the_speech_request() -> None:
authorization: Final = headers["Authorization"]
assert "/us-west-2/polly/aws4_request" in authorization
assert json.loads(body) == {"Text": "ping", "VoiceId": "Joanna"}
@pytest.mark.parametrize(
("model", "expected"),
[
("standard", "standard"),
("Long-Form", "long-form"),
("aws_polly/generative", "generative"),
("aws_polly", "neural"),
("unknown", "neural"),
],
)
def test_extract_engine_from_model(model: str, expected: str) -> None:
assert AWSPollyTextToSpeechConfig()._extract_engine_from_model(model) == expected