Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 51 additions & 56 deletions src/backend/test/llm/test_minimax_provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for MiniMax provider upgrade to OpenAI-compatible API (M2.7 support).
"""Tests for MiniMax provider upgrade to OpenAI-compatible API (M3 support).

These tests verify the MiniMax provider changes without requiring the full
bisheng backend to be installed, by testing the specific files that were changed.
Expand Down Expand Up @@ -90,7 +90,7 @@ class TestCoreAIModuleChanges(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.init_py_path = os.path.join(
os.path.dirname(__file__), '..', 'bisheng', 'core', 'ai', '__init__.py'
os.path.dirname(__file__), '..', '..', 'bisheng', 'core', 'ai', '__init__.py'
)
cls.init_py_path = os.path.normpath(cls.init_py_path)
with open(cls.init_py_path) as f:
Expand Down Expand Up @@ -139,7 +139,7 @@ class TestEmbeddingModuleConsistency(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.emb_py_path = os.path.join(
os.path.dirname(__file__), '..', 'bisheng', 'llm', 'domain', 'llm', 'embedding.py'
os.path.dirname(__file__), '..', '..', 'bisheng', 'llm', 'domain', 'llm', 'embedding.py'
)
cls.emb_py_path = os.path.normpath(cls.emb_py_path)
with open(cls.emb_py_path) as f:
Expand Down Expand Up @@ -169,7 +169,7 @@ class TestFrontendModelData(unittest.TestCase):
def setUpClass(cls):
cls.data_path = os.path.join(
os.path.dirname(__file__),
'../../frontend/platform/public/models/data.json'
'../../../frontend/platform/public/models/data.json'
)
cls.data_path = os.path.normpath(cls.data_path)
if not os.path.exists(cls.data_path):
Expand All @@ -181,6 +181,11 @@ def test_minimax_models_exist(self):
"""MiniMax models should be present in data.json."""
self.assertIn('minimax', self.model_data)

def test_minimax_has_m3_model(self):
"""MiniMax-M3 should be listed as an LLM model."""
model_names = [m['model_name'] for m in self.model_data['minimax']]
self.assertIn('MiniMax-M3', model_names)

def test_minimax_has_m27_model(self):
"""MiniMax-M2.7 should be listed as an LLM model."""
model_names = [m['model_name'] for m in self.model_data['minimax']]
Expand All @@ -191,33 +196,24 @@ def test_minimax_has_m27_highspeed_model(self):
model_names = [m['model_name'] for m in self.model_data['minimax']]
self.assertIn('MiniMax-M2.7-highspeed', model_names)

def test_minimax_has_m25_model(self):
"""MiniMax-M2.5 should still be listed for backward compatibility."""
def test_minimax_older_models_removed(self):
"""Older MiniMax models (M2.5 / M2.5-highspeed / Text-01) should be removed."""
model_names = [m['model_name'] for m in self.model_data['minimax']]
self.assertIn('MiniMax-M2.5', model_names)
for legacy in ('MiniMax-M2.5', 'MiniMax-M2.5-highspeed', 'MiniMax-Text-01'):
self.assertNotIn(legacy, model_names)

def test_minimax_has_m25_highspeed_model(self):
"""MiniMax-M2.5-highspeed should still be listed."""
model_names = [m['model_name'] for m in self.model_data['minimax']]
self.assertIn('MiniMax-M2.5-highspeed', model_names)

def test_minimax_has_text01_model(self):
"""MiniMax-Text-01 should still be listed for backward compatibility."""
model_names = [m['model_name'] for m in self.model_data['minimax']]
self.assertIn('MiniMax-Text-01', model_names)

def test_minimax_m27_is_first(self):
"""MiniMax-M2.7 should be the first (default) model."""
self.assertEqual(self.model_data['minimax'][0]['model_name'], 'MiniMax-M2.7')
def test_minimax_m3_is_first(self):
"""MiniMax-M3 should be the first (default) model."""
self.assertEqual(self.model_data['minimax'][0]['model_name'], 'MiniMax-M3')

def test_minimax_models_are_llm_type(self):
"""All MiniMax models should be of type 'llm'."""
for model in self.model_data['minimax']:
self.assertEqual(model['model_type'], 'llm')

def test_minimax_model_count(self):
"""MiniMax should have 5 models listed."""
self.assertEqual(len(self.model_data['minimax']), 5)
"""MiniMax should have 3 models listed (M3, M2.7, M2.7-highspeed)."""
self.assertEqual(len(self.model_data['minimax']), 3)


class TestFrontendCustomForm(unittest.TestCase):
Expand All @@ -227,7 +223,7 @@ class TestFrontendCustomForm(unittest.TestCase):
def setUpClass(cls):
cls.form_path = os.path.join(
os.path.dirname(__file__),
'../../frontend/platform/src/pages/ModelPage/manage/CustomForm.tsx'
'../../../frontend/platform/src/pages/ModelPage/manage/CustomForm.tsx'
)
cls.form_path = os.path.normpath(cls.form_path)
if not os.path.exists(cls.form_path):
Expand All @@ -250,7 +246,7 @@ class TestMiniMaxLLMServerTypeEnum(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.const_path = os.path.join(
os.path.dirname(__file__), '..', 'bisheng', 'llm', 'domain', 'const.py'
os.path.dirname(__file__), '..', '..', 'bisheng', 'llm', 'domain', 'const.py'
)
cls.const_path = os.path.normpath(cls.const_path)
with open(cls.const_path) as f:
Expand All @@ -267,7 +263,7 @@ class TestOpenAIParamsFunction(unittest.TestCase):
@classmethod
def setUpClass(cls):
llm_py_path = os.path.join(
os.path.dirname(__file__), '..', 'bisheng', 'llm', 'domain', 'llm', 'llm.py'
os.path.dirname(__file__), '..', '..', 'bisheng', 'llm', 'domain', 'llm', 'llm.py'
)
llm_py_path = os.path.normpath(llm_py_path)
with open(llm_py_path) as f:
Expand All @@ -288,7 +284,7 @@ def setUpClass(cls):
def test_extracts_api_key(self):
"""Should use openai_api_key from server config."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'test-minimax-key', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -297,7 +293,7 @@ def test_extracts_api_key(self):
def test_extracts_base_url(self):
"""Should use openai_api_base as base_url."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -306,7 +302,7 @@ def test_extracts_base_url(self):
def test_strips_trailing_slash(self):
"""Should strip trailing slash from base_url."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1/'},
{}
)
Expand All @@ -315,7 +311,7 @@ def test_strips_trailing_slash(self):
def test_stream_usage_true(self):
"""Should set stream_usage=True."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -324,7 +320,7 @@ def test_stream_usage_true(self):
def test_no_legacy_params(self):
"""Should not produce minimax_api_key or group_id."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -333,8 +329,7 @@ def test_no_legacy_params(self):

