232 lines
8.3 KiB
Python
232 lines
8.3 KiB
Python
"""
|
|
Tests for MiniMax provider integration.
|
|
|
|
These tests validate MiniMax provider settings, chat class, and factory integration.
|
|
We import from leann.settings and leann.chat directly to avoid triggering
|
|
the full leann.__init__ import chain which requires C++ backend builds.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
# Add the leann-core source to sys.path so we can import submodules
|
|
# without triggering __init__.py's backend imports.
|
|
_LEANN_SRC = os.path.join(os.path.dirname(__file__), "..", "packages", "leann-core", "src")
|
|
if _LEANN_SRC not in sys.path:
|
|
sys.path.insert(0, os.path.abspath(_LEANN_SRC))
|
|
|
|
# Prevent leann.__init__ from running its heavy imports by pre-registering
|
|
# a lightweight stub in sys.modules (if not already present).
|
|
if "leann" not in sys.modules:
|
|
import types
|
|
|
|
_stub = types.ModuleType("leann")
|
|
_stub.__path__ = [os.path.join(os.path.abspath(_LEANN_SRC), "leann")]
|
|
sys.modules["leann"] = _stub
|
|
|
|
# Now we can safely import the modules we actually test.
|
|
from leann.settings import ( # noqa: E402
|
|
resolve_minimax_api_key,
|
|
resolve_minimax_base_url,
|
|
)
|
|
|
|
|
|
class TestMiniMaxSettings:
|
|
"""Test MiniMax settings resolver functions."""
|
|
|
|
def test_resolve_minimax_api_key_explicit(self):
|
|
assert resolve_minimax_api_key("test-key") == "test-key"
|
|
|
|
def test_resolve_minimax_api_key_from_env(self):
|
|
with patch.dict(os.environ, {"MINIMAX_API_KEY": "env-key"}):
|
|
assert resolve_minimax_api_key() == "env-key"
|
|
|
|
def test_resolve_minimax_api_key_none(self):
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
assert resolve_minimax_api_key() is None
|
|
|
|
def test_resolve_minimax_base_url_default(self):
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
assert resolve_minimax_base_url() == "https://api.minimax.io/v1"
|
|
|
|
def test_resolve_minimax_base_url_explicit(self):
|
|
assert resolve_minimax_base_url("https://custom.url/v1") == "https://custom.url/v1"
|
|
|
|
def test_resolve_minimax_base_url_from_env(self):
|
|
with patch.dict(os.environ, {"MINIMAX_BASE_URL": "https://env.url/v1"}):
|
|
assert resolve_minimax_base_url() == "https://env.url/v1"
|
|
|
|
def test_resolve_minimax_base_url_leann_env(self):
|
|
with patch.dict(os.environ, {"LEANN_MINIMAX_BASE_URL": "https://leann.url/v1"}):
|
|
assert resolve_minimax_base_url() == "https://leann.url/v1"
|
|
|
|
def test_resolve_minimax_base_url_strips_trailing_slash(self):
|
|
assert resolve_minimax_base_url("https://api.minimax.io/v1/") == "https://api.minimax.io/v1"
|
|
|
|
|
|
class TestMiniMaxChat:
|
|
"""Test MiniMaxChat class."""
|
|
|
|
def test_init_requires_api_key(self):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with pytest.raises(ValueError, match="MiniMax API key is required"):
|
|
MiniMaxChat(api_key=None)
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_init_with_api_key(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
chat = MiniMaxChat(api_key="test-key")
|
|
assert chat.model == "MiniMax-M2.5"
|
|
assert chat.api_key == "test-key"
|
|
assert chat.base_url == "https://api.minimax.io/v1"
|
|
mock_openai_cls.assert_called_once_with(
|
|
api_key="test-key", base_url="https://api.minimax.io/v1"
|
|
)
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_init_custom_model(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
chat = MiniMaxChat(model="MiniMax-M2.5-highspeed", api_key="test-key")
|
|
assert chat.model == "MiniMax-M2.5-highspeed"
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_init_custom_base_url(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
chat = MiniMaxChat(api_key="test-key", base_url="https://api.minimaxi.com/v1")
|
|
assert chat.base_url == "https://api.minimaxi.com/v1"
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_ask_returns_response(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
# Mock the response chain
|
|
mock_client = MagicMock()
|
|
mock_openai_cls.return_value = mock_client
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.choices = [MagicMock()]
|
|
mock_response.choices[0].message.content = "Hello from MiniMax!"
|
|
mock_response.choices[0].finish_reason = "stop"
|
|
mock_response.usage.total_tokens = 100
|
|
mock_response.usage.prompt_tokens = 50
|
|
mock_response.usage.completion_tokens = 50
|
|
mock_client.chat.completions.create.return_value = mock_response
|
|
|
|
chat = MiniMaxChat(api_key="test-key")
|
|
result = chat.ask("Hello")
|
|
|
|
assert result == "Hello from MiniMax!"
|
|
mock_client.chat.completions.create.assert_called_once()
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_ask_with_kwargs(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
mock_client = MagicMock()
|
|
mock_openai_cls.return_value = mock_client
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.choices = [MagicMock()]
|
|
mock_response.choices[0].message.content = "Response"
|
|
mock_response.choices[0].finish_reason = "stop"
|
|
mock_response.usage.total_tokens = 50
|
|
mock_response.usage.prompt_tokens = 25
|
|
mock_response.usage.completion_tokens = 25
|
|
mock_client.chat.completions.create.return_value = mock_response
|
|
|
|
chat = MiniMaxChat(api_key="test-key")
|
|
chat.ask("Hello", temperature=0.5, max_tokens=500, top_p=0.9)
|
|
|
|
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
|
assert call_kwargs["temperature"] == 0.5
|
|
assert call_kwargs["max_tokens"] == 500
|
|
assert call_kwargs["top_p"] == 0.9
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_ask_handles_error(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
mock_client = MagicMock()
|
|
mock_openai_cls.return_value = mock_client
|
|
mock_client.chat.completions.create.side_effect = Exception("API error")
|
|
|
|
chat = MiniMaxChat(api_key="test-key")
|
|
result = chat.ask("Hello")
|
|
|
|
assert "Error" in result
|
|
assert "MiniMax" in result
|
|
|
|
|
|
class TestGetLLMFactory:
|
|
"""Test get_llm factory function with minimax type."""
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_get_llm_minimax(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat, get_llm
|
|
|
|
llm = get_llm({"type": "minimax", "api_key": "test-key"})
|
|
assert isinstance(llm, MiniMaxChat)
|
|
assert llm.model == "MiniMax-M2.5"
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_get_llm_minimax_custom_model(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat, get_llm
|
|
|
|
llm = get_llm({"type": "minimax", "model": "MiniMax-M2.5-highspeed", "api_key": "test-key"})
|
|
assert isinstance(llm, MiniMaxChat)
|
|
assert llm.model == "MiniMax-M2.5-highspeed"
|
|
|
|
@patch("openai.OpenAI")
|
|
def test_get_llm_minimax_custom_base_url(self, mock_openai_cls):
|
|
from leann.chat import MiniMaxChat, get_llm
|
|
|
|
llm = get_llm(
|
|
{
|
|
"type": "minimax",
|
|
"api_key": "test-key",
|
|
"base_url": "https://api.minimaxi.com/v1",
|
|
}
|
|
)
|
|
assert isinstance(llm, MiniMaxChat)
|
|
assert llm.base_url == "https://api.minimaxi.com/v1"
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
not os.getenv("MINIMAX_API_KEY"),
|
|
reason="MINIMAX_API_KEY not set; skipping live API test",
|
|
)
|
|
class TestMiniMaxLiveAPI:
|
|
"""Live API tests for MiniMax provider (requires MINIMAX_API_KEY)."""
|
|
|
|
def test_minimax_m25_live(self):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
chat = MiniMaxChat(model="MiniMax-M2.5")
|
|
response = chat.ask("Say hello in one word.", max_tokens=10)
|
|
assert isinstance(response, str)
|
|
assert len(response) > 0
|
|
|
|
def test_minimax_m25_highspeed_live(self):
|
|
from leann.chat import MiniMaxChat
|
|
|
|
chat = MiniMaxChat(model="MiniMax-M2.5-highspeed")
|
|
response = chat.ask("Say hello in one word.", max_tokens=10)
|
|
assert isinstance(response, str)
|
|
assert len(response) > 0
|
|
|
|
def test_minimax_via_get_llm_live(self):
|
|
from leann.chat import get_llm
|
|
|
|
llm = get_llm({"type": "minimax"})
|
|
response = llm.ask("What is 1+1? Reply with just the number.", max_tokens=10)
|
|
assert isinstance(response, str)
|
|
assert len(response) > 0
|