Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
| [Google Gemini Pro](https://ai.google.dev/gemini-api/docs/api-key?hl=zh-cn) | | [StableLM](https://github.com/Stability-AI/StableLM) ||
| [讯飞星火认知大模型](https://xinghuo.xfyun.cn) | | [MOSS](https://github.com/OpenLMLab/MOSS) ||
| [Inspur Yuan 1.0](https://air.inspur.com/home) | | [通义千问](https://github.com/QwenLM/Qwen/tree/main) ||
| [MiniMax](https://api.minimax.chat/) ||[DeepSeek](https://platform.deepseek.com)||
| [MiniMax](https://platform.minimax.io/) | ✨ 支持 MiniMax-M2.5, M2.5-highspeed (204K context) |[DeepSeek](https://platform.deepseek.com)||
| [XMChat](https://github.com/MILVLG/xmchat) | 不支持流式传输|||
| [Midjourney](https://www.midjourney.com/) | 不支持流式传输|||
| [Claude](https://www.anthropic.com/) | ✨ 现已支持Claude 3 Opus、Sonnet,Haiku将会在推出后的第一时间支持|||
Expand Down
1 change: 1 addition & 0 deletions modules/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class ModelType(Enum):
Ollama = 21
Groq = 22
DeepSeek = 23
MiniMaxM25 = 24

@classmethod
def get_type(cls, model_name: str):
Expand Down
6 changes: 6 additions & 0 deletions modules/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def get_model(
from .inspurai import Yuan_Client
model = Yuan_Client(model_name, api_key=access_key,
user_name=user_name, system_prompt=system_prompt)
elif model_type == ModelType.MiniMaxM25:
logging.info(f"正在加载 MiniMax M2.5 模型: {model_name}")
from .OpenAIVision import OpenAIVisionClient
access_key = os.environ.get("MINIMAX_API_KEY", access_key)
model = OpenAIVisionClient(
model_name, api_key=access_key, user_name=user_name)
elif model_type == ModelType.Minimax:
from .minimax import MiniMax_Client
if os.environ.get("MINIMAX_API_KEY") != "":
Expand Down
18 changes: 18 additions & 0 deletions modules/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
"yuanai-1.0-translate",
"yuanai-1.0-dialog",
"yuanai-1.0-rhythm_poems",
"MiniMax-M2.5",
"MiniMax-M2.5-highspeed",
"minimax-abab5-chat",
"midjourney",
# 兼容旧配置文件,待删除
Expand Down Expand Up @@ -558,6 +560,22 @@
"token_limit": 64000,
"multimodal": False,
"model_type": "DeepSeek"
},
"MiniMax-M2.5": {
"model_name": "MiniMax-M2.5",
"api_host": "https://api.minimax.io",
"description": "MiniMax M2.5 - Peak Performance. Ultimate Value. Master the Complex. 204K context window.",
"token_limit": 204800,
"multimodal": False,
"model_type": "MiniMaxM25"
},
"MiniMax-M2.5-highspeed": {
"model_name": "MiniMax-M2.5-highspeed",
"api_host": "https://api.minimax.io",
"description": "MiniMax M2.5 High Speed - Same performance, faster and more agile. 204K context window.",
"token_limit": 204800,
"multimodal": False,
"model_type": "MiniMaxM25"
}
}

Expand Down
Empty file added tests/__init__.py
Empty file.
165 changes: 165 additions & 0 deletions tests/test_minimax_m25.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
"""Unit tests for MiniMax M2.5 provider integration."""

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


class TestMiniMaxM25ModelType:
"""Test MiniMaxM25 ModelType enum and type detection."""

def test_minimax_m25_enum_exists(self):
"""MiniMaxM25 enum value should exist in ModelType."""
from modules.models.base_model import ModelType
assert hasattr(ModelType, "MiniMaxM25")
assert ModelType.MiniMaxM25.value == 24

def test_minimax_m25_type_detection(self):
"""MiniMax-M2.5 models should be detected as MiniMaxM25 type via metadata."""
from modules.models.base_model import ModelType
model_type = ModelType.get_type("MiniMax-M2.5")
assert model_type == ModelType.MiniMaxM25

def test_minimax_m25_highspeed_type_detection(self):
"""MiniMax-M2.5-highspeed should be detected as MiniMaxM25 type via metadata."""
from modules.models.base_model import ModelType
model_type = ModelType.get_type("MiniMax-M2.5-highspeed")
assert model_type == ModelType.MiniMaxM25

def test_old_minimax_type_detection(self):
"""Old minimax-abab5-chat should still be detected as Minimax type."""
from modules.models.base_model import ModelType
model_type = ModelType.get_type("minimax-abab5-chat")
assert model_type == ModelType.Minimax


class TestMiniMaxM25ModelMetadata:
"""Test MiniMax M2.5 model metadata configuration."""

def test_m25_in_online_models(self):
"""MiniMax-M2.5 should be in ONLINE_MODELS list."""
from modules.presets import ONLINE_MODELS
assert "MiniMax-M2.5" in ONLINE_MODELS
assert "MiniMax-M2.5-highspeed" in ONLINE_MODELS

def test_m25_metadata_exists(self):
"""MiniMax-M2.5 should have metadata entry."""
from modules.presets import MODEL_METADATA
assert "MiniMax-M2.5" in MODEL_METADATA
assert "MiniMax-M2.5-highspeed" in MODEL_METADATA

def test_m25_metadata_model_name(self):
"""Model name should be the API model ID."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["model_name"] == "MiniMax-M2.5"
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["model_name"] == "MiniMax-M2.5-highspeed"

def test_m25_metadata_api_host(self):
"""API host should be the MiniMax OpenAI-compatible endpoint."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["api_host"] == "https://api.minimax.io"
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["api_host"] == "https://api.minimax.io"

def test_m25_metadata_token_limit(self):
"""Token limit should be 204800 for M2.5 models."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["token_limit"] == 204800
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["token_limit"] == 204800

def test_m25_metadata_model_type(self):
"""Model type should be MiniMaxM25."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["model_type"] == "MiniMaxM25"
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["model_type"] == "MiniMaxM25"

def test_m25_metadata_not_multimodal(self):
"""M2.5 models should not be multimodal."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["multimodal"] is False
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["multimodal"] is False

def test_m25_metadata_has_description(self):
"""M2.5 models should have descriptions."""
from modules.presets import MODEL_METADATA
assert len(MODEL_METADATA["MiniMax-M2.5"]["description"]) > 0
assert len(MODEL_METADATA["MiniMax-M2.5-highspeed"]["description"]) > 0

def test_old_minimax_still_exists(self):
"""Old minimax-abab5-chat metadata should still exist."""
from modules.presets import MODEL_METADATA
assert "minimax-abab5-chat" in MODEL_METADATA


class TestMiniMaxM25ApiHost:
"""Test API host URL formatting for MiniMax M2.5."""

def test_format_openai_host_minimax(self):
"""format_openai_host should correctly format MiniMax API host."""
from modules.shared import format_openai_host
chat_url, images_url, api_base, balance_url, usage_url = format_openai_host("https://api.minimax.io")
assert chat_url == "https://api.minimax.io/v1/chat/completions"
assert api_base == "https://api.minimax.io/v1"

def test_format_openai_host_minimax_with_v1(self):
"""format_openai_host should handle API host that already ends with /v1."""
from modules.shared import format_openai_host
chat_url, images_url, api_base, balance_url, usage_url = format_openai_host("https://api.minimax.io/v1")
assert chat_url == "https://api.minimax.io/v1/chat/completions"
assert api_base == "https://api.minimax.io/v1"

def test_format_openai_host_minimax_domestic(self):
"""format_openai_host should work with domestic MiniMax API host."""
from modules.shared import format_openai_host
chat_url, images_url, api_base, balance_url, usage_url = format_openai_host("https://api.minimaxi.com")
assert chat_url == "https://api.minimaxi.com/v1/chat/completions"
assert api_base == "https://api.minimaxi.com/v1"


class TestMiniMaxM25ModelRouting:
"""Test model routing for MiniMax M2.5 in models.py."""

@patch.dict(os.environ, {"MINIMAX_API_KEY": "test-minimax-key"})
@patch("modules.models.OpenAIVision.OpenAIVisionClient.__init__", return_value=None)
@patch("modules.models.OpenAIVision.OpenAIVisionClient.description", new_callable=lambda: property(lambda self: "test"), create=True)
def test_m25_uses_openai_vision_client(self, mock_desc, mock_init):
"""MiniMax M2.5 should be routed through OpenAIVisionClient."""
from modules.models.base_model import ModelType
model_type = ModelType.get_type("MiniMax-M2.5")
assert model_type == ModelType.MiniMaxM25

def test_m25_env_key_name(self):
"""MiniMax M2.5 routing should use MINIMAX_API_KEY env variable."""
# Verify the env var name used in models.py routing
import inspect
from modules.models import models
source = inspect.getsource(models.get_model)
assert 'os.environ.get("MINIMAX_API_KEY"' in source

def test_old_minimax_routing_preserved(self):
"""Old minimax routing code should still exist."""
import inspect
from modules.models import models
source = inspect.getsource(models.get_model)
assert "ModelType.Minimax" in source
assert "MiniMax_Client" in source


class TestMiniMaxM25DefaultConfig:
"""Test default configuration values for MiniMax M2.5."""

def test_default_temperature(self):
"""Default temperature should be 1.0 (from DEFAULT_METADATA)."""
from modules.presets import DEFAULT_METADATA
assert DEFAULT_METADATA["temperature"] == 1.0

def test_m25_uses_default_temperature(self):
"""M2.5 models should use default temperature of 1.0."""
from modules.presets import MODEL_METADATA
assert MODEL_METADATA["MiniMax-M2.5"]["temperature"] == 1.0
assert MODEL_METADATA["MiniMax-M2.5-highspeed"]["temperature"] == 1.0

def test_default_stream(self):
"""Default stream should be True."""
from modules.presets import DEFAULT_METADATA
assert DEFAULT_METADATA["stream"] is True
129 changes: 129 additions & 0 deletions tests/test_minimax_m25_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Integration tests for MiniMax M2.5 provider - requires MINIMAX_API_KEY."""

import os
import json
import pytest
import requests

API_KEY = os.environ.get("MINIMAX_API_KEY")
BASE_URL = os.environ.get("MINIMAX_BASE_URL", "https://api.minimax.io/v1")

pytestmark = pytest.mark.skipif(not API_KEY, reason="MINIMAX_API_KEY not set")


class TestMiniMaxM25ChatCompletion:
"""Integration tests for MiniMax M2.5 chat completions via OpenAI-compatible API."""

def test_basic_chat_completion(self):
"""MiniMax M2.5 should return a valid chat completion."""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"model": "MiniMax-M2.5",
"messages": [{"role": "user", "content": 'Say "test passed"'}],
"max_tokens": 20,
"temperature": 1.0,
},
timeout=90,
)
assert response.status_code == 200, f"API error: {response.text}"
data = response.json()
assert "choices" in data
assert len(data["choices"]) > 0
content = data["choices"][0]["message"]["content"]
assert len(content) > 0, "Empty response content"

def test_highspeed_model(self):
"""MiniMax-M2.5-highspeed should also work."""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"model": "MiniMax-M2.5-highspeed",
"messages": [{"role": "user", "content": "Say hello"}],
"max_tokens": 20,
"temperature": 1.0,
},
timeout=90,
)
assert response.status_code == 200, f"API error: {response.text}"
data = response.json()
assert len(data["choices"][0]["message"]["content"]) > 0

def test_streaming(self):
"""MiniMax M2.5 should support streaming responses."""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"model": "MiniMax-M2.5",
"messages": [{"role": "user", "content": "Count 1 to 3"}],
"max_tokens": 50,
"stream": True,
"temperature": 1.0,
},
stream=True,
timeout=90,
)
assert response.status_code == 200, f"API error: {response.text}"
chunks = 0
for line in response.iter_lines():
if line:
decoded = line.decode()
if decoded.startswith("data:") and "[DONE]" not in decoded:
chunks += 1
assert chunks > 1, "Expected multiple streaming chunks"

def test_system_message(self):
"""MiniMax M2.5 should handle system messages."""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"model": "MiniMax-M2.5",
"messages": [
{"role": "system", "content": "You are a helpful assistant. Always respond in exactly one word."},
{"role": "user", "content": "What color is the sky?"},
],
"max_tokens": 10,
"temperature": 1.0,
},
timeout=90,
)
assert response.status_code == 200, f"API error: {response.text}"
data = response.json()
assert len(data["choices"][0]["message"]["content"]) > 0

def test_usage_info(self):
"""Response should include usage information."""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"model": "MiniMax-M2.5",
"messages": [{"role": "user", "content": "Hi"}],
"max_tokens": 10,
"temperature": 1.0,
},
timeout=90,
)
assert response.status_code == 200
data = response.json()
assert "usage" in data
assert data["usage"]["total_tokens"] > 0
Loading