mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 05:12:26 +00:00
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
326 lines
13 KiB
Python
326 lines
13 KiB
Python
"""Tests for MiniMax cloud provider support."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from openjarvis.core.registry import EngineRegistry
|
|
from openjarvis.core.types import Message, Role
|
|
from openjarvis.engine._base import EngineConnectionError
|
|
from openjarvis.engine.cloud import (
|
|
_MINIMAX_MODELS,
|
|
PRICING,
|
|
CloudEngine,
|
|
_is_minimax_model,
|
|
estimate_cost,
|
|
)
|
|
|
|
|
|
def _make_cloud_engine(monkeypatch: pytest.MonkeyPatch) -> CloudEngine:
|
|
"""Create a CloudEngine with all API keys cleared."""
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
|
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
|
|
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
monkeypatch.delenv("MINIMAX_API_KEY", raising=False)
|
|
if not EngineRegistry.contains("cloud"):
|
|
EngineRegistry.register_value("cloud", CloudEngine)
|
|
return CloudEngine()
|
|
|
|
|
|
def _fake_minimax_response(
|
|
content: str = "Hello from MiniMax!",
|
|
model: str = "MiniMax-M2.5",
|
|
prompt_tokens: int = 10,
|
|
completion_tokens: int = 5,
|
|
tool_calls: list | None = None,
|
|
) -> SimpleNamespace:
|
|
usage = SimpleNamespace(
|
|
prompt_tokens=prompt_tokens,
|
|
completion_tokens=completion_tokens,
|
|
total_tokens=prompt_tokens + completion_tokens,
|
|
)
|
|
message = SimpleNamespace(content=content, tool_calls=tool_calls)
|
|
choice = SimpleNamespace(message=message, finish_reason="stop")
|
|
return SimpleNamespace(choices=[choice], usage=usage, model=model)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routing tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxRouting:
|
|
def test_is_minimax_model(self) -> None:
|
|
assert _is_minimax_model("MiniMax-M2.7") is True
|
|
assert _is_minimax_model("MiniMax-M2.7-highspeed") is True
|
|
assert _is_minimax_model("MiniMax-M2.5") is True
|
|
assert _is_minimax_model("MiniMax-M2.5-highspeed") is True
|
|
assert _is_minimax_model("minimax-m2.7") is True
|
|
assert _is_minimax_model("gpt-4o") is False
|
|
assert _is_minimax_model("claude-opus-4-6") is False
|
|
assert _is_minimax_model("gemini-3-pro") is False
|
|
|
|
def test_minimax_models_list(self) -> None:
|
|
assert "MiniMax-M2.7" in _MINIMAX_MODELS
|
|
assert "MiniMax-M2.7-highspeed" in _MINIMAX_MODELS
|
|
assert "MiniMax-M2.5" in _MINIMAX_MODELS
|
|
assert "MiniMax-M2.5-highspeed" in _MINIMAX_MODELS
|
|
|
|
def test_m27_is_first_in_list(self) -> None:
|
|
assert _MINIMAX_MODELS[0] == "MiniMax-M2.7"
|
|
assert _MINIMAX_MODELS[1] == "MiniMax-M2.7-highspeed"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Init tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxInit:
|
|
def test_init_with_api_key(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("MINIMAX_API_KEY", "test-minimax-key")
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
|
fake_openai = mock.MagicMock()
|
|
with mock.patch.dict("sys.modules", {"openai": fake_openai}):
|
|
if not EngineRegistry.contains("cloud"):
|
|
EngineRegistry.register_value("cloud", CloudEngine)
|
|
engine = CloudEngine()
|
|
assert engine._minimax_client is not None
|
|
|
|
def test_health_with_minimax_key(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("MINIMAX_API_KEY", "test-minimax-key")
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
|
fake_openai = mock.MagicMock()
|
|
with mock.patch.dict("sys.modules", {"openai": fake_openai}):
|
|
engine = CloudEngine()
|
|
assert engine.health() is True
|
|
|
|
def test_no_minimax_key_no_client(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
assert engine._minimax_client is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generate tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxGenerate:
|
|
def test_m27_generate(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response(
|
|
content="I am MiniMax M2.7", model="MiniMax-M2.7"
|
|
)
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.7"
|
|
)
|
|
assert result["content"] == "I am MiniMax M2.7"
|
|
assert result["model"] == "MiniMax-M2.7"
|
|
assert result["usage"]["prompt_tokens"] == 10
|
|
assert result["usage"]["completion_tokens"] == 5
|
|
|
|
def test_m27_highspeed_generate(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response(
|
|
content="I am MiniMax M2.7 Highspeed", model="MiniMax-M2.7-highspeed"
|
|
)
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.7-highspeed"
|
|
)
|
|
assert result["content"] == "I am MiniMax M2.7 Highspeed"
|
|
assert result["model"] == "MiniMax-M2.7-highspeed"
|
|
|
|
def test_m25_generate(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response(
|
|
content="I am MiniMax M2.5", model="MiniMax-M2.5"
|
|
)
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.5"
|
|
)
|
|
assert result["content"] == "I am MiniMax M2.5"
|
|
assert result["model"] == "MiniMax-M2.5"
|
|
assert result["usage"]["prompt_tokens"] == 10
|
|
assert result["usage"]["completion_tokens"] == 5
|
|
|
|
def test_m25_highspeed_generate(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response(
|
|
content="I am MiniMax M2.5 Highspeed", model="MiniMax-M2.5-highspeed"
|
|
)
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.5-highspeed"
|
|
)
|
|
assert result["content"] == "I am MiniMax M2.5 Highspeed"
|
|
assert result["model"] == "MiniMax-M2.5-highspeed"
|
|
|
|
def test_temperature_clamped_above_zero(
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""MiniMax requires temperature in (0.0, 1.0]; verify zero is clamped."""
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response()
|
|
engine._minimax_client = fake_client
|
|
|
|
engine.generate(
|
|
[Message(role=Role.USER, content="Hi")],
|
|
model="MiniMax-M2.5",
|
|
temperature=0.0,
|
|
)
|
|
call_kwargs = fake_client.chat.completions.create.call_args
|
|
actual_temp = call_kwargs.kwargs.get("temperature") or call_kwargs[1].get(
|
|
"temperature"
|
|
)
|
|
assert actual_temp >= 0.01, "Temperature should be clamped above zero"
|
|
|
|
def test_temperature_clamped_at_max(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""MiniMax requires temperature <= 1.0; verify high values are clamped."""
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response()
|
|
engine._minimax_client = fake_client
|
|
|
|
engine.generate(
|
|
[Message(role=Role.USER, content="Hi")],
|
|
model="MiniMax-M2.5",
|
|
temperature=2.0,
|
|
)
|
|
call_kwargs = fake_client.chat.completions.create.call_args
|
|
actual_temp = call_kwargs.kwargs.get("temperature") or call_kwargs[1].get(
|
|
"temperature"
|
|
)
|
|
assert actual_temp <= 1.0, "Temperature should be clamped at 1.0"
|
|
|
|
def test_tool_calls_extraction(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_tool_call = SimpleNamespace(
|
|
id="call_minimax_123",
|
|
type="function",
|
|
function=SimpleNamespace(name="search", arguments='{"q":"test"}'),
|
|
)
|
|
fake_resp = _fake_minimax_response(content="", model="MiniMax-M2.5")
|
|
fake_resp.choices[0].message.tool_calls = [fake_tool_call]
|
|
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = fake_resp
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Search")], model="MiniMax-M2.5"
|
|
)
|
|
assert "tool_calls" in result
|
|
assert len(result["tool_calls"]) == 1
|
|
tc = result["tool_calls"][0]
|
|
assert tc["id"] == "call_minimax_123"
|
|
assert tc["name"] == "search"
|
|
|
|
def test_no_tool_calls_when_absent(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
fake_client.chat.completions.create.return_value = _fake_minimax_response(
|
|
content="Just text"
|
|
)
|
|
engine._minimax_client = fake_client
|
|
|
|
result = engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.5"
|
|
)
|
|
assert "tool_calls" not in result
|
|
|
|
def test_no_client_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
assert engine._minimax_client is None
|
|
|
|
with pytest.raises(EngineConnectionError, match="MiniMax client not available"):
|
|
engine.generate(
|
|
[Message(role=Role.USER, content="Hi")], model="MiniMax-M2.5"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Model discovery tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxModelDiscovery:
|
|
def test_list_models_includes_minimax(
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
engine._minimax_client = mock.MagicMock()
|
|
models = engine.list_models()
|
|
for m in _MINIMAX_MODELS:
|
|
assert m in models
|
|
|
|
def test_only_minimax_client(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
engine._minimax_client = mock.MagicMock()
|
|
models = engine.list_models()
|
|
assert set(models) == set(_MINIMAX_MODELS)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pricing tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxPricing:
|
|
def test_minimax_models_in_pricing(self) -> None:
|
|
assert "MiniMax-M2.7" in PRICING
|
|
assert "MiniMax-M2.7-highspeed" in PRICING
|
|
assert "MiniMax-M2.5" in PRICING
|
|
assert "MiniMax-M2.5-highspeed" in PRICING
|
|
|
|
def test_minimax_m27_cost_estimate(self) -> None:
|
|
# MiniMax-M2.7: $0.30/M in, $1.20/M out
|
|
cost = estimate_cost("MiniMax-M2.7", 1_000_000, 1_000_000)
|
|
assert cost == pytest.approx(1.50)
|
|
|
|
def test_minimax_m27_highspeed_cost_estimate(self) -> None:
|
|
# MiniMax-M2.7-highspeed: $0.60/M in, $2.40/M out
|
|
cost = estimate_cost("MiniMax-M2.7-highspeed", 1_000_000, 1_000_000)
|
|
assert cost == pytest.approx(3.00)
|
|
|
|
def test_minimax_m25_cost_estimate(self) -> None:
|
|
# MiniMax-M2.5: $0.30/M in, $1.20/M out
|
|
cost = estimate_cost("MiniMax-M2.5", 1_000_000, 1_000_000)
|
|
assert cost == pytest.approx(1.50)
|
|
|
|
def test_zero_tokens_zero_cost(self) -> None:
|
|
assert estimate_cost("MiniMax-M2.7", 0, 0) == 0.0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Close tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMiniMaxClose:
|
|
def test_close_minimax_client(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
engine = _make_cloud_engine(monkeypatch)
|
|
fake_client = mock.MagicMock()
|
|
engine._minimax_client = fake_client
|
|
engine.close()
|
|
assert engine._minimax_client is None
|
|
fake_client.close.assert_called_once()
|