Skip to content
Merged
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
10 changes: 10 additions & 0 deletions backend_api_python/app/config/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
28 changes: 28 additions & 0 deletions backend_api_python/app/routes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
Expand Down Expand Up @@ -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',
Expand Down
25 changes: 18 additions & 7 deletions backend_api_python/app/services/llm.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -23,6 +23,7 @@ class LLMProvider(Enum):
GOOGLE = "google"
DEEPSEEK = "deepseek"
GROK = "grok"
MINIMAX = "minimax"


# Provider configurations
Expand Down Expand Up @@ -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",
},
}


Expand All @@ -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

Expand All @@ -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,
Expand All @@ -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 ""

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions backend_api_python/app/utils/config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
4 changes: 4 additions & 0 deletions backend_api_python/env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
# =========================
Expand Down Expand Up @@ -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
Expand Down
Loading