feat: add MiniMax as LLM provider with auto-detection

Add MiniMax M2.7 and M2.7-highspeed models to the STRIX_MODEL_MAP for
strix/ prefix shortcuts, auto-detect MINIMAX_API_KEY and set the MiniMax
API base URL (https://api.minimax.io/v1) when a MiniMax model is selected.

Includes provider documentation page, overview/README updates, 17 unit
tests and 3 integration tests covering model mapping, config resolution,
API key auto-detection, and end-to-end streaming completion.
This commit is contained in:
PR Bot 2026-03-23 20:55:46 +08:00
parent 7d5a45deaf
commit 83e947d03e
7 changed files with 309 additions and 1 deletions

View file

@ -230,8 +230,9 @@ export STRIX_REASONING_EFFORT="high" # control thinking effort (default: high,
- [OpenAI GPT-5.4](https://openai.com/api/) — `openai/gpt-5.4`
- [Anthropic Claude Sonnet 4.6](https://claude.com/platform/api) — `anthropic/claude-sonnet-4-6`
- [Google Gemini 3 Pro Preview](https://cloud.google.com/vertex-ai) — `vertex_ai/gemini-3-pro-preview`
- [MiniMax-M2.7](https://platform.minimax.io) — `openai/MiniMax-M2.7` (set `LLM_API_BASE=https://api.minimax.io/v1`)
See the [LLM Providers documentation](https://docs.strix.ai/llm-providers/overview) for all supported providers including Vertex AI, Bedrock, Azure, and local models.
See the [LLM Providers documentation](https://docs.strix.ai/llm-providers/overview) for all supported providers including Vertex AI, Bedrock, Azure, MiniMax, and local models.
## Enterprise

View file

@ -0,0 +1,43 @@
---
title: "MiniMax"
description: "Configure Strix with MiniMax models"
---
[MiniMax](https://www.minimax.io) provides powerful large language models with up to 1M token context windows through an OpenAI-compatible API.
## Setup
```bash
export STRIX_LLM="openai/MiniMax-M2.7"
export LLM_API_KEY="your-minimax-api-key"
export LLM_API_BASE="https://api.minimax.io/v1"
```
Or use the shorthand with automatic base URL detection:
```bash
export STRIX_LLM="openai/MiniMax-M2.7"
export MINIMAX_API_KEY="your-minimax-api-key"
```
## Available Models
| Model | Configuration | Context Window |
|-------|---------------|----------------|
| MiniMax-M2.7 | `openai/MiniMax-M2.7` | 1M tokens |
| MiniMax-M2.7-highspeed | `openai/MiniMax-M2.7-highspeed` | 1M tokens |
**MiniMax-M2.7** is the latest flagship model with strong reasoning and coding capabilities.
**MiniMax-M2.7-highspeed** offers faster inference with slightly reduced quality.
## Get API Key
1. Go to [platform.minimax.io](https://platform.minimax.io)
2. Sign up or sign in
3. Navigate to API Keys and create a new key
## Notes
- MiniMax API is fully OpenAI-compatible, so it works via the `openai/` LiteLLM prefix
- Temperature range: 0.0 to 1.0 (inclusive)
- When `MINIMAX_API_KEY` is set and the model name contains "minimax", the API key and base URL are auto-detected

View file

@ -14,6 +14,7 @@ Set your model and API key:
| GPT-5.4 | OpenAI | `openai/gpt-5.4` |
| Claude Sonnet 4.6 | Anthropic | `anthropic/claude-sonnet-4-6` |
| Gemini 3 Pro | Google Vertex | `vertex_ai/gemini-3-pro-preview` |
| MiniMax-M2.7 | MiniMax | `openai/MiniMax-M2.7` |
```bash
export STRIX_LLM="openai/gpt-5.4"
@ -52,6 +53,9 @@ See the [Local Models guide](/llm-providers/local) for setup instructions and re
<Card title="Azure OpenAI" href="/llm-providers/azure">
GPT-5.4 via Azure.
</Card>
<Card title="MiniMax" href="/llm-providers/minimax">
MiniMax-M2.7 models with 1M context.
</Card>
<Card title="Local Models" href="/llm-providers/local">
Llama 4, Mistral, and self-hosted models.
</Card>

View file

@ -212,4 +212,17 @@ def resolve_llm_config() -> tuple[str | None, str | None, str | None]:
or Config.get("ollama_api_base")
)
# Auto-detect MiniMax provider: use MINIMAX_API_KEY and set base URL
if _is_minimax_model(model):
if not api_key:
api_key = os.getenv("MINIMAX_API_KEY")
if not api_base:
api_base = "https://api.minimax.io/v1"
return model, api_key, api_base
def _is_minimax_model(model: str) -> bool:
"""Check if the model name refers to a MiniMax model."""
lower = model.lower()
return "minimax" in lower

View file

@ -41,6 +41,8 @@ STRIX_MODEL_MAP: dict[str, str] = {
"gemini-3-flash-preview": "gemini/gemini-3-flash-preview",
"glm-5": "openrouter/z-ai/glm-5",
"glm-4.7": "openrouter/z-ai/glm-4.7",
"minimax-m2.7": "openai/MiniMax-M2.7",
"minimax-m2.7-highspeed": "openai/MiniMax-M2.7-highspeed",
}

167
tests/llm/test_minimax.py Normal file
View file

@ -0,0 +1,167 @@
"""Tests for MiniMax model integration."""
import os
import pytest
from strix.config.config import Config, _is_minimax_model, resolve_llm_config
from strix.llm.config import LLMConfig
from strix.llm.utils import STRIX_MODEL_MAP, resolve_strix_model
class TestMiniMaxModelMap:
"""Tests for MiniMax entries in STRIX_MODEL_MAP."""
def test_minimax_m27_in_model_map(self):
assert "minimax-m2.7" in STRIX_MODEL_MAP
assert STRIX_MODEL_MAP["minimax-m2.7"] == "openai/MiniMax-M2.7"
def test_minimax_m27_highspeed_in_model_map(self):
assert "minimax-m2.7-highspeed" in STRIX_MODEL_MAP
assert STRIX_MODEL_MAP["minimax-m2.7-highspeed"] == "openai/MiniMax-M2.7-highspeed"
class TestMiniMaxModelResolution:
"""Tests for resolving strix/ MiniMax models."""
def test_resolve_strix_minimax_m27(self):
api_model, canonical = resolve_strix_model("strix/minimax-m2.7")
assert api_model == "openai/minimax-m2.7"
assert canonical == "openai/MiniMax-M2.7"
def test_resolve_strix_minimax_m27_highspeed(self):
api_model, canonical = resolve_strix_model("strix/minimax-m2.7-highspeed")
assert api_model == "openai/minimax-m2.7-highspeed"
assert canonical == "openai/MiniMax-M2.7-highspeed"
def test_resolve_direct_minimax_model_passthrough(self):
api_model, canonical = resolve_strix_model("openai/MiniMax-M2.7")
assert api_model == "openai/MiniMax-M2.7"
assert canonical == "openai/MiniMax-M2.7"
class TestIsMiniMaxModel:
"""Tests for MiniMax model detection."""
def test_detects_minimax_openai_prefix(self):
assert _is_minimax_model("openai/MiniMax-M2.7")
def test_detects_minimax_case_insensitive(self):
assert _is_minimax_model("openai/minimax-m2.7")
def test_detects_minimax_strix_prefix(self):
assert _is_minimax_model("strix/minimax-m2.7")
def test_non_minimax_model(self):
assert not _is_minimax_model("openai/gpt-5.4")
def test_non_minimax_anthropic(self):
assert not _is_minimax_model("anthropic/claude-sonnet-4-6")
class TestMiniMaxConfigResolution:
"""Tests for MiniMax auto-detection in resolve_llm_config."""
def test_auto_detect_minimax_api_key(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7")
monkeypatch.setenv("MINIMAX_API_KEY", "test-minimax-key")
monkeypatch.delenv("LLM_API_KEY", raising=False)
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
model, api_key, api_base = resolve_llm_config()
assert model == "openai/MiniMax-M2.7"
assert api_key == "test-minimax-key"
assert api_base == "https://api.minimax.io/v1"
def test_llm_api_key_takes_precedence_over_minimax_key(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7")
monkeypatch.setenv("LLM_API_KEY", "llm-key-takes-precedence")
monkeypatch.setenv("MINIMAX_API_KEY", "minimax-key")
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
model, api_key, api_base = resolve_llm_config()
assert api_key == "llm-key-takes-precedence"
assert api_base == "https://api.minimax.io/v1"
def test_custom_api_base_takes_precedence(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7")
monkeypatch.setenv("MINIMAX_API_KEY", "test-key")
monkeypatch.setenv("LLM_API_BASE", "https://custom-proxy.com/v1")
monkeypatch.delenv("LLM_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
model, api_key, api_base = resolve_llm_config()
assert api_base == "https://custom-proxy.com/v1"
def test_no_minimax_key_no_auto_detect(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5.4")
monkeypatch.delenv("LLM_API_KEY", raising=False)
monkeypatch.delenv("MINIMAX_API_KEY", raising=False)
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
model, api_key, api_base = resolve_llm_config()
assert model == "openai/gpt-5.4"
assert api_key is None
assert api_base is None
def test_minimax_auto_base_url_when_no_base_set(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7-highspeed")
monkeypatch.setenv("LLM_API_KEY", "some-key")
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
model, api_key, api_base = resolve_llm_config()
assert api_base == "https://api.minimax.io/v1"
class TestMiniMaxLLMConfig:
"""Tests for LLMConfig with MiniMax models."""
def test_llm_config_minimax_direct(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7")
monkeypatch.setenv("LLM_API_KEY", "test-key")
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
config = LLMConfig()
assert config.model_name == "openai/MiniMax-M2.7"
assert config.litellm_model == "openai/MiniMax-M2.7"
assert config.api_key == "test-key"
assert config.api_base == "https://api.minimax.io/v1"
def test_llm_config_minimax_strix_shortcut(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("STRIX_LLM", "strix/minimax-m2.7")
monkeypatch.setenv("MINIMAX_API_KEY", "minimax-key")
monkeypatch.delenv("LLM_API_KEY", raising=False)
monkeypatch.delenv("LLM_API_BASE", raising=False)
monkeypatch.delenv("OPENAI_API_BASE", raising=False)
monkeypatch.delenv("LITELLM_BASE_URL", raising=False)
monkeypatch.delenv("OLLAMA_API_BASE", raising=False)
config = LLMConfig()
assert config.model_name == "strix/minimax-m2.7"
assert config.litellm_model == "openai/minimax-m2.7"
assert config.canonical_model == "openai/MiniMax-M2.7"
assert config.api_key == "minimax-key"

View file

@ -0,0 +1,78 @@
"""Integration tests for MiniMax provider.
These tests verify end-to-end MiniMax integration by making real API calls.
They require MINIMAX_API_KEY to be set in the environment.
"""
import os
import pytest
from strix.llm.config import LLMConfig
from strix.llm.llm import LLM
pytestmark = pytest.mark.skipif(
not os.environ.get("MINIMAX_API_KEY"),
reason="MINIMAX_API_KEY not set",
)
@pytest.fixture()
def minimax_llm(monkeypatch: pytest.MonkeyPatch) -> LLM:
"""Create an LLM instance configured for MiniMax."""
monkeypatch.setenv("STRIX_LLM", "openai/MiniMax-M2.7")
monkeypatch.setenv("LLM_API_KEY", os.environ.get("MINIMAX_API_KEY", ""))
monkeypatch.setenv("LLM_API_BASE", "https://api.minimax.io/v1")
monkeypatch.setenv("STRIX_TELEMETRY", "0")
config = LLMConfig()
return LLM(config, agent_name=None)
@pytest.mark.asyncio()
async def test_minimax_basic_completion(minimax_llm: LLM):
"""Test that MiniMax can complete a simple prompt."""
messages = [{"role": "user", "content": "Reply with exactly: hello"}]
responses = []
async for response in minimax_llm.generate(messages):
responses.append(response)
assert len(responses) > 0
final = responses[-1]
assert final.content
assert "hello" in final.content.lower()
@pytest.mark.asyncio()
async def test_minimax_streaming(minimax_llm: LLM):
"""Test that MiniMax streaming produces incremental responses."""
messages = [{"role": "user", "content": "Count from 1 to 3, one number per line."}]
responses = []
async for response in minimax_llm.generate(messages):
responses.append(response)
# Streaming should produce multiple intermediate responses
assert len(responses) >= 2
final = responses[-1]
assert "1" in final.content
assert "2" in final.content
assert "3" in final.content
@pytest.mark.asyncio()
async def test_minimax_config_auto_detection():
"""Test that MINIMAX_API_KEY auto-detection works end-to-end."""
api_key = os.environ.get("MINIMAX_API_KEY", "")
orig_llm_key = os.environ.pop("LLM_API_KEY", None)
orig_llm_base = os.environ.pop("LLM_API_BASE", None)
os.environ["STRIX_LLM"] = "openai/MiniMax-M2.7"
try:
config = LLMConfig()
assert config.api_key == api_key
assert config.api_base == "https://api.minimax.io/v1"
finally:
if orig_llm_key is not None:
os.environ["LLM_API_KEY"] = orig_llm_key
if orig_llm_base is not None:
os.environ["LLM_API_BASE"] = orig_llm_base