From 86502f264563b21727ab2fe73015ce080c07ec5f Mon Sep 17 00:00:00 2001 From: PR Bot Date: Fri, 20 Mar 2026 21:10:20 +0800 Subject: [PATCH] feat: add MiniMax as built-in LLM provider Add MiniMax (https://www.minimaxi.com) as a first-class LLM provider with M2.7, M2.5, and M2.5-highspeed models via OpenAI-compatible API. Changes: - Backend: add MiniMax to DEFAULT_PROVIDERS_CONFIG with API URL and models - Frontend: add MiniMax to AddProviderDialog PROVIDER_OPTIONS with preset models - Frontend: add MiniMax to PROVIDER_NAME_MAPPING with icon - README: mention MiniMax in supported providers - Tests: 16 unit tests + 3 integration tests --- README.md | 2 +- README_zh.md | 2 +- .../components/settings/AddProviderDialog.tsx | 12 ++ react/src/constants.ts | 4 + server/services/config_service.py | 10 ++ server/tests/__init__.py | 0 server/tests/test_minimax_integration.py | 74 ++++++++++ server/tests/test_minimax_provider.py | 132 ++++++++++++++++++ 8 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 server/tests/__init__.py create mode 100644 server/tests/test_minimax_integration.py create mode 100644 server/tests/test_minimax_provider.py diff --git a/README.md b/README.md index 836da5257..e27d29074 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Star us, and you will receive all release notifications from GitHub without any 🎬 One-Prompt Image & Video Generation Turn one prompt into complete images or videos in seconds. - -Supports GPT-4o, Midjourney, VEO3, Kling,veo3,seedance etc. + -Supports GPT-4o, MiniMax, Midjourney, VEO3, Kling,veo3,seedance etc. -Auto-optimized prompts & multi-turn refinement diff --git a/README_zh.md b/README_zh.md index b7332620c..c6f8417ae 100644 --- a/README_zh.md +++ b/README_zh.md @@ -23,7 +23,7 @@ 由大语言模型(LLM)驱动,Jaaz 能理解你的想法并生成优化的提示词,用于创作高质量的图像或故事板。 混合模型部署 -支持通过 Ollama、ComfyUI 运行本地模型,也支持 Replicate、OpenAI 或 Claude 等远程 API。可实现 100% 本地运行或连接到云端。 +支持通过 Ollama、ComfyUI 运行本地模型,也支持 Replicate、OpenAI、Claude 或 MiniMax 等远程 API。可实现 100% 本地运行或连接到云端。 轻松接入所有最强 API 登录后,你可以使用所有最新的模型(gpt-image-1, flux kntext, google……)。 diff --git a/react/src/components/settings/AddProviderDialog.tsx b/react/src/components/settings/AddProviderDialog.tsx index 2b59b8958..a70131303 100644 --- a/react/src/components/settings/AddProviderDialog.tsx +++ b/react/src/components/settings/AddProviderDialog.tsx @@ -115,6 +115,18 @@ const PROVIDER_OPTIONS = [ }, }, }, + { + value: 'minimax', + label: 'MiniMax', + data: { + apiUrl: 'https://api.minimax.io/v1/', + models: { + 'MiniMax-M2.7': { type: 'text' }, + 'MiniMax-M2.5': { type: 'text' }, + 'MiniMax-M2.5-highspeed': { type: 'text' }, + }, + }, + }, { value: '硅基流动', label: '硅基流动 (SiliconFlow)', diff --git a/react/src/constants.ts b/react/src/constants.ts index 3b3258fd6..bdd55633f 100644 --- a/react/src/constants.ts +++ b/react/src/constants.ts @@ -40,6 +40,10 @@ export const PROVIDER_NAME_MAPPING: { name: 'ComfyUI', icon: 'https://framerusercontent.com/images/3cNQMWKzIhIrQ5KErBm7dSmbd2w.png', }, + minimax: { + name: 'MiniMax', + icon: 'https://www.minimaxi.com/favicon.ico', + }, } // Tool call name mapping diff --git a/server/services/config_service.py b/server/services/config_service.py index cb853278f..93ac8d8b5 100644 --- a/server/services/config_service.py +++ b/server/services/config_service.py @@ -59,6 +59,16 @@ class ProviderConfig(TypedDict, total=False): 'api_key': '', 'max_tokens': 8192, }, + 'minimax': { + 'models': { + 'MiniMax-M2.7': {'type': 'text'}, + 'MiniMax-M2.5': {'type': 'text'}, + 'MiniMax-M2.5-highspeed': {'type': 'text'}, + }, + 'url': 'https://api.minimax.io/v1/', + 'api_key': '', + 'max_tokens': 8192, + }, } diff --git a/server/tests/__init__.py b/server/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/server/tests/test_minimax_integration.py b/server/tests/test_minimax_integration.py new file mode 100644 index 000000000..58096366f --- /dev/null +++ b/server/tests/test_minimax_integration.py @@ -0,0 +1,74 @@ +"""Integration tests for MiniMax provider. + +These tests verify actual MiniMax API connectivity. +Requires MINIMAX_API_KEY environment variable to be set. +""" +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +MINIMAX_API_KEY = os.environ.get('MINIMAX_API_KEY', '') + + +@unittest.skipUnless(MINIMAX_API_KEY, 'MINIMAX_API_KEY not set') +class TestMiniMaxAPIIntegration(unittest.TestCase): + """Integration tests that call the real MiniMax API.""" + + def test_minimax_chat_completion(self): + """Test basic chat completion via OpenAI-compatible API.""" + from openai import OpenAI + + client = OpenAI( + api_key=MINIMAX_API_KEY, + base_url='https://api.minimax.io/v1/', + ) + response = client.chat.completions.create( + model='MiniMax-M2.5-highspeed', + messages=[{'role': 'user', 'content': 'Say hello in one word.'}], + max_tokens=10, + temperature=0, + ) + self.assertIsNotNone(response.choices) + self.assertGreater(len(response.choices), 0) + self.assertIsNotNone(response.choices[0].message.content) + + def test_minimax_model_list(self): + """Test that MiniMax models are accessible.""" + from openai import OpenAI + + client = OpenAI( + api_key=MINIMAX_API_KEY, + base_url='https://api.minimax.io/v1/', + ) + # Verify the API endpoint responds + response = client.chat.completions.create( + model='MiniMax-M2.5-highspeed', + messages=[{'role': 'user', 'content': 'Hi'}], + max_tokens=5, + temperature=0, + ) + self.assertEqual(response.model, 'MiniMax-M2.5-highspeed') + + def test_minimax_streaming(self): + """Test streaming chat completion.""" + from openai import OpenAI + + client = OpenAI( + api_key=MINIMAX_API_KEY, + base_url='https://api.minimax.io/v1/', + ) + stream = client.chat.completions.create( + model='MiniMax-M2.5-highspeed', + messages=[{'role': 'user', 'content': 'Say hi.'}], + max_tokens=10, + temperature=0, + stream=True, + ) + chunks = list(stream) + self.assertGreater(len(chunks), 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/server/tests/test_minimax_provider.py b/server/tests/test_minimax_provider.py new file mode 100644 index 000000000..73f587c57 --- /dev/null +++ b/server/tests/test_minimax_provider.py @@ -0,0 +1,132 @@ +"""Unit tests for MiniMax provider integration.""" +import os +import sys +import unittest + +# Add server directory to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + + +class TestMiniMaxDefaultConfig(unittest.TestCase): + """Test that MiniMax is properly configured in DEFAULT_PROVIDERS_CONFIG.""" + + def setUp(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + self.config = DEFAULT_PROVIDERS_CONFIG + + def test_minimax_provider_exists(self): + self.assertIn('minimax', self.config) + + def test_minimax_url(self): + self.assertEqual( + self.config['minimax']['url'], + 'https://api.minimax.io/v1/' + ) + + def test_minimax_has_text_models(self): + models = self.config['minimax']['models'] + self.assertIn('MiniMax-M2.7', models) + self.assertIn('MiniMax-M2.5', models) + self.assertIn('MiniMax-M2.5-highspeed', models) + + def test_minimax_models_are_text_type(self): + models = self.config['minimax']['models'] + for model_name, model_config in models.items(): + self.assertEqual( + model_config.get('type'), 'text', + f"Model {model_name} should be type 'text'" + ) + + def test_minimax_has_empty_api_key(self): + self.assertEqual(self.config['minimax']['api_key'], '') + + def test_minimax_max_tokens(self): + self.assertEqual(self.config['minimax']['max_tokens'], 8192) + + +class TestMiniMaxConfigService(unittest.TestCase): + """Test ConfigService handles MiniMax provider correctly.""" + + def test_config_service_includes_minimax(self): + from services.config_service import config_service + config = config_service.get_config() + self.assertIn('minimax', config) + + def test_minimax_not_marked_as_custom(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + minimax_config = DEFAULT_PROVIDERS_CONFIG['minimax'] + self.assertNotIn('is_custom', minimax_config) + + +class TestMiniMaxProviderRouting(unittest.TestCase): + """Test that MiniMax provider routes via OpenAI-compatible path (not Ollama). + + This tests the routing logic from _create_text_model() without importing + the full agent_service module (which has heavy dependencies). + """ + + def test_minimax_provider_is_not_ollama(self): + """MiniMax should not match the 'ollama' provider check.""" + provider = 'minimax' + self.assertNotEqual(provider, 'ollama') + + def test_minimax_openai_compatible_routing(self): + """Simulate the _create_text_model routing logic.""" + provider = 'minimax' + model = 'MiniMax-M2.7' + url = 'https://api.minimax.io/v1/' + + # This mirrors the logic in agent_service._create_text_model + if provider == 'ollama': + path = 'ollama' + else: + path = 'openai_compat' + + self.assertEqual(path, 'openai_compat') + + def test_all_minimax_models_route_correctly(self): + """All MiniMax models should use OpenAI-compatible path.""" + from services.config_service import DEFAULT_PROVIDERS_CONFIG + models = DEFAULT_PROVIDERS_CONFIG['minimax']['models'] + for model_name in models: + provider = 'minimax' + self.assertNotEqual(provider, 'ollama', + f"Model {model_name} should not route via Ollama") + + +class TestMiniMaxProviderConfig(unittest.TestCase): + """Test MiniMax provider configuration structure.""" + + def test_minimax_config_has_required_fields(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + minimax = DEFAULT_PROVIDERS_CONFIG['minimax'] + self.assertIn('models', minimax) + self.assertIn('url', minimax) + self.assertIn('api_key', minimax) + self.assertIn('max_tokens', minimax) + + def test_minimax_model_count(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + models = DEFAULT_PROVIDERS_CONFIG['minimax']['models'] + self.assertEqual(len(models), 3) + + def test_minimax_url_starts_with_https(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + url = DEFAULT_PROVIDERS_CONFIG['minimax']['url'] + self.assertTrue(url.startswith('https://')) + + def test_minimax_url_ends_with_slash(self): + from services.config_service import DEFAULT_PROVIDERS_CONFIG + url = DEFAULT_PROVIDERS_CONFIG['minimax']['url'] + self.assertTrue(url.endswith('/')) + + def test_minimax_config_matches_other_providers(self): + """MiniMax config should have the same structure as OpenAI config.""" + from services.config_service import DEFAULT_PROVIDERS_CONFIG + minimax = DEFAULT_PROVIDERS_CONFIG['minimax'] + openai = DEFAULT_PROVIDERS_CONFIG['openai'] + self.assertEqual(set(minimax.keys()), set(openai.keys())) + + +if __name__ == '__main__': + unittest.main()