mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(vertex-ai): preserve existing Gemini TTS models and voice mappings
This commit is contained in:
parent
f67e9794c3
commit
c3baeed801
5 changed files with 128 additions and 8 deletions
|
|
@ -193,12 +193,24 @@ def _normalize_gemini_speech_config_item(value: object) -> object:
|
|||
def normalize_gemini_speech_config(
|
||||
value: Mapping[str, object],
|
||||
) -> dict[str, object]: # mutable-ok: provider request serialization requires a concrete dict
|
||||
normalized_value: Final = _normalize_gemini_speech_config_item(value)
|
||||
if isinstance(normalized_value, dict):
|
||||
return cast( # cast-ok: the runtime dict check establishes the normalizer's concrete result shape
|
||||
dict[str, object], normalized_value
|
||||
)
|
||||
return {} # mutable-ok: provider request serialization requires a concrete empty dict
|
||||
nested_config: Final = value.get("speechConfig", value.get("speech_config"))
|
||||
config: Final = nested_config if isinstance(nested_config, Mapping) else value
|
||||
normalized_value: Final = _normalize_gemini_speech_config_item(config)
|
||||
if not isinstance(normalized_value, dict):
|
||||
return {} # mutable-ok: provider request serialization requires a concrete empty dict
|
||||
normalized: Final = cast( # cast-ok: runtime dict check establishes the normalizer's concrete result shape
|
||||
dict[str, object], normalized_value
|
||||
)
|
||||
voice_name: Final = next(
|
||||
(normalized[key] for key in ("name", "voiceName", "voice") if isinstance(normalized.get(key), str)),
|
||||
None,
|
||||
)
|
||||
if voice_name is None or "voiceConfig" in normalized or "multiSpeakerVoiceConfig" in normalized:
|
||||
return normalized
|
||||
return { # mutable-ok: provider request serialization requires a concrete dict
|
||||
**{key: item for key, item in normalized.items() if key not in ("name", "voiceName", "voice", "modelName")},
|
||||
"voiceConfig": {"prebuiltVoiceConfig": {"voiceName": voice_name}},
|
||||
}
|
||||
|
||||
|
||||
class VertexAIBaseConfig:
|
||||
|
|
|
|||
|
|
@ -65812,10 +65812,46 @@
|
|||
"output_cost_per_token": 2e-06,
|
||||
"source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing"
|
||||
},
|
||||
"vertex_ai/gemini-2.5-flash-tts": {
|
||||
"input_cost_per_token": 5e-07,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 9.999999999999999e-06,
|
||||
"output_cost_per_token": 9.999999999999999e-06,
|
||||
"source": "https://cloud.google.com/text-to-speech/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/audio/speech"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"audio"
|
||||
],
|
||||
"supports_audio_output": true
|
||||
},
|
||||
"vertex_ai/gemini-2.5-pro-tts": {
|
||||
"input_cost_per_token": 1e-06,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 1.9999999999999998e-05,
|
||||
"output_cost_per_token": 1.9999999999999998e-05,
|
||||
"source": "https://cloud.google.com/text-to-speech/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/audio/speech"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"audio"
|
||||
],
|
||||
"supports_audio_output": true
|
||||
},
|
||||
"vertex_ai/gemini-2.5-flash-preview-tts": {
|
||||
"input_cost_per_token": 5e-07,
|
||||
"input_cost_per_token_batches": 2.5e-07,
|
||||
"litellm_provider": "vertex_ai",
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 1e-05,
|
||||
"output_cost_per_token": 1e-05,
|
||||
|
|
|
|||
|
|
@ -65812,10 +65812,46 @@
|
|||
"output_cost_per_token": 2e-06,
|
||||
"source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing"
|
||||
},
|
||||
"vertex_ai/gemini-2.5-flash-tts": {
|
||||
"input_cost_per_token": 5e-07,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 9.999999999999999e-06,
|
||||
"output_cost_per_token": 9.999999999999999e-06,
|
||||
"source": "https://cloud.google.com/text-to-speech/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/audio/speech"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"audio"
|
||||
],
|
||||
"supports_audio_output": true
|
||||
},
|
||||
"vertex_ai/gemini-2.5-pro-tts": {
|
||||
"input_cost_per_token": 1e-06,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 1.9999999999999998e-05,
|
||||
"output_cost_per_token": 1.9999999999999998e-05,
|
||||
"source": "https://cloud.google.com/text-to-speech/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/audio/speech"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"audio"
|
||||
],
|
||||
"supports_audio_output": true
|
||||
},
|
||||
"vertex_ai/gemini-2.5-flash-preview-tts": {
|
||||
"input_cost_per_token": 5e-07,
|
||||
"input_cost_per_token_batches": 2.5e-07,
|
||||
"litellm_provider": "vertex_ai",
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "audio_speech",
|
||||
"output_cost_per_audio_token": 1e-05,
|
||||
"output_cost_per_token": 1e-05,
|
||||
|
|
|
|||
|
|
@ -527,3 +527,28 @@ class TestGeminiTTSSpeechConfigInRequestBody:
|
|||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("voice_key", ["name", "voiceName", "voice_name", "voice"])
|
||||
@pytest.mark.parametrize("wrapper", [None, "speechConfig", "speech_config"])
|
||||
def test_speech_bridge_preserves_single_speaker_voice_mapping(voice_key: str, wrapper: str | None):
|
||||
handler = SpeechToCompletionBridgeTransformationHandler()
|
||||
config = {voice_key: "Umbriel", "language_code": "en-US"}
|
||||
voice = {wrapper: config} if wrapper else config
|
||||
result = handler.transform_request(
|
||||
model=f"vertex_ai/{GEMINI_3_1_FLASH_TTS_MODEL}",
|
||||
input="Hello",
|
||||
voice=voice,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
litellm_logging_obj=MagicMock(),
|
||||
custom_llm_provider="vertex_ai",
|
||||
)
|
||||
assert result["audio"] == {
|
||||
"speech_config": {
|
||||
"voiceConfig": {"prebuiltVoiceConfig": {"voiceName": "Umbriel"}},
|
||||
"languageCode": "en-US",
|
||||
},
|
||||
"format": "pcm16",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,3 +72,14 @@ def test_gemini_tts_detection_uses_model_metadata(monkeypatch):
|
|||
_invalidate_model_cost_lowercase_map()
|
||||
|
||||
assert is_gemini_tts_model(model)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["gemini-2.5-flash-preview-tts", "gemini-2.5-flash-tts", "gemini-2.5-pro-tts"])
|
||||
@pytest.mark.parametrize("prefixed", [False, True])
|
||||
def test_existing_vertex_tts_models_keep_gemini_dispatch(model: str, prefixed: bool, monkeypatch):
|
||||
model_cost = _load_model_cost_map(Path(__file__).parents[2] / "model_prices_and_context_window.json")
|
||||
monkeypatch.setattr(litellm, "model_cost", model_cost)
|
||||
litellm.get_model_info.cache_clear()
|
||||
_invalidate_model_cost_lowercase_map()
|
||||
requested_model = f"vertex_ai/{model}" if prefixed else model
|
||||
assert is_gemini_tts_model(requested_model, custom_llm_provider="vertex_ai")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue