Merge pull request #18377 from BerriAI/litellm_minmax_anthropic_spec

Add anthropic native endpoint support for Minimax
This commit is contained in:
Sameer Kankute 2025-12-23 21:49:27 +05:30 committed by GitHub
commit d0fa01d13c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1119 additions and 2 deletions

View file

@ -0,0 +1,385 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# MiniMax
# MiniMax - v1/messages
## Overview
Litellm provides anthropic specs compatible support for minmax
## Supported Models
MiniMax offers three models through their Anthropic-compatible API:
| Model | Description | Input Cost | Output Cost | Prompt Caching Read | Prompt Caching Write |
|-------|-------------|------------|-------------|---------------------|----------------------|
| **MiniMax-M2.1** | Powerful Multi-Language Programming with Enhanced Programming Experience (~60 tps) | $0.3/M tokens | $1.2/M tokens | $0.03/M tokens | $0.375/M tokens |
| **MiniMax-M2.1-lightning** | Faster and More Agile (~100 tps) | $0.3/M tokens | $2.4/M tokens | $0.03/M tokens | $0.375/M tokens |
| **MiniMax-M2** | Agentic capabilities, Advanced reasoning | $0.3/M tokens | $1.2/M tokens | $0.03/M tokens | $0.375/M tokens |
## Usage Examples
### Basic Chat Completion
```python
import litellm
response = litellm.anthropic.messages.acreate(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Hello, how are you?"}],
api_key="your-minimax-api-key",
api_base="https://api.minimax.io/anthropic/v1/messages",
max_tokens=1000
)
print(response.choices[0].message.content)
```
### Using Environment Variables
```bash
export MINIMAX_API_KEY="your-minimax-api-key"
export MINIMAX_API_BASE="https://api.minimax.io/anthropic/v1/messages"
```
```python
import litellm
response = litellm.anthropic.messages.acreate(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=1000
)
```
### With Thinking (M2.1 Feature)
```python
response = litellm.anthropic.messages.acreate(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Solve: 2+2=?"}],
thinking={"type": "enabled", "budget_tokens": 1000},
api_key="your-minimax-api-key"
)
# Access thinking content
for block in response.choices[0].message.content:
if hasattr(block, 'type') and block.type == 'thinking':
print(f"Thinking: {block.thinking}")
```
### With Tool Calling
```python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
]
response = litellm.anthropic.messages.acreate(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "What's the weather in SF?"}],
tools=tools,
api_key="your-minimax-api-key",
max_tokens=1000
)
```
## Usage with LiteLLM Proxy
You can use MiniMax models with the Anthropic SDK by routing through LiteLLM Proxy:
| Step | Description |
|------|-------------|
| **1. Start LiteLLM Proxy** | Configure proxy with MiniMax models in `config.yaml` |
| **2. Set Environment Variables** | Point Anthropic SDK to proxy endpoint |
| **3. Use Anthropic SDK** | Call MiniMax models using native Anthropic SDK |
### Step 1: Configure LiteLLM Proxy
Create a `config.yaml`:
```yaml
model_list:
- model_name: minimax/MiniMax-M2.1
litellm_params:
model: minimax/MiniMax-M2.1
api_key: os.environ/MINIMAX_API_KEY
api_base: https://api.minimax.io/anthropic/v1/messages
```
Start the proxy:
```bash
litellm --config config.yaml
```
### Step 2: Use with Anthropic SDK
```python
import os
os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000"
os.environ["ANTHROPIC_API_KEY"] = "sk-1234" # Your LiteLLM proxy key
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="minimax/MiniMax-M2.1",
max_tokens=1000,
system="You are a helpful assistant.",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hi, how are you?"
}
]
}
]
)
for block in message.content:
if block.type == "thinking":
print(f"Thinking:\n{block.thinking}\n")
elif block.type == "text":
print(f"Text:\n{block.text}\n")
```
# MiniMax - v1/chat/completions
## Usage with LiteLLM SDK
You can use MiniMax's OpenAI-compatible API directly with LiteLLM:
### Basic Chat Completion
```python
import litellm
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"}
],
api_key="your-minimax-api-key",
api_base="https://api.minimax.io/v1"
)
print(response.choices[0].message.content)
```
### Using Environment Variables
```bash
export MINIMAX_API_KEY="your-minimax-api-key"
export MINIMAX_API_BASE="https://api.minimax.io/v1"
```
```python
import litellm
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Hello!"}]
)
```
### With Reasoning Split
```python
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Solve: 2+2=?"}
],
extra_body={"reasoning_split": True},
api_key="your-minimax-api-key",
api_base="https://api.minimax.io/v1"
)
# Access reasoning details if available
if hasattr(response.choices[0].message, 'reasoning_details'):
print(f"Thinking: {response.choices[0].message.reasoning_details}")
print(f"Response: {response.choices[0].message.content}")
```
### With Tool Calling
```python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
]
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "What's the weather in SF?"}],
tools=tools,
api_key="your-minimax-api-key",
api_base="https://api.minimax.io/v1"
)
```
### Streaming
```python
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True,
api_key="your-minimax-api-key",
api_base="https://api.minimax.io/v1"
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```
## Usage with OpenAI SDK via LiteLLM Proxy
You can also use MiniMax models with the OpenAI SDK by routing through LiteLLM Proxy:
| Step | Description |
|------|-------------|
| **1. Start LiteLLM Proxy** | Configure proxy with MiniMax models in `config.yaml` |
| **2. Set Environment Variables** | Point OpenAI SDK to proxy endpoint |
| **3. Use OpenAI SDK** | Call MiniMax models using native OpenAI SDK |
### Step 1: Configure LiteLLM Proxy
Create a `config.yaml`:
```yaml
model_list:
- model_name: minimax/MiniMax-M2.1
litellm_params:
model: minimax/MiniMax-M2.1
api_key: os.environ/MINIMAX_API_KEY
api_base: https://api.minimax.io/v1
```
Start the proxy:
```bash
litellm --config config.yaml
```
### Step 2: Use with OpenAI SDK
```python
import os
os.environ["OPENAI_BASE_URL"] = "http://localhost:4000"
os.environ["OPENAI_API_KEY"] = "sk-1234" # Your LiteLLM proxy key
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hi, how are you?"},
],
# Set reasoning_split=True to separate thinking content
extra_body={"reasoning_split": True},
)
# Access thinking and response
if hasattr(response.choices[0].message, 'reasoning_details'):
print(f"Thinking:\n{response.choices[0].message.reasoning_details[0]['text']}\n")
print(f"Text:\n{response.choices[0].message.content}\n")
```
### Streaming with OpenAI SDK
```python
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a story"},
],
extra_body={"reasoning_split": True},
stream=True,
)
reasoning_buffer = ""
text_buffer = ""
for chunk in stream:
if hasattr(chunk.choices[0].delta, "reasoning_details") and chunk.choices[0].delta.reasoning_details:
for detail in chunk.choices[0].delta.reasoning_details:
if "text" in detail:
reasoning_text = detail["text"]
new_reasoning = reasoning_text[len(reasoning_buffer):]
if new_reasoning:
print(new_reasoning, end="", flush=True)
reasoning_buffer = reasoning_text
if chunk.choices[0].delta.content:
content_text = chunk.choices[0].delta.content
new_text = content_text[len(text_buffer):] if text_buffer else content_text
if new_text:
print(new_text, end="", flush=True)
text_buffer = content_text
```
## Cost Calculation
Cost calculation works automatically using the pricing information in `model_prices_and_context_window.json`.
Example:
```python
response = litellm.completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Hello!"}],
api_key="your-minimax-api-key"
)
# Access cost information
print(f"Cost: ${response._hidden_params.get('response_cost', 0)}")
```

