fix(minimax): correct API endpoint and add region selection (#158)
This commit is contained in:
+2
-1
@@ -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 (阿里云)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
# 网络搜索(可选)
|
||||
|
||||
@@ -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)."""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user