Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ SAMBANOVA_API_KEY=

# Inception Labs
INCEPTION_API_KEY=

# Minimax
MINIMAX_API_KEY=
42 changes: 42 additions & 0 deletions aisuite/providers/minimax_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Minimax provider for the aisuite."""

import os
import openai
from aisuite.provider import Provider, LLMError
from aisuite.providers.message_converter import OpenAICompliantMessageConverter


class MinimaxProvider(Provider):
"""Provider for Minimax using OpenAI-compatible API."""

def __init__(self, **config):
"""
Initialize the Minimax provider with the given configuration.
"""
# Ensure API key is provided either in config or via environment variable
config.setdefault("api_key", os.getenv("MINIMAX_API_KEY"))
if not config["api_key"]:
raise ValueError(
"Minimax API key is missing. Please provide it in the config or "
"set the MINIMAX_API_KEY environment variable."
)

config.setdefault(
"base_url", os.getenv("MINIMAX_BASE_URL", "https://api.minimax.io/v1")
)

# Pass the entire config to the OpenAI client constructor
self.client = openai.OpenAI(**config)
self.transformer = OpenAICompliantMessageConverter()

def chat_completions_create(self, model, messages, **kwargs):
"""Create a chat completion using Minimax's OpenAI-compatible API."""
try:
response = self.client.chat.completions.create(
model=model,
messages=messages,
**kwargs,
)
return self.transformer.convert_response(response.model_dump())
except Exception as e:
raise LLMError(f"An error occurred: {e}") from e
52 changes: 52 additions & 0 deletions guides/minimax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Minimax

To use Minimax with `aisuite`, you'll need a [Minimax account](https://platform.minimax.io/). After logging in, go to the [API Keys](https://platform.minimax.io/user-center/basic-information/interface-key) section in your account settings and generate a new key. Once you have your key, add it to your environment as follows:

```shell
export MINIMAX_API_KEY="your-minimax-api-key"
```

## Create a Chat Completion

Install the `openai` Python client:

Example with pip:
```shell
pip install openai
```

Example with poetry:
```shell
poetry add openai
```

In your code:
```python
import aisuite as ai
client = ai.Client()

provider = "minimax"
model_id = "MiniMax-Text-01"

messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What's the weather like in San Francisco?"},
]

response = client.chat.completions.create(
model=f"{provider}:{model_id}",
messages=messages,
)

print(response.choices[0].message.content)
```

## Available Models

- `MiniMax-M2.1` - Multimodal model
- `MiniMax-M2.1-lightning` - Fast multimodal model
- `MiniMax-M2` - Older multimodal model