View file

@ -722,6 +722,7 @@ const sidebars = {
"providers/meta_llama",
"providers/milvus_vector_stores",
"providers/mistral",
"providers/minimax",
"providers/moonshot",
"providers/morph",
"providers/nebius",

View file

@ -1482,6 +1482,7 @@ if TYPE_CHECKING:
from .llms.bytez.chat.transformation import BytezChatConfig as BytezChatConfig
from .llms.compactifai.chat.transformation import CompactifAIChatConfig as CompactifAIChatConfig
from .llms.empower.chat.transformation import EmpowerChatConfig as EmpowerChatConfig
from .llms.minimax.chat.transformation import MinimaxChatConfig as MinimaxChatConfig
from .llms.aiohttp_openai.chat.transformation import AiohttpOpenAIChatConfig as AiohttpOpenAIChatConfig
from .llms.huggingface.chat.transformation import HuggingFaceChatConfig as HuggingFaceChatConfig
from .llms.huggingface.embedding.transformation import HuggingFaceEmbeddingConfig as HuggingFaceEmbeddingConfig

View file

@ -165,6 +165,7 @@ LLM_CONFIG_NAMES = (
"BytezChatConfig",
"CompactifAIChatConfig",
"EmpowerChatConfig",
"MinimaxChatConfig",
"AiohttpOpenAIChatConfig",
"HuggingFaceChatConfig",
"HuggingFaceEmbeddingConfig",
@ -750,6 +751,14 @@ def _lazy_import_llm_configs(name: str) -> Any: # noqa: PLR0915
_globals["EmpowerChatConfig"] = _EmpowerChatConfig
return _EmpowerChatConfig
if name == "MinimaxChatConfig":
from .llms.minimax.chat.transformation import (
MinimaxChatConfig as _MinimaxChatConfig,
)
_globals["MinimaxChatConfig"] = _MinimaxChatConfig
return _MinimaxChatConfig
if name == "AiohttpOpenAIChatConfig":
from .llms.aiohttp_openai.chat.transformation import (
AiohttpOpenAIChatConfig as _AiohttpOpenAIChatConfig,

View file

@ -4,8 +4,8 @@ import httpx
import litellm
from litellm.constants import REPLICATE_MODEL_NAME_WITH_ID_LENGTH
from litellm.secret_managers.main import get_secret, get_secret_str
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.secret_managers.main import get_secret, get_secret_str
from ..types.router import LiteLLM_Params
@ -267,6 +267,12 @@ def get_llm_provider( # noqa: PLR0915
elif endpoint == "api.moonshot.ai/v1":
custom_llm_provider = "moonshot"
dynamic_api_key = get_secret_str("MOONSHOT_API_KEY")
elif endpoint == "api.minimax.io/anthropic" or endpoint == "api.minimaxi.com/anthropic":
custom_llm_provider = "minimax"
dynamic_api_key = get_secret_str("MINIMAX_API_KEY")
elif endpoint == "api.minimax.io/v1" or endpoint == "api.minimaxi.com/v1":
custom_llm_provider = "minimax"
dynamic_api_key = get_secret_str("MINIMAX_API_KEY")
elif endpoint == "platform.publicai.co/v1":
custom_llm_provider = "publicai"
dynamic_api_key = get_secret_str("PUBLICAI_API_KEY")

View file

@ -0,0 +1,4 @@
"""
MiniMax OpenAI-compatible chat API
"""

View file

@ -0,0 +1,83 @@
"""
MiniMax OpenAI transformation config - extends OpenAI chat config for MiniMax's OpenAI-compatible API
"""
from typing import Optional
import litellm
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
from litellm.secret_managers.main import get_secret_str
class MinimaxChatConfig(OpenAIGPTConfig):
"""
MiniMax OpenAI configuration that extends OpenAIGPTConfig.
MiniMax provides an OpenAI-compatible API at:
- International: https://api.minimax.io/v1
- China: https://api.minimaxi.com/v1
Supported models:
- MiniMax-M2.1
- MiniMax-M2.1-lightning
- MiniMax-M2
"""
@staticmethod
def get_api_key(api_key: Optional[str] = None) -> Optional[str]:
"""
Get MiniMax API key from environment or parameters.
"""
return (
api_key
or get_secret_str("MINIMAX_API_KEY")
or litellm.api_key
)
@staticmethod
def get_api_base(
api_base: Optional[str] = None,
) -> str:
"""
Get MiniMax API base URL.
Defaults to international endpoint: https://api.minimax.io/v1
For China, set to: https://api.minimaxi.com/v1
"""
return (
api_base
or get_secret_str("MINIMAX_API_BASE")
or "https://api.minimax.io/v1"
)
def get_complete_url(
self,
api_base: Optional[str],
api_key: Optional[str],
model: str,
optional_params: dict,
litellm_params: dict,
stream: Optional[bool] = None,
) -> str:
"""
Get the complete URL for MiniMax OpenAI API.
Override to ensure we use MiniMax's endpoint.
"""
# Get the base URL (either provided or default MiniMax endpoint)
base_url = self.get_api_base(api_base=api_base)
# Ensure it ends with /chat/completions
if base_url.endswith("/chat/completions"):
return base_url
elif base_url.endswith("/v1"):
return f"{base_url}/chat/completions"
elif base_url.endswith("/"):
return f"{base_url}v1/chat/completions"
else:
return f"{base_url}/v1/chat/completions"
def get_supported_openai_params(self, model: str) -> list:
"""
Get supported OpenAI parameters for MiniMax.
Adds reasoning_split to the list of supported params.
"""
base_params = super().get_supported_openai_params(model=model)
return base_params + ["reasoning_split"]

View file

@ -0,0 +1,81 @@
"""
MiniMax Anthropic transformation config - extends AnthropicConfig for MiniMax's Anthropic-compatible API
"""
from typing import Optional
import litellm
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
AnthropicMessagesConfig,
)
from litellm.secret_managers.main import get_secret_str
class MinimaxMessagesConfig(AnthropicMessagesConfig):
"""
MiniMax Anthropic configuration that extends AnthropicConfig.
MiniMax provides an Anthropic-compatible API at:
- International: https://api.minimax.io/anthropic
- China: https://api.minimaxi.com/anthropic
Supported models:
- MiniMax-M2.1
- MiniMax-M2.1-lightning
- MiniMax-M2
"""
@property
def custom_llm_provider(self) -> Optional[str]:
return "minimax"
@staticmethod
def get_api_key(api_key: Optional[str] = None) -> Optional[str]:
"""
Get MiniMax API key from environment or parameters.
"""
return (
api_key
or get_secret_str("MINIMAX_API_KEY")
or litellm.api_key
)
@staticmethod
def get_api_base(
api_base: Optional[str] = None,
) -> str:
"""
Get MiniMax API base URL.
Defaults to international endpoint: https://api.minimax.io/anthropic
For China, set to: https://api.minimaxi.com/anthropic
"""
return (
api_base
or get_secret_str("MINIMAX_API_BASE")
or "https://api.minimax.io/anthropic/v1/messages"
)
def get_complete_url(
self,
api_base: Optional[str],
api_key: Optional[str],
model: str,
optional_params: dict,
litellm_params: dict,
stream: Optional[bool] = None,
) -> str:
"""
Get the complete URL for MiniMax API.
Override to ensure we use MiniMax's endpoint, not Anthropic's.
"""
# Get the base URL (either provided or default MiniMax endpoint)
base_url = self.get_api_base(api_base=api_base)
# If the base URL already includes the full path, return it
if base_url.endswith("/v1/messages"):
return base_url
# Otherwise append the messages endpoint
if base_url.endswith("/"):
return f"{base_url}v1/messages"
else:
return f"{base_url}/v1/messages"

View file

@ -68,7 +68,6 @@ from litellm.constants import (
DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT,
)
from litellm.exceptions import LiteLLMUnknownProvider
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.integrations.custom_logger import CustomLogger
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.audio_utils.utils import (
@ -98,6 +97,7 @@ from litellm.llms.base_llm.base_model_iterator import (
from litellm.llms.bedrock.common_utils import BedrockModelInfo
from litellm.llms.cohere.common_utils import CohereModelInfo
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.llms.vertex_ai.common_utils import (
VertexAIModelRoute,
get_vertex_ai_model_route,
@ -2247,6 +2247,42 @@ def completion( # type: ignore # noqa: PLR0915
logging.post_call(
input=messages, api_key=api_key, original_response=response
)
elif custom_llm_provider == "minimax":
api_key = (
api_key
or get_secret_str("MINIMAX_API_KEY")
or litellm.api_key
)
api_base = (
api_base
or litellm.api_base
or get_secret_str("MINIMAX_API_BASE")
or "https://api.minimax.io/v1"
)
response = base_llm_http_handler.completion(
model=model,
messages=messages,
api_base=api_base,
custom_llm_provider=custom_llm_provider,
model_response=model_response,
encoding=_get_encoding(),
logging_obj=logging,
optional_params=optional_params,
timeout=timeout,
litellm_params=litellm_params,
shared_session=shared_session,
acompletion=acompletion,
stream=stream,
api_key=api_key,
headers=headers,
client=client,
provider_config=provider_config,
)
logging.post_call(
input=messages, api_key=api_key, original_response=response
)
elif (
model in litellm.open_ai_chat_completion_models
or custom_llm_provider == "custom_openai"

View file

@ -1357,6 +1357,20 @@
"litellm_provider": "azure",
"mode": "chat"
},
"azure_ai/gpt-oss-120b": {
"input_cost_per_token": 1.5e-7,
"output_cost_per_token": 6e-7,
"litellm_provider": "azure_ai",
"max_input_tokens": 131072,
"max_output_tokens": 131072,
"max_tokens": 131072,
"mode": "chat",
"source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/",
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
},
"azure/eu/gpt-4o-2024-08-06": {
"deprecation_date": "2026-02-27",
"cache_read_input_token_cost": 1.375e-06,
@ -3707,6 +3721,32 @@
"/v1/images/generations"
]
},
"azure/gpt-image-1.5": {
"cache_read_input_image_token_cost": 2e-06,
"cache_read_input_token_cost": 1.25e-06,
"input_cost_per_token": 5e-06,
"input_cost_per_image_token": 8e-06,
"litellm_provider": "azure",
"mode": "image_generation",
"output_cost_per_image_token": 3.2e-05,
"supported_endpoints": [
"/v1/images/generations",
"/v1/images/edits"
]
},
"azure/gpt-image-1.5-2025-12-16": {
"cache_read_input_image_token_cost": 2e-06,
"cache_read_input_token_cost": 1.25e-06,
"input_cost_per_token": 5e-06,
"input_cost_per_image_token": 8e-06,
"litellm_provider": "azure",
"mode": "image_generation",
"output_cost_per_image_token": 3.2e-05,
"supported_endpoints": [
"/v1/images/generations",
"/v1/images/edits"
]
},
"azure/low/1024-x-1024/gpt-image-1-mini": {
"input_cost_per_pixel": 2.0751953125e-09,
"litellm_provider": "azure",
@ -19350,6 +19390,48 @@
"output_cost_per_token": 1.2e-06,
"supports_system_messages": true
},
"minimax/MiniMax-M2.1": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 1.2e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 1000000,
"max_output_tokens": 8192
},
"minimax/MiniMax-M2.1-lightning": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.4e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 1000000,
"max_output_tokens": 8192
},
"minimax/MiniMax-M2": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 1.2e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 200000,
"max_output_tokens": 8192
},
"mistral.magistral-small-2509": {
"input_cost_per_token": 5e-07,
"litellm_provider": "bedrock_converse",

View file

@ -3014,6 +3014,7 @@ class LlmProviders(str, Enum):
AMAZON_NOVA = "amazon_nova"
A2A_AGENT = "a2a_agent"
LANGGRAPH = "langgraph"
MINIMAX = "minimax"
SYNTHETIC = "synthetic"
APERTIS = "apertis"
NANOGPT = "nano-gpt"

View file

@ -7224,6 +7224,8 @@ class ProviderConfigManager:
return litellm.IBMWatsonXAIConfig()
elif litellm.LlmProviders.EMPOWER == provider:
return litellm.EmpowerChatConfig()
elif litellm.LlmProviders.MINIMAX == provider:
return litellm.MinimaxChatConfig()
elif litellm.LlmProviders.GITHUB == provider:
return litellm.GithubChatConfig()
elif litellm.LlmProviders.COMPACTIFAI == provider:
@ -7501,6 +7503,12 @@ class ProviderConfigManager:
)
return AzureAnthropicMessagesConfig()
elif litellm.LlmProviders.MINIMAX == provider:
from litellm.llms.minimax.messages.transformation import (
MinimaxMessagesConfig,
)
return MinimaxMessagesConfig()
return None
@staticmethod

View file

@ -19390,6 +19390,48 @@
"output_cost_per_token": 1.2e-06,
"supports_system_messages": true
},
"minimax/MiniMax-M2.1": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 1.2e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 1000000,
"max_output_tokens": 8192
},
"minimax/MiniMax-M2.1-lightning": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.4e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 1000000,
"max_output_tokens": 8192
},
"minimax/MiniMax-M2": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 1.2e-06,
"cache_read_input_token_cost": 3e-08,
"cache_creation_input_token_cost": 3.75e-07,
"litellm_provider": "minimax",
"mode": "chat",
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_prompt_caching": true,
"supports_system_messages": true,
"max_input_tokens": 200000,
"max_output_tokens": 8192
},
"mistral.magistral-small-2509": {
"input_cost_per_token": 5e-07,
"litellm_provider": "bedrock_converse",

View file

@ -0,0 +1,2 @@
# MiniMax tests

View file

@ -0,0 +1,2 @@
# MiniMax chat tests

View file

@ -0,0 +1,225 @@
"""
Test MiniMax OpenAI-compatible API support
"""
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(
0, os.path.abspath("../")
) # Adds the parent directory to the system path
import litellm
from litellm import completion
from litellm.llms.minimax.chat.transformation import MinimaxChatConfig
def test_minimax_chat_config():
"""Test that MinimaxChatConfig is properly configured"""
config = MinimaxChatConfig()
# Test get_api_base default
api_base = config.get_api_base()
assert api_base == "https://api.minimax.io/v1"
# Test get_api_base with custom value
custom_base = config.get_api_base(api_base="https://api.minimaxi.com/v1")
assert custom_base == "https://api.minimaxi.com/v1"
# Test get_complete_url
complete_url = config.get_complete_url(
api_base="https://api.minimax.io/v1",
api_key=None,
model="MiniMax-M2.1",
optional_params={},
litellm_params={},
stream=False
)
assert complete_url == "https://api.minimax.io/v1/chat/completions"
def test_minimax_chat_config_url_variations():
"""Test URL handling with different base URL formats"""
config = MinimaxChatConfig()
# Test with /v1 ending
url1 = config.get_complete_url(
api_base="https://api.minimax.io/v1",
api_key=None,
model="MiniMax-M2.1",
optional_params={},
litellm_params={},
)
assert url1 == "https://api.minimax.io/v1/chat/completions"
# Test with trailing slash
url2 = config.get_complete_url(
api_base="https://api.minimax.io/",
api_key=None,
model="MiniMax-M2.1",
optional_params={},
litellm_params={},
)
assert url2 == "https://api.minimax.io/v1/chat/completions"
# Test without trailing slash
url3 = config.get_complete_url(
api_base="https://api.minimax.io",
api_key=None,
model="MiniMax-M2.1",
optional_params={},
litellm_params={},
)
assert url3 == "https://api.minimax.io/v1/chat/completions"
# Test with full path already
url4 = config.get_complete_url(
api_base="https://api.minimax.io/v1/chat/completions",
api_key=None,
model="MiniMax-M2.1",
optional_params={},
litellm_params={},
)
assert url4 == "https://api.minimax.io/v1/chat/completions"
def test_minimax_provider_routing():
"""Test that minimax provider is properly routed"""
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
# Test with minimax/ prefix
model, provider, api_key, api_base = get_llm_provider(
model="minimax/MiniMax-M2.1",
api_base="https://api.minimax.io/v1"
)
assert provider == "minimax"
assert model == "MiniMax-M2.1"
def test_minimax_provider_config_manager():
"""Test that ProviderConfigManager returns MinimaxChatConfig"""
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
config = ProviderConfigManager.get_provider_chat_config(
model="MiniMax-M2.1",
provider=LlmProviders.MINIMAX
)
assert config is not None
assert isinstance(config, MinimaxChatConfig)
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_chat_completion_basic():
"""Test basic chat completion with MiniMax OpenAI-compatible API"""
response = completion(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"}
],
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/v1"
)
assert response is not None
assert hasattr(response, "choices")
assert len(response.choices) > 0
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_chat_completion_with_reasoning_split():
"""Test completion with reasoning_split parameter (MiniMax M2.1 feature)"""
response = completion(
model="minimax/MiniMax-M2.1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Solve this problem: 2+2=?"}
],
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/v1",
extra_body={"reasoning_split": True}
)
assert response is not None
# Check if reasoning_details is present in response
if hasattr(response.choices[0].message, "reasoning_details"):
assert response.choices[0].message.reasoning_details is not None
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_chat_completion_with_tools():
"""Test completion with tool calling (function calling)"""
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
},
}
]
response = completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
tools=tools,
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/v1"
)
assert response is not None
assert hasattr(response, "choices")
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_chat_completion_streaming():
"""Test streaming completion"""
response = completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Count to 5"}],
stream=True,
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/v1"
)
chunks = []
for chunk in response:
chunks.append(chunk)
assert len(chunks) > 0
if __name__ == "__main__":
# Run basic tests that don't require API key
print("Testing MiniMax Chat Config...")
test_minimax_chat_config()
print("✓ Config test passed")
print("\nTesting MiniMax Chat Config URL Variations...")
test_minimax_chat_config_url_variations()
print("✓ URL variations test passed")
print("\nTesting MiniMax Provider Routing...")
test_minimax_provider_routing()
print("✓ Routing test passed")
print("\nTesting MiniMax Provider Config Manager...")
test_minimax_provider_config_manager()
print("✓ Provider config manager test passed")
print("\n✅ All basic tests passed!")

