mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
- Add MiniMax-M2.7-highspeed constant to const.py and MODEL_LIST - Update MinimaxBot default model from MiniMax-M2.1 to MiniMax-M2.7 - Add MinimaxVoice TTS provider (voice/minimax/minimax_voice.py) - Supports speech-2.8-hd and speech-2.8-turbo models - SSE streaming with hex-decoded audio chunks - Reuses MINIMAX_API_KEY - Register MinimaxVoice in voice factory - Add unit tests (14 tests, all passing) - Update README with MiniMax-M2.7-highspeed and TTS configuration
185 lines
7.3 KiB
Python
185 lines
7.3 KiB
Python
# encoding:utf-8
|
|
"""
|
|
Unit tests for MiniMax provider additions:
|
|
- MiniMax-M2.7-highspeed constant in const.py
|
|
- Default model update in MinimaxBot
|
|
- MinimaxVoice TTS provider
|
|
"""
|
|
import sys
|
|
import os
|
|
import json
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch, PropertyMock
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
|
|
class TestMinimaxConst(unittest.TestCase):
|
|
"""Test that MiniMax-M2.7-highspeed is properly registered in const.py."""
|
|
|
|
def test_m2_7_highspeed_constant_defined(self):
|
|
from common import const
|
|
self.assertTrue(hasattr(const, "MINIMAX_M2_7_HIGHSPEED"))
|
|
self.assertEqual(const.MINIMAX_M2_7_HIGHSPEED, "MiniMax-M2.7-highspeed")
|
|
|
|
def test_m2_7_constant_defined(self):
|
|
from common import const
|
|
self.assertEqual(const.MINIMAX_M2_7, "MiniMax-M2.7")
|
|
|
|
def test_m2_7_highspeed_in_model_list(self):
|
|
from common import const
|
|
self.assertIn("MiniMax-M2.7-highspeed", const.MODEL_LIST)
|
|
|
|
def test_m2_7_in_model_list(self):
|
|
from common import const
|
|
self.assertIn("MiniMax-M2.7", const.MODEL_LIST)
|
|
|
|
def test_minimax_provider_key_defined(self):
|
|
from common import const
|
|
self.assertEqual(const.MiniMax, "minimax")
|
|
|
|
|
|
class TestMinimaxBotDefaultModel(unittest.TestCase):
|
|
"""Test that MinimaxBot defaults to MiniMax-M2.7."""
|
|
|
|
def test_default_model_is_m2_7(self):
|
|
# Patch conf() to return empty config
|
|
mock_conf = MagicMock()
|
|
mock_conf.get = MagicMock(side_effect=lambda key, default=None: default)
|
|
|
|
with patch("models.minimax.minimax_bot.conf", return_value=mock_conf):
|
|
with patch("models.minimax.minimax_bot.SessionManager"):
|
|
from models.minimax import minimax_bot
|
|
# Reload to pick up patches
|
|
import importlib
|
|
importlib.reload(minimax_bot)
|
|
with patch("models.minimax.minimax_bot.conf", return_value=mock_conf):
|
|
bot = minimax_bot.MinimaxBot.__new__(minimax_bot.MinimaxBot)
|
|
bot.args = {
|
|
"model": mock_conf.get("model") or "MiniMax-M2.7",
|
|
}
|
|
self.assertEqual(bot.args["model"], "MiniMax-M2.7")
|
|
|
|
def test_default_model_string(self):
|
|
"""Verify the fallback string literal in minimax_bot.py is MiniMax-M2.7."""
|
|
import ast
|
|
bot_path = os.path.join(os.path.dirname(__file__), "..", "models", "minimax", "minimax_bot.py")
|
|
with open(bot_path) as f:
|
|
source = f.read()
|
|
# Verify MiniMax-M2.7 is in the source (not M2.1)
|
|
self.assertIn("MiniMax-M2.7", source)
|
|
self.assertNotIn('"MiniMax-M2.1"', source)
|
|
|
|
|
|
class TestMinimaxVoice(unittest.TestCase):
|
|
"""Test MinimaxVoice TTS provider."""
|
|
|
|
def _make_voice(self, api_key="test-key", api_base="https://api.minimax.io/v1"):
|
|
mock_conf = MagicMock()
|
|
def conf_get(key, default=None):
|
|
return {
|
|
"minimax_api_key": api_key,
|
|
"minimax_api_base": api_base,
|
|
}.get(key, default)
|
|
mock_conf.get = conf_get
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
from voice.minimax.minimax_voice import MinimaxVoice
|
|
return MinimaxVoice()
|
|
|
|
def test_instantiation(self):
|
|
voice = self._make_voice()
|
|
self.assertIsNotNone(voice)
|
|
|
|
def test_api_base_strips_v1_suffix(self):
|
|
voice = self._make_voice(api_base="https://api.minimax.io/v1")
|
|
self.assertEqual(voice.api_base, "https://api.minimax.io")
|
|
|
|
def test_api_base_no_trailing_slash(self):
|
|
voice = self._make_voice(api_base="https://api.minimax.io")
|
|
self.assertEqual(voice.api_base, "https://api.minimax.io")
|
|
|
|
def test_voice_to_text_not_supported(self):
|
|
voice = self._make_voice()
|
|
with self.assertRaises(NotImplementedError):
|
|
voice.voiceToText("dummy.wav")
|
|
|
|
def test_text_to_voice_success(self):
|
|
"""Test textToVoice with mocked SSE stream response."""
|
|
import os
|
|
os.makedirs("tmp", exist_ok=True)
|
|
|
|
# Build fake SSE stream bytes
|
|
audio_hex = bytes([0x49, 0x44, 0x33]).hex() # "ID3" MP3 magic bytes
|
|
sse_line = f'data: {{"data": {{"audio": "{audio_hex}", "status": 2}}}}\n\n'
|
|
done_line = "data: [DONE]\n\n"
|
|
fake_body = (sse_line + done_line).encode("utf-8")
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.raise_for_status = MagicMock()
|
|
mock_response.iter_lines.return_value = [
|
|
line.encode("utf-8") for line in (sse_line + done_line).splitlines() if line
|
|
]
|
|
|
|
mock_conf = MagicMock()
|
|
def conf_get(key, default=None):
|
|
return {
|
|
"minimax_api_key": "test-key",
|
|
"minimax_api_base": "https://api.minimax.io",
|
|
}.get(key, default)
|
|
mock_conf.get = conf_get
|
|
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
with patch("voice.minimax.minimax_voice.requests.post", return_value=mock_response):
|
|
from voice.minimax import minimax_voice
|
|
import importlib
|
|
importlib.reload(minimax_voice)
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
voice = minimax_voice.MinimaxVoice()
|
|
from bridge.reply import ReplyType
|
|
reply = voice.textToVoice("Hello, world!")
|
|
self.assertEqual(reply.type, ReplyType.VOICE)
|
|
self.assertTrue(reply.content.endswith(".mp3"))
|
|
|
|
def test_text_to_voice_no_audio_returns_error(self):
|
|
"""Test that empty SSE stream returns an ERROR reply."""
|
|
mock_response = MagicMock()
|
|
mock_response.raise_for_status = MagicMock()
|
|
mock_response.iter_lines.return_value = []
|
|
|
|
mock_conf = MagicMock()
|
|
def conf_get(key, default=None):
|
|
return {
|
|
"minimax_api_key": "test-key",
|
|
"minimax_api_base": "https://api.minimax.io",
|
|
}.get(key, default)
|
|
mock_conf.get = conf_get
|
|
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
with patch("voice.minimax.minimax_voice.requests.post", return_value=mock_response):
|
|
from voice.minimax import minimax_voice
|
|
import importlib
|
|
importlib.reload(minimax_voice)
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
voice = minimax_voice.MinimaxVoice()
|
|
from bridge.reply import ReplyType
|
|
reply = voice.textToVoice("Hello")
|
|
self.assertEqual(reply.type, ReplyType.ERROR)
|
|
|
|
|
|
class TestVoiceFactory(unittest.TestCase):
|
|
"""Test that minimax is registered in the voice factory."""
|
|
|
|
def test_minimax_voice_factory(self):
|
|
mock_conf = MagicMock()
|
|
mock_conf.get = MagicMock(return_value=None)
|
|
with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
|
|
from voice.factory import create_voice
|
|
voice = create_voice("minimax")
|
|
from voice.minimax.minimax_voice import MinimaxVoice
|
|
self.assertIsInstance(voice, MinimaxVoice)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|