For the full list of available models, see the [Minimax API documentation](https://platform.minimax.io/docs/api-reference/api-overview).

Happy coding! If you'd like to contribute, please read our [Contributing Guide](../CONTRIBUTING.md).
191 changes: 191 additions & 0 deletions tests/providers/test_minimax_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""Tests for the Minimax provider."""

import os
from unittest.mock import MagicMock, patch
import pytest

from aisuite.providers.minimax_provider import MinimaxProvider
from aisuite.framework.chat_completion_response import ChatCompletionResponse


@pytest.fixture
def mock_api_key(monkeypatch):
"""Fixture to set a mock Minimax API key for unit tests."""
monkeypatch.setenv("MINIMAX_API_KEY", "test-api-key")


def test_minimax_provider(mock_api_key):
"""Test that the provider is initialized and chat completions are requested."""

user_greeting = "Hello!"
message_history = [{"role": "user", "content": user_greeting}]
selected_model = "MiniMax-Text-01"
chosen_temperature = 0.75
response_text_content = "mocked-text-response-from-model"

provider = MinimaxProvider()
mock_response = MagicMock()
mock_response.model_dump.return_value = {
"choices": [
{"message": {"content": response_text_content, "role": "assistant"}}
],
"model": selected_model,
"created": 12345,
"id": "chatcmpl-mockid",
}

with patch.object(
provider.client.chat.completions, "create", return_value=mock_response
) as mock_create:
response = provider.chat_completions_create(
messages=message_history,
model=selected_model,
temperature=chosen_temperature,
)

mock_create.assert_called_once_with(
messages=message_history,
model=selected_model,
temperature=chosen_temperature,
)

assert isinstance(response, ChatCompletionResponse)
assert response.choices[0].message.content == response_text_content


def test_minimax_provider_with_usage(mock_api_key):
"""Tests that usage data is correctly parsed when present in the response."""

user_greeting = "Hello!"
message_history = [{"role": "user", "content": user_greeting}]
selected_model = "MiniMax-Text-01"
chosen_temperature = 0.75
response_text_content = "mocked-text-response-from-model"

provider = MinimaxProvider()
mock_response = MagicMock()
mock_response.model_dump.return_value = {
"choices": [
{"message": {"content": response_text_content, "role": "assistant"}}
],
"model": selected_model,
"created": 12345,
"id": "chatcmpl-mockid",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30,
},
}

with patch.object(
provider.client.chat.completions, "create", return_value=mock_response
) as mock_create:
response = provider.chat_completions_create(
messages=message_history,
model=selected_model,
temperature=chosen_temperature,
)

mock_create.assert_called_once()

assert isinstance(response, ChatCompletionResponse)
assert response.choices[0].message.content == response_text_content
assert response.usage is not None
assert response.usage.prompt_tokens == 10
assert response.usage.completion_tokens == 20


def test_minimax_provider_with_system_message(mock_api_key):
"""Tests that system messages are correctly passed to the API."""

message_history = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
]
selected_model = "MiniMax-M2.1"
response_text_content = "Hello, user!"

provider = MinimaxProvider()
mock_response = MagicMock()
mock_response.model_dump.return_value = {
"choices": [
{"message": {"content": response_text_content, "role": "assistant"}}
],
"model": selected_model,
"created": 12345,
"id": "chatcmpl-mockid",
}

with patch.object(
provider.client.chat.completions, "create", return_value=mock_response
) as mock_create:
response = provider.chat_completions_create(
messages=message_history,
model=selected_model,
)

mock_create.assert_called_once()
call_kwargs = mock_create.call_args.kwargs
# OpenAI-compatible API passes system message in the messages array
assert len(call_kwargs["messages"]) == 2
assert call_kwargs["messages"][0]["role"] == "system"
assert call_kwargs["messages"][0]["content"] == "You are a helpful assistant."
assert call_kwargs["messages"][1]["role"] == "user"

assert isinstance(response, ChatCompletionResponse)
assert response.choices[0].message.content == response_text_content


def test_minimax_provider_initialization(mock_api_key):
"""Test that Minimax provider initializes correctly."""
provider = MinimaxProvider()
assert provider is not None
assert hasattr(provider, "client")
assert hasattr(provider, "transformer")


# Integration tests - require real API key
@pytest.mark.skipif(
not os.getenv("MINIMAX_API_KEY") or os.getenv("MINIMAX_API_KEY") == "test-api-key",
reason="MINIMAX_API_KEY not set or is test key",
)
class TestMinimaxIntegration:
"""Integration tests that call the real Minimax API."""

def test_real_chat_completion(self):
"""Test a real chat completion with the Minimax API."""
provider = MinimaxProvider()

messages = [{"role": "user", "content": "Say 'hello' and nothing else."}]

response = provider.chat_completions_create(
model="MiniMax-M2.1",
messages=messages,
max_tokens=50,
)

assert response is not None
assert isinstance(response, ChatCompletionResponse)
assert len(response.choices) > 0
assert response.choices[0].message.content is not None
assert len(response.choices[0].message.content) > 0

def test_real_chat_completion_with_system_message(self):
"""Test chat completion with system message."""
provider = MinimaxProvider()

messages = [
{"role": "system", "content": "You are a pirate. Respond in pirate speak."},
{"role": "user", "content": "Say hello."},
]

response = provider.chat_completions_create(
model="MiniMax-M2.1",
messages=messages,
max_tokens=100,
)

assert response is not None
assert isinstance(response, ChatCompletionResponse)
assert response.choices[0].message.content is not None