mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
* test: drop the cwd-relative sys.path.insert calls from the test suite
TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.
Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.
Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.
* test: drop the duplicate imports the sys.path sweep exposed to F811
* test(pre-call-utils): restore the os import the new bedrock tests need
366 lines
12 KiB
Python
366 lines
12 KiB
Python
"""
|
|
Tests for MiniMax Text-to-Speech integration
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
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
|
|
|
|
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"""
|
|
|
|
# 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!")
|