mirror of
https://github.com/usestrix/strix.git
synced 2026-09-11 22:51:18 +00:00
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:
parent
7d5a45deaf
commit
83e947d03e
7 changed files with 309 additions and 1 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
43
docs/llm-providers/minimax.mdx
Normal file
43
docs/llm-providers/minimax.mdx
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
167
tests/llm/test_minimax.py
Normal 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"
|
||||
78
tests/llm/test_minimax_integration.py
Normal file
78
tests/llm/test_minimax_integration.py
Normal 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
|
||||
Loading…
Add table
Reference in a new issue