-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: add MiniMax as LLM provider with auto-detection #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+60
to
+76
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The other two integration tests in this file use a Prompt To Fix With AIThis is a comment left during a code review.
Path: tests/llm/test_minimax_integration.py
Line: 60-76
Comment:
**`STRIX_LLM` not restored in test teardown**
`os.environ["STRIX_LLM"]` is set on line 63 but is never restored in the `finally` block. If this test runs in an environment where `MINIMAX_API_KEY` is present, `STRIX_LLM` will remain as `"openai/MiniMax-M2.7"` for any subsequently executed test, potentially causing failures in other integration tests that rely on a different `STRIX_LLM` value.
The other two integration tests in this file use a `monkeypatch` fixture (which auto-restores on teardown), but this function bypasses it and does not capture or restore the original value of `STRIX_LLM`.
How can I resolve this? If you propose a fix, please make it concise. |
||
| if orig_llm_base is not None: | ||
| os.environ["LLM_API_BASE"] = orig_llm_base | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
STRIX_LLM="strix/minimax-m2.7"is configured, the earlier block setsapi_base = STRIX_API_BASE(the Strix gateway). The new MiniMax detection block then runs unconditionally: theapi_baseoverride is correctly skipped (since it's already set), but if noLLM_API_KEYis present,MINIMAX_API_KEYis picked up and used as theapi_keyfor a call that goes to the Strix gateway.Unless the Strix gateway is designed to receive and forward a raw provider key, this will cause authentication failures for
strix/minimax-*users who only haveMINIMAX_API_KEYset. Consider adding anot model.startswith("strix/")guard before the MiniMax detection block so it only fires for direct provider calls.Prompt To Fix With AI