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
25 changes: 23 additions & 2 deletions docs/documentation/model-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Usually, an `API_KEY` is required to integrate with 3P models. To do so, the [Mo

These keys will be injected at runtime into the Lambda function Environment Variables; they won't be visible in the AWS Lambda Console.

For example, if you wish to be able to interact with AI21 Labs., OpenAI's and Cohere endpoints:
For example, if you wish to be able to interact with AI21 Labs., OpenAI's, Cohere and MiniMax endpoints:

- Open the [Model Interface Keys Secret](https://github.com/aws-samples/aws-genai-llm-chatbot/blob/main/lib/model-interfaces/langchain/index.ts#L38) in Secrets Manager. You can find the secret name in the stack output, too.
- Update the Secrets by adding a key to the JSON
Expand All @@ -50,7 +50,8 @@ For example, if you wish to be able to interact with AI21 Labs., OpenAI's and Co
{
"AI21_API_KEY": "xxxxx",
"OPENAI_API_KEY": "sk-xxxxxxxxxxxxxxx",
"COHERE_API_KEY": "xxxxx"
"COHERE_API_KEY": "xxxxx",
"MINIMAX_API_KEY": "xxxxx"
}
```

Expand All @@ -59,6 +60,26 @@ N.B: In case of no keys needs, the secret value must be an empty JSON `{}`, NOT

make sure that the environment variable matches what is expected by the framework in use, like Langchain ([see available langchain integrations](https://python.langchain.com/docs/integrations/llms/)).

### MiniMax integration as third party model

[MiniMax](https://www.minimax.io/) provides OpenAI-compatible LLM APIs. The following models are supported:

| Model | Context Length | Description |
|-------|---------------|-------------|
| MiniMax-M2.7 | 1M tokens | Latest flagship model |
| MiniMax-M2.5 | 204K tokens | Strong reasoning capabilities |
| MiniMax-M2.5-highspeed | 204K tokens | Faster variant of M2.5 |

To enable MiniMax models, add your API key to the Secrets Manager secret:

```json
{
"MINIMAX_API_KEY": "your-minimax-api-key"
}
```

Once added, MiniMax models will automatically appear in the model selection UI.

### Azure OpenAI integration as third party model

- Open the SharedApiKeysSecretxyz in SecretManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .sagemaker import *
from .bedrock import *
from .bedrock_agent import *
from .minimax import *
from .base import Mode
from .shared import *
from .nexus import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from .minimax_chat import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import os

from langchain_openai import ChatOpenAI

from adapters.base import ModelAdapter
from genai_core.registry import registry


class MinimaxChatAdapter(ModelAdapter):
def __init__(self, model_id, *args, **kwargs):
self.model_id = model_id

super().__init__(*args, **kwargs)

def get_llm(self, model_kwargs={}):
api_key = os.environ.get("MINIMAX_API_KEY")
if not api_key:
raise Exception("MINIMAX_API_KEY must be set in the environment")

params = {}
if "streaming" in model_kwargs:
params["streaming"] = model_kwargs["streaming"]
if "temperature" in model_kwargs:
temperature = model_kwargs["temperature"]
# MiniMax accepts temperature in [0, 1]
params["temperature"] = max(0.0, min(1.0, temperature))
if "maxTokens" in model_kwargs:
params["max_tokens"] = model_kwargs["maxTokens"]

return ChatOpenAI(
model_name=self.model_id,
openai_api_key=api_key,
openai_api_base="https://api.minimax.io/v1",
callbacks=[self.callback_handler],
**params,
)


# Register the adapter
registry.register(r"^minimax.*", MinimaxChatAdapter)
11 changes: 11 additions & 0 deletions lib/shared/layers/python-sdk/python/genai_core/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ def get_openai_client() -> Optional[Any]:
return openai


def get_minimax_client() -> Optional[openai.OpenAI]:
api_key = genai_core.parameters.get_external_api_key("MINIMAX_API_KEY")
if not api_key:
return None

return openai.OpenAI(
api_key=api_key,
base_url="https://api.minimax.io/v1",
)


def get_sagemaker_client() -> Any:
config = Config(retries={"max_attempts": 15, "mode": "adaptive"})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def list_models(self) -> list[dict[str, Any]]:
if azure_openai_models:
models.extend(azure_openai_models)

# Get MiniMax models
minimax_models = _list_minimax_models()
if minimax_models:
models.extend(minimax_models)

return models

def get_embedding_models(self) -> list[dict[str, Any]]:
Expand Down Expand Up @@ -389,3 +394,43 @@ def _list_sagemaker_models():
}
for model in models
]


# MiniMax models use the OpenAI-compatible API at https://api.minimax.io/v1
_MINIMAX_MODELS = [
{
"id": "MiniMax-M2.7",
"name": "MiniMax-M2.7",
"description": "MiniMax M2.7 — latest flagship model with 1M context",
},
{
"id": "MiniMax-M2.5",
"name": "MiniMax-M2.5",
"description": "MiniMax M2.5 — 204K context, strong reasoning",
},
{
"id": "MiniMax-M2.5-highspeed",
"name": "MiniMax-M2.5-highspeed",
"description": "MiniMax M2.5 high-speed variant — 204K context, faster",
},
]


def _list_minimax_models():
api_key = genai_core.parameters.get_external_api_key("MINIMAX_API_KEY")
if not api_key:
return None

return [
{
"provider": Provider.MINIMAX.value,
"name": model["id"],
"streaming": True,
"inputModalities": [Modality.TEXT.value],
"outputModalities": [Modality.TEXT.value],
"interface": ModelInterface.LANGCHAIN.value,
"ragSupported": True,
"bedrockGuardrails": False,
}
for model in _MINIMAX_MODELS
]
1 change: 1 addition & 0 deletions lib/shared/layers/python-sdk/python/genai_core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Provider(Enum):
SAGEMAKER = "sagemaker"
AMAZON = "amazon"
COHERE = "cohere"
MINIMAX = "minimax"


class Modality(Enum):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import os
import pytest
from unittest.mock import patch, MagicMock

from adapters.minimax.minimax_chat import MinimaxChatAdapter
from genai_core.types import ChatbotMode


@pytest.fixture
def adapter():
os.environ["MINIMAX_API_KEY"] = "test-minimax-key"
with (
patch("genai_core.langchain.DynamoDBChatMessageHistory"),
patch("genai_core.clients.get_bedrock_client"),
):
adapter = MinimaxChatAdapter(
model_id="MiniMax-M2.7",
session_id="test_session",
user_id="test_user",
mode=ChatbotMode.CHAIN.value,
model_kwargs={},
)
yield adapter
os.environ.pop("MINIMAX_API_KEY", None)


def test_get_llm_basic(adapter):
"""Test that get_llm returns a ChatOpenAI configured for MiniMax."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
llm = adapter.get_llm()

mock_chat.assert_called_once()
call_kwargs = mock_chat.call_args
assert call_kwargs.kwargs["model_name"] == "MiniMax-M2.7"
assert call_kwargs.kwargs["openai_api_key"] == "test-minimax-key"
assert call_kwargs.kwargs["openai_api_base"] == "https://api.minimax.io/v1"


def test_get_llm_with_streaming(adapter):
"""Test that streaming is passed through to ChatOpenAI."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(model_kwargs={"streaming": True})

call_kwargs = mock_chat.call_args.kwargs
assert call_kwargs["streaming"] is True


def test_get_llm_with_temperature(adapter):
"""Test that temperature is clamped to [0, 1]."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(model_kwargs={"temperature": 0.7})
assert mock_chat.call_args.kwargs["temperature"] == 0.7


def test_get_llm_clamps_temperature_high(adapter):
"""Test that temperature > 1 is clamped to 1."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(model_kwargs={"temperature": 2.0})
assert mock_chat.call_args.kwargs["temperature"] == 1.0


def test_get_llm_clamps_temperature_low(adapter):
"""Test that temperature < 0 is clamped to 0."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(model_kwargs={"temperature": -0.5})
assert mock_chat.call_args.kwargs["temperature"] == 0.0


def test_get_llm_with_max_tokens(adapter):
"""Test that maxTokens is mapped to max_tokens."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(model_kwargs={"maxTokens": 4096})
assert mock_chat.call_args.kwargs["max_tokens"] == 4096


def test_get_llm_missing_api_key():
"""Test that missing API key raises an exception during construction."""
os.environ.pop("MINIMAX_API_KEY", None)
with (
patch("genai_core.langchain.DynamoDBChatMessageHistory"),
patch("genai_core.clients.get_bedrock_client"),
):
with pytest.raises(Exception, match="MINIMAX_API_KEY must be set"):
MinimaxChatAdapter(
model_id="MiniMax-M2.7",
session_id="test_session",
user_id="test_user",
mode=ChatbotMode.CHAIN.value,
model_kwargs={},
)


def test_get_llm_with_all_kwargs(adapter):
"""Test that all model kwargs are passed correctly."""
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm(
model_kwargs={
"streaming": True,
"temperature": 0.5,
"maxTokens": 2048,
}
)
call_kwargs = mock_chat.call_args.kwargs
assert call_kwargs["streaming"] is True
assert call_kwargs["temperature"] == 0.5
assert call_kwargs["max_tokens"] == 2048
assert call_kwargs["openai_api_base"] == "https://api.minimax.io/v1"


def test_adapter_model_id(adapter):
"""Test that model_id is stored correctly."""
assert adapter.model_id == "MiniMax-M2.7"


def test_get_llm_m25_model():
"""Test adapter works with M2.5 model."""
os.environ["MINIMAX_API_KEY"] = "test-key"
with (
patch("genai_core.langchain.DynamoDBChatMessageHistory"),
patch("genai_core.clients.get_bedrock_client"),
):
adapter = MinimaxChatAdapter(
model_id="MiniMax-M2.5",
session_id="test_session",
user_id="test_user",
mode=ChatbotMode.CHAIN.value,
model_kwargs={},
)
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm()
assert mock_chat.call_args.kwargs["model_name"] == "MiniMax-M2.5"
os.environ.pop("MINIMAX_API_KEY", None)


def test_get_llm_m25_highspeed_model():
"""Test adapter works with M2.5-highspeed model."""
os.environ["MINIMAX_API_KEY"] = "test-key"
with (
patch("genai_core.langchain.DynamoDBChatMessageHistory"),
patch("genai_core.clients.get_bedrock_client"),
):
adapter = MinimaxChatAdapter(
model_id="MiniMax-M2.5-highspeed",
session_id="test_session",
user_id="test_user",
mode=ChatbotMode.CHAIN.value,
model_kwargs={},
)
with patch("adapters.minimax.minimax_chat.ChatOpenAI") as mock_chat:
mock_chat.return_value = MagicMock()
adapter.get_llm()
assert (
mock_chat.call_args.kwargs["model_name"]
== "MiniMax-M2.5-highspeed"
)
os.environ.pop("MINIMAX_API_KEY", None)
Loading