mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-10-09 03:17:51 +00:00
feat: upgrade MiniMax default model to M3
- Add MiniMax-M3 to docs/examples and set as default model - Keep MiniMax-M2.7 and MiniMax-M2.7-highspeed as alternatives - Update provider registry comment to mention M3 - Expand unit/integration tests to cover M3 alongside legacy models Co-Authored-By: Octopus <liyuan851277048@icloud.com>
This commit is contained in:
parent
36005e3958
commit
7387948097
6 changed files with 71 additions and 27 deletions
|
|
@ -210,8 +210,8 @@ openspace
|
|||
# Execute task
|
||||
openspace --model "anthropic/claude-sonnet-4-5" --query "Create a monitoring dashboard for my Docker containers"
|
||||
|
||||
# Use MiniMax (high-performance, 204K context)
|
||||
openspace --model "minimax/MiniMax-M2.7" --query "Build a REST API with FastAPI"
|
||||
# Use MiniMax (latest M3, 512K context with image input)
|
||||
openspace --model "minimax/MiniMax-M3" --query "Build a REST API with FastAPI"
|
||||
```
|
||||
|
||||
Add your own custom skills: [`openspace/skills/README.md`](openspace/skills/README.md).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
# OpenRouter (for openrouter/* models, e.g. openrouter/anthropic/claude-sonnet-4.5)
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
# MiniMax (for minimax/* models, e.g. minimax/MiniMax-M2.7)
|
||||
# MiniMax (for minimax/* models, e.g. minimax/MiniMax-M3)
|
||||
# MINIMAX_API_KEY=
|
||||
|
||||
# ── OpenSpace Cloud (optional) ──────────────────────────────
|
||||
|
|
|
|||
|
|
@ -123,16 +123,17 @@ OpenSpace uses [LiteLLM](https://docs.litellm.ai/docs/providers) for model routi
|
|||
| Anthropic | `anthropic/claude-sonnet-4-5` | `ANTHROPIC_API_KEY` |
|
||||
| OpenAI | `openai/gpt-4o` | `OPENAI_API_KEY` |
|
||||
| DeepSeek | `deepseek/deepseek-chat` | `DEEPSEEK_API_KEY` |
|
||||
| MiniMax | `minimax/MiniMax-M2.7` | `MINIMAX_API_KEY` |
|
||||
| MiniMax | `minimax/MiniMax-M3` | `MINIMAX_API_KEY` |
|
||||
|
||||
### MiniMax
|
||||
|
||||
[MiniMax](https://platform.minimax.io) offers high-performance LLMs with 204K context at competitive pricing.
|
||||
[MiniMax](https://platform.minimax.io) offers high-performance LLMs at competitive pricing.
|
||||
|
||||
**Available models:**
|
||||
|
||||
| Model | Context | Description |
|
||||
|-------|---------|-------------|
|
||||
| `MiniMax-M3` | 512K | Latest model, max output 128K, supports image input (default) |
|
||||
| `MiniMax-M2.7` | 204K | Peak performance, ultimate value |
|
||||
| `MiniMax-M2.7-highspeed` | 204K | Same performance, faster and more agile |
|
||||
|
||||
|
|
@ -143,7 +144,7 @@ OpenSpace uses [LiteLLM](https://docs.litellm.ai/docs/providers) for model routi
|
|||
export MINIMAX_API_KEY=your-key-here
|
||||
|
||||
# Run with MiniMax
|
||||
openspace --model "minimax/MiniMax-M2.7" --query "your task"
|
||||
openspace --model "minimax/MiniMax-M3" --query "your task"
|
||||
```
|
||||
|
||||
**API docs:** [OpenAI-compatible API](https://platform.minimax.io/docs/api-reference/text-openai-api)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ PROVIDER_REGISTRY: List[tuple] = [
|
|||
("zhipu", ("zhipu", "glm", "zai"), ""),
|
||||
("dashscope", ("qwen", "dashscope"), ""),
|
||||
("moonshot", ("moonshot", "kimi"), "https://api.moonshot.ai/v1"),
|
||||
("minimax", ("minimax",), "https://api.minimax.io/v1"), # MiniMax-M2.7, MiniMax-M2.7-highspeed
|
||||
("minimax", ("minimax",), "https://api.minimax.io/v1"), # MiniMax-M3 (default), MiniMax-M2.7, MiniMax-M2.7-highspeed
|
||||
("groq", ("groq",), ""),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,23 @@ SKIP_REASON = "MINIMAX_API_KEY not set"
|
|||
class TestMiniMaxChatIntegration(unittest.TestCase):
|
||||
"""Integration tests for MiniMax chat completions via litellm."""
|
||||
|
||||
def test_m3_chat_completion(self):
|
||||
"""MiniMax M3 should return a valid chat completion."""
|
||||
import litellm
|
||||
|
||||
response = litellm.completion(
|
||||
model="minimax/MiniMax-M3",
|
||||
messages=[{"role": "user", "content": "What is 2+2? Reply with just the number."}],
|
||||
max_tokens=50,
|
||||
temperature=0.5,
|
||||
api_key=MINIMAX_API_KEY,
|
||||
)
|
||||
self.assertTrue(response.choices)
|
||||
content = response.choices[0].message.content
|
||||
self.assertIsNotNone(content)
|
||||
|
||||
def test_basic_chat_completion(self):
|
||||
"""MiniMax M2.7 should return a valid chat completion."""
|
||||
"""MiniMax M2.7 should still return a valid chat completion."""
|
||||
import litellm
|
||||
|
||||
response = litellm.completion(
|
||||
|
|
@ -46,11 +61,11 @@ class TestMiniMaxChatIntegration(unittest.TestCase):
|
|||
self.assertTrue(response.choices[0].message.content)
|
||||
|
||||
def test_llm_client_with_minimax(self):
|
||||
"""LLMClient should work with MiniMax models (temperature clamping applied)."""
|
||||
"""LLMClient should work with MiniMax M3 (temperature clamping applied)."""
|
||||
from openspace.llm.client import LLMClient
|
||||
|
||||
client = LLMClient(
|
||||
model="minimax/MiniMax-M2.7-highspeed",
|
||||
model="minimax/MiniMax-M3",
|
||||
timeout=30.0,
|
||||
max_retries=1,
|
||||
api_key=MINIMAX_API_KEY,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ from openspace.host_detection.resolver import build_llm_kwargs
|
|||
class TestIsMiniMaxModel(unittest.TestCase):
|
||||
"""Tests for MiniMax model detection."""
|
||||
|
||||
def test_minimax_m3_model(self):
|
||||
self.assertTrue(_is_minimax_model("minimax/MiniMax-M3"))
|
||||
|
||||
def test_minimax_prefixed_model(self):
|
||||
self.assertTrue(_is_minimax_model("minimax/MiniMax-M2.7"))
|
||||
|
||||
|
|
@ -22,10 +25,10 @@ class TestIsMiniMaxModel(unittest.TestCase):
|
|||
self.assertTrue(_is_minimax_model("minimax/MiniMax-M2.7-highspeed"))
|
||||
|
||||
def test_minimax_case_insensitive(self):
|
||||
self.assertTrue(_is_minimax_model("MiniMax/MiniMax-M2.7"))
|
||||
self.assertTrue(_is_minimax_model("MiniMax/MiniMax-M3"))
|
||||
|
||||
def test_minimax_in_openai_compat(self):
|
||||
self.assertTrue(_is_minimax_model("openai/MiniMax-M2.7"))
|
||||
self.assertTrue(_is_minimax_model("openai/MiniMax-M3"))
|
||||
|
||||
def test_non_minimax_openai_model(self):
|
||||
self.assertFalse(_is_minimax_model("openai/gpt-4o"))
|
||||
|
|
@ -45,44 +48,44 @@ class TestApplyMiniMaxConstraints(unittest.TestCase):
|
|||
|
||||
def test_clamp_temperature_zero(self):
|
||||
"""Temperature 0 should be clamped to 0.01."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": 0}
|
||||
kwargs = {"model": "minimax/MiniMax-M3", "temperature": 0}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 0.01)
|
||||
|
||||
def test_clamp_temperature_negative(self):
|
||||
"""Negative temperature should be clamped to 0.01."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": -0.5}
|
||||
kwargs = {"model": "minimax/MiniMax-M3", "temperature": -0.5}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 0.01)
|
||||
|
||||
def test_clamp_temperature_above_one(self):
|
||||
"""Temperature > 1.0 should be clamped to 1.0."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": 1.5}
|
||||
kwargs = {"model": "minimax/MiniMax-M3", "temperature": 1.5}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 1.0)
|
||||
|
||||
def test_valid_temperature_unchanged(self):
|
||||
"""Valid temperature (0 < t <= 1.0) should remain unchanged."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": 0.7}
|
||||
kwargs = {"model": "minimax/MiniMax-M3", "temperature": 0.7}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 0.7)
|
||||
|
||||
def test_temperature_one_unchanged(self):
|
||||
"""Temperature 1.0 is valid and should remain unchanged."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": 1.0}
|
||||
kwargs = {"model": "minimax/MiniMax-M3", "temperature": 1.0}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 1.0)
|
||||
|
||||
def test_no_temperature_no_change(self):
|
||||
"""When temperature is not set, no clamping should occur."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7"}
|
||||
kwargs = {"model": "minimax/MiniMax-M3"}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertNotIn("temperature", result)
|
||||
|
||||
def test_remove_response_format(self):
|
||||
"""response_format should be removed for MiniMax models."""
|
||||
kwargs = {
|
||||
"model": "minimax/MiniMax-M2.7",
|
||||
"model": "minimax/MiniMax-M3",
|
||||
"response_format": {"type": "json_object"},
|
||||
}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
|
|
@ -91,7 +94,7 @@ class TestApplyMiniMaxConstraints(unittest.TestCase):
|
|||
def test_other_params_preserved(self):
|
||||
"""Non-constrained parameters should be preserved."""
|
||||
kwargs = {
|
||||
"model": "minimax/MiniMax-M2.7",
|
||||
"model": "minimax/MiniMax-M3",
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
|
|
@ -101,6 +104,12 @@ class TestApplyMiniMaxConstraints(unittest.TestCase):
|
|||
self.assertEqual(result["messages"], [{"role": "user", "content": "Hello"}])
|
||||
self.assertEqual(result["temperature"], 0.5)
|
||||
|
||||
def test_constraints_apply_to_m27_legacy(self):
|
||||
"""Constraints should also apply to legacy M2.7 models."""
|
||||
kwargs = {"model": "minimax/MiniMax-M2.7", "temperature": 0}
|
||||
result = _apply_minimax_constraints(kwargs)
|
||||
self.assertEqual(result["temperature"], 0.01)
|
||||
|
||||
|
||||
class TestProviderRegistry(unittest.TestCase):
|
||||
"""Tests for MiniMax in the nanobot provider registry."""
|
||||
|
|
@ -124,8 +133,18 @@ class TestProviderRegistry(unittest.TestCase):
|
|||
self.assertIn("minimax", keywords)
|
||||
break
|
||||
|
||||
def test_match_provider_minimax_m3(self):
|
||||
"""match_provider should find minimax config for M3 models."""
|
||||
providers = {
|
||||
"minimax": {"apiKey": "test-key-m3"},
|
||||
}
|
||||
result = match_provider(providers, "minimax/MiniMax-M3")
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result["api_key"], "test-key-m3")
|
||||
self.assertEqual(result["api_base"], "https://api.minimax.io/v1")
|
||||
|
||||
def test_match_provider_minimax_model(self):
|
||||
"""match_provider should find minimax config for minimax models."""
|
||||
"""match_provider should find minimax config for legacy M2.7 models."""
|
||||
providers = {
|
||||
"minimax": {"apiKey": "test-key-123"},
|
||||
}
|
||||
|
|
@ -139,7 +158,7 @@ class TestProviderRegistry(unittest.TestCase):
|
|||
providers = {
|
||||
"minimax": {"apiKey": "test-key-456"},
|
||||
}
|
||||
result = match_provider(providers, "MiniMax-M2.7")
|
||||
result = match_provider(providers, "MiniMax-M3")
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result["api_key"], "test-key-456")
|
||||
|
||||
|
|
@ -159,8 +178,17 @@ class TestResolverMiniMaxDetection(unittest.TestCase):
|
|||
|
||||
@patch.dict(os.environ, {"MINIMAX_API_KEY": "minimax-test-key"}, clear=False)
|
||||
@patch("openspace.host_detection.nanobot.try_read_nanobot_config", return_value=None)
|
||||
def test_auto_detect_minimax_api_key(self, _mock_nanobot):
|
||||
"""MINIMAX_API_KEY should be auto-detected for minimax models."""
|
||||
def test_auto_detect_minimax_api_key_m3(self, _mock_nanobot):
|
||||
"""MINIMAX_API_KEY should be auto-detected for minimax M3 models."""
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M3")
|
||||
self.assertEqual(model, "minimax/MiniMax-M3")
|
||||
self.assertEqual(kwargs.get("api_key"), "minimax-test-key")
|
||||
self.assertEqual(kwargs.get("api_base"), "https://api.minimax.io/v1")
|
||||
|
||||
@patch.dict(os.environ, {"MINIMAX_API_KEY": "minimax-test-key"}, clear=False)
|
||||
@patch("openspace.host_detection.nanobot.try_read_nanobot_config", return_value=None)
|
||||
def test_auto_detect_minimax_api_key_m27(self, _mock_nanobot):
|
||||
"""MINIMAX_API_KEY should still be auto-detected for legacy M2.7 models."""
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M2.7")
|
||||
self.assertEqual(model, "minimax/MiniMax-M2.7")
|
||||
self.assertEqual(kwargs.get("api_key"), "minimax-test-key")
|
||||
|
|
@ -172,8 +200,8 @@ class TestResolverMiniMaxDetection(unittest.TestCase):
|
|||
"""Without MINIMAX_API_KEY, no api_key should be set."""
|
||||
# Remove MINIMAX_API_KEY if it exists
|
||||
os.environ.pop("MINIMAX_API_KEY", None)
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M2.7")
|
||||
self.assertEqual(model, "minimax/MiniMax-M2.7")
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M3")
|
||||
self.assertEqual(model, "minimax/MiniMax-M3")
|
||||
self.assertNotIn("api_key", kwargs)
|
||||
|
||||
@patch.dict(os.environ, {"MINIMAX_API_KEY": "minimax-key"}, clear=False)
|
||||
|
|
@ -192,7 +220,7 @@ class TestResolverMiniMaxDetection(unittest.TestCase):
|
|||
@patch("openspace.host_detection.nanobot.try_read_nanobot_config", return_value=None)
|
||||
def test_explicit_key_overrides_minimax(self, _mock_nanobot):
|
||||
"""OPENSPACE_LLM_API_KEY (Tier 1) should override MINIMAX_API_KEY (Tier 3)."""
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M2.7")
|
||||
model, kwargs = build_llm_kwargs("minimax/MiniMax-M3")
|
||||
self.assertEqual(kwargs["api_key"], "explicit-key")
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue