fix(minimax): correct API endpoint and add region selection (#158)

This commit is contained in:
dinos
2026-04-15 14:30:26 +02:00
committed by GitHub
parent c4237fecb8
commit 2961e5ee88
8 changed files with 107 additions and 17 deletions
+2 -1
View File
@@ -7,7 +7,8 @@ GOOGLE_API_KEY= # aistudio.google.com/api-keys
NVIDIA_API_KEY= # build.nvidia.com
# Direct providers (optional)
MINIMAX_API_KEY= # platform.minimaxi.com (Anthropic-compatible)
MINIMAX_API_KEY= # platform.minimaxi.com (China, default) or platform.minimax.io (Global)
MINIMAX_BASE_URL= # https://api.minimaxi.com/anthropic (default) or https://api.minimax.io/anthropic
ZHIPU_API_KEY= # open.bigmodel.cn (智谱)
VOLCENGINE_API_KEY= # volcengine.com (火山引擎)
DASHSCOPE_API_KEY= # dashscope.aliyuncs.com (阿里云)
+80 -12
View File
@@ -278,10 +278,19 @@ def validate_google_key(api_key: str) -> tuple[bool, str]:
return False, f"Error: {e}"
def validate_minimax_key(api_key: str) -> tuple[bool, str]:
"""Validate a MiniMax API key by making a test request.
def validate_minimax_key(
api_key: str,
base_url: str = "https://api.minimaxi.com/anthropic",
) -> tuple[bool, str]:
"""Validate a MiniMax API key without consuming tokens.
Uses the Anthropic-compatible endpoint at api.minimaxi.com.
Sends a messages.create() with an empty model string. MiniMax checks
auth *before* validating request params, so a valid key returns 400
(bad model) while an invalid key returns 401.
Args:
api_key: The MiniMax API key to validate.
base_url: Anthropic-compatible endpoint (global or mainland China).
Returns:
Tuple of (is_valid, message).
@@ -294,16 +303,22 @@ def validate_minimax_key(api_key: str) -> tuple[bool, str]:
client = anthropic.Anthropic(
api_key=api_key,
base_url="https://api.minimaxi.com/anthropic",
base_url=base_url,
)
client.models.list()
client.messages.create(
model="",
max_tokens=1,
messages=[{"role": "user", "content": "hi"}],
)
# Unexpected success — treat as valid
return True, "Valid"
except anthropic.AuthenticationError:
return False, "Invalid API key"
except anthropic.APIStatusError:
# Any non-auth HTTP error (400 bad model, 500 insufficient balance,
# etc.) means the key itself was accepted → treat as valid.
return True, "Valid"
except Exception as e:
error_str = str(e).lower()
if any(
k in error_str for k in ("401", "unauthorized", "invalid", "authentication")
):
return False, "Invalid API key"
return False, f"Error: {e}"
@@ -758,6 +773,51 @@ def _step_provider(config: EvoScientistConfig) -> str:
return provider
_MINIMAX_REGIONS: dict[str, str] = {
"global": "https://api.minimax.io/anthropic",
"cn": "https://api.minimaxi.com/anthropic",
}
def _step_minimax_region(config: EvoScientistConfig) -> str:
"""Step 2a (MiniMax): Select API region.
MiniMax has two regional endpoints — Global (api.minimax.io) and
Mainland China (api.minimaxi.com). API keys are region-bound.
Returns:
The selected base URL.
"""
current = config.minimax_base_url or os.environ.get("MINIMAX_BASE_URL", "")
if current == _MINIMAX_REGIONS["global"]:
default = "global"
else:
default = "cn"
region = questionary.select(
"Select MiniMax API region (must match where your key was created):",
choices=[
Choice(
title="Global (api.minimax.io — platform.minimax.io keys)",
value="global",
),
Choice(
title="Mainland China (api.minimaxi.com — platform.minimaxi.com keys)",
value="cn",
),
],
default=default,
style=WIZARD_STYLE,
qmark=QMARK,
use_indicator=True,
).ask()
if region is None:
raise KeyboardInterrupt()
return _MINIMAX_REGIONS[region]
def _provider_key_info(config: EvoScientistConfig, provider: str):
"""Return (display_name, current_value, validate_fn) for a provider."""
mapping = {
@@ -769,7 +829,13 @@ def _provider_key_info(config: EvoScientistConfig, provider: str):
"minimax": (
"MiniMax",
config.minimax_api_key or os.environ.get("MINIMAX_API_KEY", ""),
validate_minimax_key,
lambda key: validate_minimax_key(
key,
base_url=config.minimax_base_url
or os.environ.get(
"MINIMAX_BASE_URL", "https://api.minimaxi.com/anthropic"
),
),
),
"nvidia": (
"NVIDIA",
@@ -2819,7 +2885,7 @@ def run_onboard(skip_validation: bool = False) -> bool:
provider = _step_provider(config)
config.provider = provider
# Step 2a: Base URL (custom-openai, custom-anthropic, or ollama provider)
# Step 2a: Base URL (custom-openai, custom-anthropic, minimax, or ollama)
ollama_detected_models: list[str] = []
if provider == "custom-openai":
current_base_url = config.custom_openai_base_url or os.environ.get(
@@ -2833,6 +2899,8 @@ def run_onboard(skip_validation: bool = False) -> bool:
)
base_url = _step_base_url(config, current_value=current_base_url)
config.custom_anthropic_base_url = base_url
elif provider == "minimax":
config.minimax_base_url = _step_minimax_region(config)
elif provider == "ollama":
ollama_url, ollama_detected_models = _step_ollama_base_url(config)
config.ollama_base_url = ollama_url
+4
View File
@@ -67,6 +67,7 @@ class EvoScientistConfig:
nvidia_api_key: str = ""
google_api_key: str = ""
minimax_api_key: str = ""
minimax_base_url: str = ""
siliconflow_api_key: str = ""
openrouter_api_key: str = ""
deepseek_api_key: str = ""
@@ -365,6 +366,7 @@ _ENV_MAPPINGS = {
"nvidia_api_key": "NVIDIA_API_KEY",
"google_api_key": "GOOGLE_API_KEY",
"minimax_api_key": "MINIMAX_API_KEY",
"minimax_base_url": "MINIMAX_BASE_URL",
"siliconflow_api_key": "SILICONFLOW_API_KEY",
"openrouter_api_key": "OPENROUTER_API_KEY",
"deepseek_api_key": "DEEPSEEK_API_KEY",
@@ -455,6 +457,8 @@ def apply_config_to_env(config: EvoScientistConfig) -> None:
os.environ["GOOGLE_API_KEY"] = config.google_api_key
if config.minimax_api_key and not os.environ.get("MINIMAX_API_KEY"):
os.environ["MINIMAX_API_KEY"] = config.minimax_api_key
if config.minimax_base_url and not os.environ.get("MINIMAX_BASE_URL"):
os.environ["MINIMAX_BASE_URL"] = config.minimax_base_url
if config.siliconflow_api_key and not os.environ.get("SILICONFLOW_API_KEY"):
os.environ["SILICONFLOW_API_KEY"] = config.siliconflow_api_key
if config.openrouter_api_key and not os.environ.get("OPENROUTER_API_KEY"):
+3 -1
View File
@@ -102,7 +102,7 @@ _MODEL_ENTRIES: list[tuple[str, str, str]] = [
("gemini-2.5-flash", "gemini-2.5-flash", "google-genai"),
("gemini-2.5-flash-lite", "gemini-2.5-flash-lite", "google-genai"),
("gemini-2.5-pro", "gemini-2.5-pro", "google-genai"),
# MiniMax (direct API — Anthropic-compatible at api.minimaxi.com)
# MiniMax (direct API — Anthropic-compatible; default: api.minimaxi.com, global: api.minimax.io)
("minimax-m2.7", "MiniMax-M2.7", "minimax"),
("minimax-m2.7-highspeed", "MiniMax-M2.7-highspeed", "minimax"),
("minimax-m2.5", "MiniMax-M2.5", "minimax"),
@@ -392,6 +392,8 @@ def get_chat_model(
"Anthropic-compatible API endpoint URL (e.g. https://api.anthropic.com)."
)
base_url = base_url.rstrip("/")
elif provider == "minimax":
base_url = os.environ.get("MINIMAX_BASE_URL", base_url_default).rstrip("/")
else:
base_url = base_url_default
if base_url:
+2 -1
View File
@@ -264,7 +264,8 @@ Set at least one LLM provider key and (optionally) a search key:
export ANTHROPIC_API_KEY="sk-..." # Claude — console.anthropic.com
export OPENAI_API_KEY="sk-..." # GPT — platform.openai.com
export GOOGLE_API_KEY="AI..." # Gemini — aistudio.google.com/api-keys
export MINIMAX_API_KEY="sk-..." # MiniMax — platform.minimaxi.com (Anthropic-compatible)
export MINIMAX_API_KEY="sk-..." # MiniMax — platform.minimaxi.com (China, default) or platform.minimax.io (Global)
export MINIMAX_BASE_URL="https://api.minimax.io/anthropic" # only needed for Global keys (default: https://api.minimaxi.com/anthropic)
export NVIDIA_API_KEY="nvapi-..." # NIM — build.nvidia.com
# Web search (optional)
+2 -1
View File
@@ -273,7 +273,8 @@ EvoSci onboard
export ANTHROPIC_API_KEY="sk-..." # Claude — console.anthropic.com
export OPENAI_API_KEY="sk-..." # GPT — platform.openai.com
export GOOGLE_API_KEY="AI..." # Gemini — aistudio.google.com/api-keys
export MINIMAX_API_KEY="sk-..." # MiniMax — platform.minimaxi.com (Anthropic-compatible)
export MINIMAX_API_KEY="sk-..." # MiniMax — platform.minimaxi.com(默认,中国大陆)或 platform.minimax.io(国际版)
export MINIMAX_BASE_URL="https://api.minimax.io/anthropic" # 仅国际版需要设置(默认: https://api.minimaxi.com/anthropic)
export NVIDIA_API_KEY="nvapi-..." # NIM — build.nvidia.com
# 网络搜索(可选)
+12
View File
@@ -527,6 +527,18 @@ class TestThirdPartyRouting:
assert call_kwargs["base_url"] == "https://api.minimaxi.com/anthropic"
assert call_kwargs["api_key"] == "mm-key-123"
@patch("EvoScientist.llm.models.init_chat_model")
def test_minimax_base_url_env_override(self, mock_init, monkeypatch):
"""MINIMAX_BASE_URL env var should override the default base URL."""
mock_init.return_value = "mock_model"
monkeypatch.setenv("MINIMAX_API_KEY", "mm-key-123")
monkeypatch.setenv("MINIMAX_BASE_URL", "https://api.minimax.io/anthropic")
get_chat_model("MiniMax-M2.5", provider="minimax")
call_kwargs = mock_init.call_args[1]
assert call_kwargs["base_url"] == "https://api.minimax.io/anthropic"
@patch("EvoScientist.llm.models.init_chat_model")
def test_minimax_gets_thinking(self, mock_init, monkeypatch):
"""MiniMax provider should get auto-thinking (thinking-capable via Anthropic)."""
+2 -1
View File
@@ -1,9 +1,10 @@
"""Integration tests for MiniMax direct provider.
These tests validate that the MiniMax provider can connect to the real
MiniMax API (api.minimaxi.com/anthropic) and produce chat completions.
MiniMax API (api.minimaxi.com/anthropic by default) and produce chat completions.
Requires MINIMAX_API_KEY environment variable to be set.
Optionally set MINIMAX_BASE_URL to https://api.minimax.io/anthropic for Global keys.
"""
import os