litellm/tests/llm_translation/test_minimax_tts.py
2025-12-23 21:53:53 +05:30

372 lines
12 KiB
Python

"""
Tests for MiniMax Text-to-Speech integration
"""
import os
import sys
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
import pytest
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
import litellm
from litellm import speech
from litellm.llms.minimax.text_to_speech.transformation import (
MinimaxTextToSpeechConfig,
)
class TestMinimaxTextToSpeechConfig:
"""Test MiniMax TTS configuration and parameter mapping"""
def test_get_supported_openai_params(self):
"""Test that supported OpenAI params are correctly defined"""
config = MinimaxTextToSpeechConfig()
supported_params = config.get_supported_openai_params("speech-2.6-hd")
assert "voice" in supported_params
assert "response_format" in supported_params
assert "speed" in supported_params
def test_voice_mapping(self):
"""Test OpenAI voice to MiniMax voice_id mapping"""
config = MinimaxTextToSpeechConfig()
# Test OpenAI voice mappings
assert config._extract_voice_id("alloy") == "male-qn-qingse"
assert config._extract_voice_id("echo") == "male-qn-jingying"
assert config._extract_voice_id("nova") == "female-yujie"
# Test custom voice passthrough
assert config._extract_voice_id("custom-voice-id") == "custom-voice-id"
def test_format_mapping(self):
"""Test response format mapping"""
config = MinimaxTextToSpeechConfig()
assert config.FORMAT_MAPPINGS["mp3"] == "mp3"
assert config.FORMAT_MAPPINGS["pcm"] == "pcm"
assert config.FORMAT_MAPPINGS["wav"] == "wav"
assert config.FORMAT_MAPPINGS["flac"] == "flac"
def test_map_openai_params_basic(self):
"""Test basic parameter mapping from OpenAI to MiniMax format"""
config = MinimaxTextToSpeechConfig()
optional_params = {
"response_format": "mp3",
"speed": 1.5,
}
voice, mapped_params = config.map_openai_params(
model="speech-2.6-hd",
optional_params=optional_params,
voice="alloy",
)
assert voice == "male-qn-qingse"
assert mapped_params["format"] == "mp3"
assert mapped_params["speed"] == 1.5
assert mapped_params["voice_id"] == "male-qn-qingse"
def test_map_openai_params_speed_clamping(self):
"""Test that speed is clamped to MiniMax's supported range"""
config = MinimaxTextToSpeechConfig()
# Test speed too high
optional_params = {"speed": 5.0}
_, mapped_params = config.map_openai_params(
model="speech-2.6-hd",
optional_params=optional_params,
voice="alloy",
)
assert mapped_params["speed"] == 2.0 # Clamped to max
# Test speed too low
optional_params = {"speed": 0.1}
_, mapped_params = config.map_openai_params(
model="speech-2.6-hd",
optional_params=optional_params,
voice="alloy",
)
assert mapped_params["speed"] == 0.5 # Clamped to min
def test_map_openai_params_with_extra_body(self):
"""Test that extra_body parameters are passed through"""
config = MinimaxTextToSpeechConfig()
optional_params = {
"extra_body": {
"vol": 1.5,
"pitch": 2,
"sample_rate": 24000,
}
}
_, mapped_params = config.map_openai_params(
model="speech-2.6-hd",
optional_params=optional_params,
voice="alloy",
)
assert mapped_params["vol"] == 1.5
assert mapped_params["pitch"] == 2
assert mapped_params["sample_rate"] == 24000
def test_validate_environment_with_api_key(self):
"""Test environment validation with API key"""
config = MinimaxTextToSpeechConfig()
headers = {}
result_headers = config.validate_environment(
headers=headers,
model="speech-2.6-hd",
api_key="test-api-key",
)
assert "Authorization" in result_headers
assert result_headers["Authorization"] == "Bearer test-api-key"
assert result_headers["Content-Type"] == "application/json"
def test_validate_environment_missing_api_key(self):
"""Test that validation fails without API key"""
config = MinimaxTextToSpeechConfig()
headers = {}
# Mock both litellm.api_key and get_secret_str to return None
import litellm
from unittest.mock import patch
original_api_key = litellm.api_key
try:
litellm.api_key = None
with patch("litellm.llms.minimax.text_to_speech.transformation.get_secret_str", return_value=None):
with pytest.raises(ValueError, match="MiniMax API key is required"):
config.validate_environment(
headers=headers,
model="speech-2.6-hd",
api_key=None,
)
finally:
litellm.api_key = original_api_key
def test_transform_text_to_speech_request(self):
"""Test request transformation to MiniMax format"""
config = MinimaxTextToSpeechConfig()
optional_params = {
"voice_id": "male-qn-qingse",
"speed": 1.2,
"format": "mp3",
"vol": 1.0,
"pitch": 0,
"sample_rate": 32000,
"bitrate": 128000,
"channel": 1,
}
result = config.transform_text_to_speech_request(
model="speech-2.6-hd",
input="Hello, world!",
voice="male-qn-qingse",
optional_params=optional_params,
litellm_params={},
headers={},
)
assert "dict_body" in result
body = result["dict_body"]
assert body["model"] == "speech-2.6-hd"
assert body["text"] == "Hello, world!"
assert body["stream"] is False
assert body["voice_setting"]["voice_id"] == "male-qn-qingse"
assert body["voice_setting"]["speed"] == 1.2
assert body["audio_setting"]["format"] == "mp3"
assert body["audio_setting"]["sample_rate"] == 32000
def test_get_complete_url(self):
"""Test URL construction"""
config = MinimaxTextToSpeechConfig()
url = config.get_complete_url(
model="speech-2.6-hd",
api_base=None,
litellm_params={},
)
assert url == "https://api.minimax.io/v1/t2a_v2"
def test_get_complete_url_custom_base(self):
"""Test URL construction with custom API base"""
config = MinimaxTextToSpeechConfig()
url = config.get_complete_url(
model="speech-2.6-hd",
api_base="https://custom.api.com",
litellm_params={},
)
assert url == "https://custom.api.com/v1/t2a_v2"
class TestMinimaxSpeechIntegration:
"""Integration tests for MiniMax TTS via litellm.speech()"""
@pytest.mark.skip(reason="Requires MiniMax API key")
def test_speech_basic(self):
"""Test basic speech synthesis call"""
# This test requires a real API key
os.environ["MINIMAX_API_KEY"] = "your-api-key-here"
speech_file_path = Path(__file__).parent / "test_minimax_speech.mp3"
response = speech(
model="minimax/speech-2.6-hd",
voice="alloy",
input="Hello, this is a test of MiniMax text to speech.",
)
response.stream_to_file(speech_file_path)
# Verify file was created
assert speech_file_path.exists()
assert speech_file_path.stat().st_size > 0
# Clean up
speech_file_path.unlink()
@pytest.mark.skip(reason="Requires MiniMax API key")
def test_speech_with_custom_params(self):
"""Test speech synthesis with custom parameters"""
os.environ["MINIMAX_API_KEY"] = "your-api-key-here"
speech_file_path = Path(__file__).parent / "test_minimax_speech_custom.mp3"
response = speech(
model="minimax/speech-2.6-turbo",
voice="nova",
input="Testing custom parameters.",
speed=1.5,
response_format="mp3",
extra_body={
"vol": 1.2,
"pitch": 1,
"sample_rate": 24000,
},
)
response.stream_to_file(speech_file_path)
# Verify file was created
assert speech_file_path.exists()
assert speech_file_path.stat().st_size > 0
# Clean up
speech_file_path.unlink()
def test_speech_mock_response(self):
"""Test speech synthesis with mocked response"""
from unittest.mock import MagicMock, patch
# Create mock audio data (hex-encoded as MiniMax returns)
mock_audio_bytes = b"fake audio data for testing"
mock_audio_hex = mock_audio_bytes.hex()
mock_response_json = {
"data": {
"audio": mock_audio_hex,
"status": 0,
"ced": ""
},
"extra_info": {},
}
with patch("litellm.llms.custom_httpx.llm_http_handler.BaseLLMHTTPHandler.text_to_speech_handler") as mock_tts:
# Create a mock httpx.Response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.headers = {}
mock_response.json.return_value = mock_response_json
mock_response.content = mock_audio_bytes
# Mock the response wrapper
from litellm.types.llms.openai import HttpxBinaryResponseContent
mock_binary_response = HttpxBinaryResponseContent(mock_response)
mock_tts.return_value = mock_binary_response
# This would normally make a real API call
# but we're mocking it for testing
response = speech(
model="minimax/speech-2.6-hd",
voice="alloy",
input="Test input",
api_key="test-key",
)
# Verify the mock was called
assert mock_tts.called
class TestMinimaxProviderRegistration:
"""Test that MiniMax is properly registered as a provider"""
def test_minimax_in_llm_providers(self):
"""Test that MINIMAX is in LlmProviders enum"""
from litellm.types.utils import LlmProviders
assert hasattr(LlmProviders, "MINIMAX")
assert LlmProviders.MINIMAX.value == "minimax"
def test_minimax_in_provider_list(self):
"""Test that minimax is in the provider list"""
assert litellm.LlmProviders.MINIMAX in litellm.provider_list
def test_get_provider_text_to_speech_config(self):
"""Test that MiniMax TTS config can be retrieved"""
from litellm.utils import ProviderConfigManager
config = ProviderConfigManager.get_provider_text_to_speech_config(
model="speech-2.6-hd",
provider=litellm.LlmProviders.MINIMAX,
)
assert config is not None
assert isinstance(config, MinimaxTextToSpeechConfig)
def test_get_llm_provider_minimax(self):
"""Test that get_llm_provider correctly identifies MiniMax models"""
from litellm import get_llm_provider
model, provider, api_key, api_base = get_llm_provider(
model="minimax/speech-2.6-hd"
)
assert model == "speech-2.6-hd"
assert provider == "minimax"
if __name__ == "__main__":
# Run basic tests
test_config = TestMinimaxTextToSpeechConfig()
test_config.test_get_supported_openai_params()
test_config.test_voice_mapping()
test_config.test_format_mapping()
test_config.test_map_openai_params_basic()
test_config.test_map_openai_params_speed_clamping()
test_config.test_transform_text_to_speech_request()
test_config.test_get_complete_url()
test_registration = TestMinimaxProviderRegistration()
test_registration.test_minimax_in_llm_providers()
test_registration.test_minimax_in_provider_list()
test_registration.test_get_provider_text_to_speech_config()
test_registration.test_get_llm_provider_minimax()
print("All basic tests passed!")