diff --git a/backend_api_python/app/config/api_keys.py b/backend_api_python/app/config/api_keys.py index 7ee779a25..91ef92013 100644 --- a/backend_api_python/app/config/api_keys.py +++ b/backend_api_python/app/config/api_keys.py @@ -99,6 +99,16 @@ def GROK_API_KEY(cls): from app.utils.config_loader import load_addon_config val = load_addon_config().get('grok', {}).get('api_key') return val if val else '' + + @property + def MINIMAX_API_KEY(cls): + """MiniMax API key""" + env_val = os.getenv('MINIMAX_API_KEY', '').strip() + if env_val: + return env_val + from app.utils.config_loader import load_addon_config + val = load_addon_config().get('minimax', {}).get('api_key') + return val if val else '' @property def TAVILY_API_KEYS(cls): diff --git a/backend_api_python/app/routes/settings.py b/backend_api_python/app/routes/settings.py index 879826d51..ed41b5199 100644 --- a/backend_api_python/app/routes/settings.py +++ b/backend_api_python/app/routes/settings.py @@ -131,6 +131,7 @@ def _refresh_runtime_services() -> None: {'value': 'google', 'label': 'Google Gemini'}, {'value': 'deepseek', 'label': 'DeepSeek'}, {'value': 'grok', 'label': 'xAI Grok'}, + {'value': 'minimax', 'label': 'MiniMax'}, ], 'description': 'Select your preferred LLM provider' }, @@ -263,6 +264,33 @@ def _refresh_runtime_services() -> None: 'description': 'xAI Grok API endpoint', 'group': 'grok' }, + # MiniMax + { + 'key': 'MINIMAX_API_KEY', + 'label': 'MiniMax API Key', + 'type': 'password', + 'required': False, + 'link': 'https://platform.minimax.io', + 'link_text': 'settings.link.getApiKey', + 'description': 'MiniMax API key', + 'group': 'minimax' + }, + { + 'key': 'MINIMAX_MODEL', + 'label': 'MiniMax Model', + 'type': 'text', + 'default': 'MiniMax-M2.7', + 'description': 'Model: MiniMax-M2.7, MiniMax-M2.7-highspeed', + 'group': 'minimax' + }, + { + 'key': 'MINIMAX_BASE_URL', + 'label': 'MiniMax Base URL', + 'type': 'text', + 'default': 'https://api.minimax.io/v1', + 'description': 'MiniMax API endpoint', + 'group': 'minimax' + }, # Common settings { 'key': 'OPENROUTER_TEMPERATURE', diff --git a/backend_api_python/app/services/llm.py b/backend_api_python/app/services/llm.py index 0d06a7741..b7c02be7a 100644 --- a/backend_api_python/app/services/llm.py +++ b/backend_api_python/app/services/llm.py @@ -1,6 +1,6 @@ """ LLM service. -Supports multiple providers: OpenRouter, OpenAI, Google Gemini, DeepSeek, Grok. +Supports multiple providers: OpenRouter, OpenAI, Google Gemini, DeepSeek, Grok, MiniMax. Kept separate from AnalysisService to avoid circular imports. """ import json @@ -23,6 +23,7 @@ class LLMProvider(Enum): GOOGLE = "google" DEEPSEEK = "deepseek" GROK = "grok" + MINIMAX = "minimax" # Provider configurations @@ -52,6 +53,11 @@ class LLMProvider(Enum): "default_model": "grok-beta", "fallback_model": "grok-beta", }, + LLMProvider.MINIMAX: { + "base_url": "https://api.minimax.io/v1", + "default_model": "MiniMax-M2.7", + "fallback_model": "MiniMax-M2.7-highspeed", + }, } @@ -61,9 +67,9 @@ class LLMService: def __init__(self, provider: str = None): """ Initialize LLM service. - + Args: - provider: Override the default provider (openrouter, openai, google, deepseek, grok) + provider: Override the default provider (openrouter, openai, google, deepseek, grok, minimax) """ self._provider_override = provider @@ -90,10 +96,11 @@ def provider(self) -> LLMProvider: pass # Auto-detect: find any provider with a configured API key - # Priority: DeepSeek > Grok > OpenAI > Google > OpenRouter + # Priority: DeepSeek > Grok > MiniMax > OpenAI > Google > OpenRouter priority_order = [ LLMProvider.DEEPSEEK, LLMProvider.GROK, + LLMProvider.MINIMAX, LLMProvider.OPENAI, LLMProvider.GOOGLE, LLMProvider.OPENROUTER, @@ -117,6 +124,7 @@ def get_api_key(self, provider: LLMProvider = None) -> str: LLMProvider.GOOGLE: APIKeys.GOOGLE_API_KEY, LLMProvider.DEEPSEEK: APIKeys.DEEPSEEK_API_KEY, LLMProvider.GROK: APIKeys.GROK_API_KEY, + LLMProvider.MINIMAX: APIKeys.MINIMAX_API_KEY, } return key_map.get(p, "") or "" @@ -308,6 +316,7 @@ def _normalize_model_for_provider(self, model: str, provider: LLMProvider) -> st 'deepseek': LLMProvider.DEEPSEEK, 'x-ai': LLMProvider.GROK, 'xai': LLMProvider.GROK, + 'minimax': LLMProvider.MINIMAX, } # If the model prefix matches the current provider, use the extracted model name @@ -339,6 +348,7 @@ def _detect_provider_from_model(self, model: str) -> Optional[LLMProvider]: 'deepseek': LLMProvider.DEEPSEEK, 'x-ai': LLMProvider.GROK, 'xai': LLMProvider.GROK, + 'minimax': LLMProvider.MINIMAX, 'anthropic': LLMProvider.OPENROUTER, # Anthropic only via OpenRouter 'meta': LLMProvider.OPENROUTER, # Meta/Llama only via OpenRouter 'mistral': LLMProvider.OPENROUTER, # Mistral only via OpenRouter @@ -399,7 +409,7 @@ def call_llm_api(self, messages: list, model: str = None, temperature: float = 0 ) # If no API key for current provider, try to find any available provider if try_alternative_providers: - for alt_provider in [LLMProvider.DEEPSEEK, LLMProvider.GROK, LLMProvider.OPENAI, LLMProvider.GOOGLE, LLMProvider.OPENROUTER]: + for alt_provider in [LLMProvider.DEEPSEEK, LLMProvider.GROK, LLMProvider.MINIMAX, LLMProvider.OPENAI, LLMProvider.GOOGLE, LLMProvider.OPENROUTER]: if alt_provider != p and self.get_api_key(alt_provider): logger.warning(f"No API key for {p.value}, switching to {alt_provider.value}") p = alt_provider @@ -494,12 +504,13 @@ def _try_alternative_providers(self, messages: list, model: str, temperature: fl use_json_mode: bool, excluded_provider: LLMProvider = None) -> str: """ Try alternative providers when current provider fails. - - Priority: DeepSeek > Grok > OpenAI > Google > OpenRouter + + Priority: DeepSeek > Grok > MiniMax > OpenAI > Google > OpenRouter """ priority_order = [ LLMProvider.DEEPSEEK, LLMProvider.GROK, + LLMProvider.MINIMAX, LLMProvider.OPENAI, LLMProvider.GOOGLE, LLMProvider.OPENROUTER, diff --git a/backend_api_python/app/utils/config_loader.py b/backend_api_python/app/utils/config_loader.py index 81b7d4e35..acfffbf8d 100644 --- a/backend_api_python/app/utils/config_loader.py +++ b/backend_api_python/app/utils/config_loader.py @@ -88,6 +88,11 @@ def env_get(name: str) -> Optional[str]: ('GROK_API_KEY', 'grok.api_key', 'string'), ('GROK_BASE_URL', 'grok.base_url', 'string'), ('GROK_MODEL', 'grok.model', 'string'), + + # MiniMax + ('MINIMAX_API_KEY', 'minimax.api_key', 'string'), + ('MINIMAX_BASE_URL', 'minimax.base_url', 'string'), + ('MINIMAX_MODEL', 'minimax.model', 'string'), # LLM Provider Selection ('LLM_PROVIDER', 'llm.provider', 'string'), diff --git a/backend_api_python/env.example b/backend_api_python/env.example index 9a47f7364..aead8d7bf 100644 --- a/backend_api_python/env.example +++ b/backend_api_python/env.example @@ -82,6 +82,9 @@ DEEPSEEK_MODEL=deepseek-chat GROK_API_KEY= GROK_MODEL=grok-beta +MINIMAX_API_KEY= +MINIMAX_MODEL=MiniMax-M2.7 + # ========================= # Common background jobs # ========================= @@ -192,6 +195,7 @@ OPENROUTER_CONNECT_TIMEOUT=30 OPENAI_BASE_URL=https://api.openai.com/v1 DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 GROK_BASE_URL=https://api.x.ai/v1 +MINIMAX_BASE_URL=https://api.minimax.io/v1 # Data sources DATA_SOURCE_TIMEOUT=30