-
-
Notifications
You must be signed in to change notification settings - Fork 86
feat: add MiniMax as a first-class LLM provider (M2.7/M2.5, 204K context) #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -52,6 +52,15 @@ | |||||||||||||||
| # --- Chutes --- | ||||||||||||||||
| #CHUTES_API_KEY_1="YOUR_CHUTES_API_KEY" | ||||||||||||||||
|
|
||||||||||||||||
| # --- MiniMax --- | ||||||||||||||||
| # Supports MiniMax-M2.7, MiniMax-M2.7-highspeed, MiniMax-M2.5, MiniMax-M2.5-highspeed. | ||||||||||||||||
| # All models share 204K context. Obtain your key at https://platform.minimaxi.com | ||||||||||||||||
| # Note: MiniMax requires temperature > 0. Set OVERRIDE_TEMPERATURE_ZERO=set (below) | ||||||||||||||||
| # to automatically convert temperature=0 requests to temperature=1.0. | ||||||||||||||||
| #MINIMAX_API_KEY_1="YOUR_MINIMAX_API_KEY" | ||||||||||||||||
|
Comment on lines
+58
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the temperature override hint actionable. This note tells users to set Suggested fix # Note: MiniMax requires temperature > 0. Set OVERRIDE_TEMPERATURE_ZERO=set (below)
# to automatically convert temperature=0 requests to temperature=1.0.
+#OVERRIDE_TEMPERATURE_ZERO=set
`#MINIMAX_API_KEY_1`="YOUR_MINIMAX_API_KEY"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| # Optional: override the API base URL (default: https://api.minimax.io/v1) | ||||||||||||||||
| #MINIMAX_API_BASE="https://api.minimax.io/v1" | ||||||||||||||||
|
|
||||||||||||||||
| # ------------------------------------------------------------------------------ | ||||||||||||||||
| # | [OAUTH] Provider OAuth 2.0 Credentials | | ||||||||||||||||
| # ------------------------------------------------------------------------------ | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| [pytest] | ||
| asyncio_mode = strict | ||
| markers = | ||
| integration: marks tests as integration tests requiring a live MINIMAX_API_KEY (deselect with '-m "not integration"') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| "cohere": "https://api.cohere.ai/v1", | ||
| "bedrock": "https://bedrock-runtime.us-east-1.amazonaws.com", | ||
| "openrouter": "https://openrouter.ai/api/v1", | ||
| "minimax": "https://api.minimax.io/v1", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honor
Suggested fix- # First, check the hardcoded map
- base_url = PROVIDER_URL_MAP.get(provider)
-
- # If not found, check for custom provider via environment variable
- if not base_url:
- api_base_env = f"{provider.upper()}_API_BASE"
- base_url = os.getenv(api_base_env)
- if not base_url:
- return None
+ api_base_env = f"{provider.upper()}_API_BASE"
+ base_url = os.getenv(api_base_env) or PROVIDER_URL_MAP.get(provider)
+ if not base_url:
+ return None🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| def get_provider_endpoint(provider: str, model_name: str, incoming_path: str) -> Optional[str]: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,142 @@ | ||||||||||||||||||||||||||||||||||
| # SPDX-License-Identifier: LGPL-3.0-only | ||||||||||||||||||||||||||||||||||
| # Copyright (c) 2026 Mirrowel | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import httpx | ||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||
| from typing import List, Dict, Any, AsyncGenerator, Union | ||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||
| import litellm | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| from .provider_interface import ProviderInterface | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| lib_logger = logging.getLogger("rotator_library") | ||||||||||||||||||||||||||||||||||
| lib_logger.propagate = False | ||||||||||||||||||||||||||||||||||
| if not lib_logger.handlers: | ||||||||||||||||||||||||||||||||||
| lib_logger.addHandler(logging.NullHandler()) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Static fallback models used when the MiniMax /v1/models endpoint is unavailable. | ||||||||||||||||||||||||||||||||||
| # MiniMax-M2.7 is the latest generation; all models share 204K context. | ||||||||||||||||||||||||||||||||||
| _MINIMAX_STATIC_MODELS = [ | ||||||||||||||||||||||||||||||||||
| "MiniMax-M2.7", | ||||||||||||||||||||||||||||||||||
| "MiniMax-M2.7-highspeed", | ||||||||||||||||||||||||||||||||||
| "MiniMax-M2.5", | ||||||||||||||||||||||||||||||||||
| "MiniMax-M2.5-highspeed", | ||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # MiniMax requires temperature strictly greater than 0. | ||||||||||||||||||||||||||||||||||
| # Values at or below 0 are clamped to this minimum before forwarding the request. | ||||||||||||||||||||||||||||||||||
| _MINIMAX_TEMPERATURE_MIN = 0.01 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class MiniMaxProvider(ProviderInterface): | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| Provider implementation for the MiniMax API. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| MiniMax exposes an OpenAI-compatible chat/completions endpoint at | ||||||||||||||||||||||||||||||||||
| https://api.minimax.io/v1. LiteLLM routes requests using the | ||||||||||||||||||||||||||||||||||
| ``minimax/`` prefix, so no custom acompletion logic is required for the | ||||||||||||||||||||||||||||||||||
| happy path. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| The only provider-specific behaviour handled here is: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| * **Model discovery** – fetches the live model list from the MiniMax | ||||||||||||||||||||||||||||||||||
| ``/v1/models`` endpoint and falls back to a static list of known | ||||||||||||||||||||||||||||||||||
| M2.7 / M2.5 models when the endpoint is unreachable. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| * **Temperature clamping** – MiniMax rejects ``temperature=0`` (or any | ||||||||||||||||||||||||||||||||||
| value ≤ 0). Requests that carry such a temperature are silently | ||||||||||||||||||||||||||||||||||
| adjusted to ``_MINIMAX_TEMPERATURE_MIN`` (0.01) before being forwarded | ||||||||||||||||||||||||||||||||||
| to LiteLLM. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Configuration | ||||||||||||||||||||||||||||||||||
| ------------- | ||||||||||||||||||||||||||||||||||
| Set one or more API keys using the numbered ``_API_KEY`` suffix pattern:: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| MINIMAX_API_KEY_1=your_key_here | ||||||||||||||||||||||||||||||||||
| MINIMAX_API_KEY_2=another_key_here | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Optionally override the API base URL (defaults to | ||||||||||||||||||||||||||||||||||
| ``https://api.minimax.io/v1``):: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| MINIMAX_API_BASE=https://api.minimax.io/v1 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| To avoid sending ``temperature=0`` to MiniMax from any client, you may | ||||||||||||||||||||||||||||||||||
| also set the global proxy option:: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| OVERRIDE_TEMPERATURE_ZERO=set | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]: | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| Returns available MiniMax model names (with ``minimax/`` prefix). | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Tries to discover models dynamically from the ``/v1/models`` endpoint. | ||||||||||||||||||||||||||||||||||
| Falls back to the static list when the endpoint is unavailable or | ||||||||||||||||||||||||||||||||||
| returns an unexpected payload. | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||
| response = await client.get( | ||||||||||||||||||||||||||||||||||
| "https://api.minimax.io/v1/models", | ||||||||||||||||||||||||||||||||||
| headers={"Authorization": f"Bearer {api_key}"}, | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You'll also need to add |
||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Model discovery still ignores The provider advertises an overridable base URL, but Lines 77-80 always call the public Suggested fix+import os
import httpx
import logging
from typing import List, Dict, Any, AsyncGenerator, Union
import litellm
@@
try:
+ api_base = os.getenv("MINIMAX_API_BASE", "https://api.minimax.io/v1").rstrip("/")
response = await client.get(
- "https://api.minimax.io/v1/models",
+ f"{api_base}/models",
headers={"Authorization": f"Bearer {api_key}"},
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| response.raise_for_status() | ||||||||||||||||||||||||||||||||||
| data = response.json().get("data", []) | ||||||||||||||||||||||||||||||||||
| if data: | ||||||||||||||||||||||||||||||||||
| models = [f"minimax/{model['id']}" for model in data] | ||||||||||||||||||||||||||||||||||
| lib_logger.debug( | ||||||||||||||||||||||||||||||||||
| f"MiniMaxProvider: discovered {len(models)} models from API" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| return models | ||||||||||||||||||||||||||||||||||
| except httpx.RequestError as e: | ||||||||||||||||||||||||||||||||||
| lib_logger.warning( | ||||||||||||||||||||||||||||||||||
| f"MiniMaxProvider: failed to fetch models from API, using static list: {e}" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||
| lib_logger.warning( | ||||||||||||||||||||||||||||||||||
| f"MiniMaxProvider: unexpected error fetching models, using static list: {e}" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Return the static fallback list | ||||||||||||||||||||||||||||||||||
| static = [f"minimax/{m}" for m in _MINIMAX_STATIC_MODELS] | ||||||||||||||||||||||||||||||||||
| lib_logger.info( | ||||||||||||||||||||||||||||||||||
| f"MiniMaxProvider: returning {len(static)} static fallback models" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| return static | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||
| # Temperature clamping | ||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def has_custom_logic(self) -> bool: | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| Enable custom acompletion so we can clamp temperature before the | ||||||||||||||||||||||||||||||||||
| request reaches the MiniMax API. | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| async def acompletion( | ||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||
| client: httpx.AsyncClient, | ||||||||||||||||||||||||||||||||||
| **kwargs: Any, | ||||||||||||||||||||||||||||||||||
| ) -> Union[litellm.ModelResponse, AsyncGenerator[litellm.ModelResponse, None]]: | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| Forward the completion request to LiteLLM after clamping temperature. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| MiniMax rejects any ``temperature`` value that is not strictly | ||||||||||||||||||||||||||||||||||
| positive. When the caller sends ``temperature=0`` (common for | ||||||||||||||||||||||||||||||||||
| deterministic / tool-use requests) this method silently raises the | ||||||||||||||||||||||||||||||||||
| value to ``_MINIMAX_TEMPERATURE_MIN``. | ||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||
| temperature = kwargs.get("temperature") | ||||||||||||||||||||||||||||||||||
| if temperature is not None and temperature <= 0: | ||||||||||||||||||||||||||||||||||
| lib_logger.debug( | ||||||||||||||||||||||||||||||||||
| f"MiniMaxProvider: clamping temperature {temperature!r} → " | ||||||||||||||||||||||||||||||||||
| f"{_MINIMAX_TEMPERATURE_MIN} (MiniMax requires temperature > 0)" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| kwargs = dict(kwargs) | ||||||||||||||||||||||||||||||||||
| kwargs["temperature"] = _MINIMAX_TEMPERATURE_MIN | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Remove internal proxy keys that must not reach LiteLLM | ||||||||||||||||||||||||||||||||||
| kwargs.pop("credential_identifier", None) | ||||||||||||||||||||||||||||||||||
| kwargs.pop("transaction_context", None) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return await litellm.acompletion(**kwargs) | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import sys | ||
| import os | ||
|
|
||
| # Allow imports from the src directory without installing the package | ||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says
OVERRIDE_TEMPERATURE_ZEROwill convert temperature=0 requests to1.0, but the MiniMax provider actually clamps to0.01(_MINIMAX_TEMPERATURE_MIN). Users relying on this comment will be surprised when they observe 0.01 in practice.