View file

@ -0,0 +1,2 @@
# MiniMax messages tests

View file

@ -0,0 +1,147 @@
"""
Test MiniMax Anthropic-compatible API support
"""
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(
0, os.path.abspath("../")
) # Adds the parent directory to the system path
import litellm
from litellm import completion
from litellm.llms.minimax.messages.transformation import MinimaxMessagesConfig
def test_minimax_anthropic_config():
"""Test that MinimaxMessagesConfig is properly configured"""
config = MinimaxMessagesConfig()
# Test custom_llm_provider
assert config.custom_llm_provider == "minimax"
# Test get_api_base default
api_base = config.get_api_base()
assert api_base == "https://api.minimax.io/anthropic/v1/messages"
# Test get_api_base with custom value
custom_base = config.get_api_base(api_base="https://api.minimaxi.com/anthropic/v1/messages")
assert custom_base == "https://api.minimaxi.com/anthropic/v1/messages"
def test_minimax_provider_routing():
"""Test that minimax provider is properly routed"""
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
# Test with minimax/ prefix
model, provider, api_key, api_base = get_llm_provider(
model="minimax/MiniMax-M2.1",
api_base="https://api.minimax.io/anthropic/v1/messages"
)
assert provider == "minimax"
assert model == "MiniMax-M2.1"
def test_minimax_provider_config_manager():
"""Test that ProviderConfigManager returns MinimaxMessagesConfig"""
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
config = ProviderConfigManager.get_provider_anthropic_messages_config(
model="MiniMax-M2.1",
provider=LlmProviders.MINIMAX
)
assert config is not None
assert isinstance(config, MinimaxMessagesConfig)
assert config.custom_llm_provider == "minimax"
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_completion_basic():
"""Test basic completion with MiniMax Anthropic-compatible API"""
response = completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Hello, how are you?"}],
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/anthropic/v1/messages"
)
assert response is not None
assert hasattr(response, "choices")
assert len(response.choices) > 0
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_completion_with_thinking():
"""Test completion with thinking parameter (MiniMax M2.1 feature)"""
response = completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "Solve this problem: 2+2=?"}],
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/anthropic/v1/messages",
thinking={"type": "enabled", "budget_tokens": 1000}
)
assert response is not None
# Check if thinking content is present in response
for choice in response.choices:
if hasattr(choice.message, "content"):
# MiniMax returns thinking blocks similar to Anthropic
assert choice.message.content is not None
@pytest.mark.skip(reason="Requires actual MiniMax API key")
def test_minimax_completion_with_tools():
"""Test completion with tool calling (function calling)"""
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
},
}
]
response = completion(
model="minimax/MiniMax-M2.1",
messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
tools=tools,
api_key=os.getenv("MINIMAX_API_KEY"),
api_base="https://api.minimax.io/anthropic/v1/messages"
)
assert response is not None
assert hasattr(response, "choices")
if __name__ == "__main__":
# Run basic tests that don't require API key
print("Testing MiniMax Anthropic Config...")
test_minimax_anthropic_config()
print("✓ Config test passed")
print("\nTesting MiniMax Provider Routing...")
test_minimax_provider_routing()
print("✓ Routing test passed")
print("\nTesting MiniMax Provider Config Manager...")
test_minimax_provider_config_manager()
print("✓ Provider config manager test passed")
print("\n✅ All basic tests passed!")