def test_preserves_model_name(self):
"""Model name should be preserved."""
for name in ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed',
'MiniMax-Text-01']:
for name in ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed']:
result = self._openai_params_fn(
{'model': name},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
Expand All @@ -345,17 +340,17 @@ def test_preserves_model_name(self):
def test_user_kwargs_applied(self):
"""User advanced kwargs should be merged."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5', 'temperature': 0.7},
{'model': 'MiniMax-M3', 'temperature': 0.7},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
{'user_kwargs': json.dumps({'max_tokens': 4096})}
)
self.assertEqual(result['temperature'], 0.7)
self.assertEqual(result['model'], 'MiniMax-M2.5')
self.assertEqual(result['model'], 'MiniMax-M3')

def test_empty_key_fallback(self):
"""Should fall back to 'empty' when API key is empty."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': '', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -364,7 +359,7 @@ def test_empty_key_fallback(self):
def test_proxy_passthrough(self):
"""Should pass through openai_proxy if configured."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1',
'openai_proxy': 'http://proxy:8080'},
{}
Expand All @@ -374,7 +369,7 @@ def test_proxy_passthrough(self):
def test_no_url_appending(self):
"""Should NOT append /chat/completions to the URL."""
result = self._openai_params_fn(
{'model': 'MiniMax-M2.5'},
{'model': 'MiniMax-M3'},
{'openai_api_key': 'key', 'openai_api_base': 'https://api.minimax.io/v1'},
{}
)
Expand All @@ -388,12 +383,12 @@ class TestMiniMaxIntegration(unittest.TestCase):
os.environ.get('MINIMAX_API_KEY'),
'MINIMAX_API_KEY not set'
)
def test_minimax_m27_chat_completion(self):
"""Test actual chat completion with MiniMax-M2.7 via OpenAI-compatible API."""
def test_minimax_m3_chat_completion(self):
"""Test actual chat completion with MiniMax-M3 via OpenAI-compatible API."""
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
model='MiniMax-M2.7',
model='MiniMax-M3',
api_key=os.environ['MINIMAX_API_KEY'],
base_url='https://api.minimax.io/v1',
temperature=0.1,
Expand All @@ -407,27 +402,29 @@ def test_minimax_m27_chat_completion(self):
os.environ.get('MINIMAX_API_KEY'),
'MINIMAX_API_KEY not set'
)
def test_minimax_m27_highspeed_chat_completion(self):
"""Test actual chat completion with MiniMax-M2.7-highspeed."""
def test_minimax_m3_streaming(self):
"""Test streaming chat completion with MiniMax-M3."""
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
model='MiniMax-M2.7-highspeed',
model='MiniMax-M3',
api_key=os.environ['MINIMAX_API_KEY'],
base_url='https://api.minimax.io/v1',
temperature=0.1,
max_tokens=64,
streaming=True,
)
response = llm.invoke('Say hello in one word.')
self.assertIsNotNone(response.content)
self.assertTrue(len(response.content) > 0)
chunks = list(llm.stream('Say hello in one word.'))
self.assertTrue(len(chunks) > 0)
full_content = ''.join(c.content for c in chunks)
self.assertTrue(len(full_content) > 0)

@unittest.skipUnless(
os.environ.get('MINIMAX_API_KEY'),
'MINIMAX_API_KEY not set'
)
def test_minimax_m27_streaming(self):
"""Test streaming chat completion with MiniMax-M2.7."""
def test_minimax_m27_chat_completion(self):
"""Test actual chat completion with MiniMax-M2.7 via OpenAI-compatible API."""
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
Expand All @@ -436,23 +433,21 @@ def test_minimax_m27_streaming(self):
base_url='https://api.minimax.io/v1',
temperature=0.1,
max_tokens=64,
streaming=True,
)
chunks = list(llm.stream('Say hello in one word.'))
self.assertTrue(len(chunks) > 0)
full_content = ''.join(c.content for c in chunks)
self.assertTrue(len(full_content) > 0)
response = llm.invoke('Say hello in one word.')
self.assertIsNotNone(response.content)
self.assertTrue(len(response.content) > 0)

@unittest.skipUnless(
os.environ.get('MINIMAX_API_KEY'),
'MINIMAX_API_KEY not set'
)
def test_minimax_m25_chat_completion(self):
"""Test actual chat completion with MiniMax-M2.5 via OpenAI-compatible API."""
def test_minimax_m27_highspeed_chat_completion(self):
"""Test actual chat completion with MiniMax-M2.7-highspeed."""
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
model='MiniMax-M2.5',
model='MiniMax-M2.7-highspeed',
api_key=os.environ['MINIMAX_API_KEY'],
base_url='https://api.minimax.io/v1',
temperature=0.1,
Expand Down
16 changes: 3 additions & 13 deletions src/frontend/platform/public/models/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,27 +81,17 @@
}],
"minimax": [{
"name": "model 1",
"model_name": "MiniMax-M2.7",
"model_name": "MiniMax-M3",
"model_type": "llm"
},
{
"name": "model 2",
"model_name": "MiniMax-M2.7-highspeed",
"model_name": "MiniMax-M2.7",
"model_type": "llm"
},
{
"name": "model 3",
"model_name": "MiniMax-M2.5",
"model_type": "llm"
},
{
"name": "model 4",
"model_name": "MiniMax-M2.5-highspeed",
"model_type": "llm"
},
{
"name": "model 5",
"model_name": "MiniMax-Text-01",
"model_name": "MiniMax-M2.7-highspeed",
"model_type": "llm"
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ const modelProviders = {
label: "Api Host",
type: "text",
placeholder: "",
default: "https://api.minimax.com/v1",
default: "https://api.minimax.io/v1",
required: true,
key: "openai_api_base",
},
Expand Down