diff --git a/frontend/public/icons/adapter-icons/MiniMax.png b/frontend/public/icons/adapter-icons/MiniMax.png new file mode 100644 index 0000000000..acaabd3da9 Binary files /dev/null and b/frontend/public/icons/adapter-icons/MiniMax.png differ diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py index ebd93e3dbd..cdb1929e15 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py @@ -512,6 +512,7 @@ def _validate_branded_openai_compatible( _NVIDIA_BUILD_API_BASE = "https://integrate.api.nvidia.com/v1" _OPENROUTER_API_BASE = "https://openrouter.ai/api/v1" +_MINIMAX_API_BASE = "https://api.minimax.io/v1" _OPENROUTER_PROVIDER_PREFIX = "openrouter/" @@ -528,6 +529,17 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: ) +class MiniMaxLLMParameters(OpenAICompatibleLLMParameters): + """OpenAI-compatible adapter for MiniMax models.""" + + # Required str so a directly-constructed instance stays valid. + api_base: str = _MINIMAX_API_BASE + + @staticmethod + def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: + return _validate_branded_openai_compatible(adapter_metadata, _MINIMAX_API_BASE) + + class OpenRouterLLMParameters(BaseChatCompletionParameters): """Adapter for OpenRouter (openrouter.ai). diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py index 2d935e218f..a3a03c7da3 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py @@ -6,6 +6,7 @@ from unstract.sdk1.adapters.llm1.anyscale import AnyscaleLLMAdapter from unstract.sdk1.adapters.llm1.azure_openai import AzureOpenAILLMAdapter from unstract.sdk1.adapters.llm1.bedrock import AWSBedrockLLMAdapter +from unstract.sdk1.adapters.llm1.minimax import MiniMaxLLMAdapter from unstract.sdk1.adapters.llm1.nvidia_build import NvidiaBuildLLMAdapter from unstract.sdk1.adapters.llm1.ollama import OllamaLLMAdapter from unstract.sdk1.adapters.llm1.openai import OpenAILLMAdapter @@ -23,6 +24,7 @@ "AnyscaleLLMAdapter", "AWSBedrockLLMAdapter", "AzureOpenAILLMAdapter", + "MiniMaxLLMAdapter", "NvidiaBuildLLMAdapter", "OllamaLLMAdapter", "OpenAILLMAdapter", diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/minimax.py b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/minimax.py new file mode 100644 index 0000000000..18f6ee3702 --- /dev/null +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/minimax.py @@ -0,0 +1,45 @@ +from typing import Any + +from unstract.sdk1.adapters.base1 import BaseAdapter, MiniMaxLLMParameters +from unstract.sdk1.adapters.enums import AdapterTypes + +DESCRIPTION = ( + "Adapter for MiniMax's OpenAI-compatible API. " + "Supply a model name and your MiniMax API key; the endpoint is preconfigured." +) + + +class MiniMaxLLMAdapter(MiniMaxLLMParameters, BaseAdapter): + @staticmethod + def get_id() -> str: + return "minimax|4f0e4241-2430-4921-81bf-8b2c6040d8d2" + + @staticmethod + def get_metadata() -> dict[str, Any]: + return { + "name": "MiniMax", + "version": "1.0.0", + "adapter": MiniMaxLLMAdapter, + "description": DESCRIPTION, + "is_active": True, + } + + @staticmethod + def get_name() -> str: + return "MiniMax" + + @staticmethod + def get_description() -> str: + return DESCRIPTION + + @staticmethod + def get_provider() -> str: + return "minimax" + + @staticmethod + def get_icon() -> str: + return "/icons/adapter-icons/MiniMax.png" + + @staticmethod + def get_adapter_type() -> AdapterTypes: + return AdapterTypes.LLM diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json new file mode 100644 index 0000000000..b7769ca42e --- /dev/null +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json @@ -0,0 +1,113 @@ +{ + "title": "MiniMax", + "type": "object", + "required": [ + "adapter_name", + "api_key", + "model" + ], + "properties": { + "adapter_name": { + "type": "string", + "title": "Name", + "default": "", + "description": "Provide a unique name for this adapter instance. Example: minimax-1" + }, + "api_key": { + "type": "string", + "title": "API Key", + "format": "password", + "description": "Your MiniMax API key from [platform.minimax.io](https://platform.minimax.io)." + }, + "model": { + "type": "string", + "title": "Model", + "default": "MiniMax-M3", + "description": "The MiniMax model name. Example: MiniMax-M3" + }, + "api_base": { + "type": "string", + "format": "url", + "title": "API Base", + "default": "https://api.minimax.io/v1", + "description": "MiniMax OpenAI-compatible endpoint. Pre-filled with the default; change only if MiniMax moves the base URL or you use a regional endpoint." + }, + "max_tokens": { + "type": "number", + "minimum": 0, + "multipleOf": 1, + "title": "Maximum Output Tokens", + "default": 4096, + "description": "Maximum number of output tokens to limit LLM replies. Leave it empty to use the provider default." + }, + "max_retries": { + "type": "number", + "minimum": 0, + "multipleOf": 1, + "title": "Max Retries", + "default": 5, + "description": "The maximum number of times to retry a request if it fails." + }, + "timeout": { + "type": "number", + "minimum": 0, + "multipleOf": 1, + "title": "Timeout", + "default": 900, + "description": "Timeout in seconds." + }, + "enable_reasoning": { + "type": "boolean", + "title": "Enable Reasoning", + "default": false, + "description": "Toggle on for reasoning models to route reasoning_effort via the OpenAI-compatible request body." + } + }, + "allOf": [ + { + "if": { + "properties": { + "enable_reasoning": { + "const": true + } + }, + "required": [ + "enable_reasoning" + ] + }, + "then": { + "properties": { + "reasoning_effort": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "default": "medium", + "title": "Reasoning Effort", + "description": "Sets the reasoning strength when Enable Reasoning is on." + } + }, + "required": [ + "reasoning_effort" + ] + } + }, + { + "if": { + "properties": { + "enable_reasoning": { + "const": false + } + }, + "required": [ + "enable_reasoning" + ] + }, + "then": { + "properties": {} + } + } + ] +} diff --git a/unstract/sdk1/tests/test_branded_openai_adapters.py b/unstract/sdk1/tests/test_branded_openai_adapters.py index c876698847..b025bfc209 100644 --- a/unstract/sdk1/tests/test_branded_openai_adapters.py +++ b/unstract/sdk1/tests/test_branded_openai_adapters.py @@ -2,6 +2,7 @@ import pytest from unstract.sdk1.adapters.base1 import ( + MiniMaxLLMParameters, NvidiaBuildEmbeddingParameters, NvidiaBuildLLMParameters, OpenAICompatibleEmbeddingParameters, @@ -14,11 +15,13 @@ OpenAICompatibleEmbeddingAdapter, ) from unstract.sdk1.adapters.llm1 import adapters as llm_adapters +from unstract.sdk1.adapters.llm1.minimax import MiniMaxLLMAdapter from unstract.sdk1.adapters.llm1.nvidia_build import NvidiaBuildLLMAdapter from unstract.sdk1.adapters.llm1.openrouter import OpenRouterLLMAdapter _NVIDIA_BUILD_API_BASE = "https://integrate.api.nvidia.com/v1" _OPENROUTER_API_BASE = "https://openrouter.ai/api/v1" +_MINIMAX_API_BASE = "https://api.minimax.io/v1" # --- Branded LLM adapters ------------------------------------------------- @@ -26,7 +29,7 @@ @pytest.mark.parametrize( "adapter", - [NvidiaBuildLLMAdapter, OpenRouterLLMAdapter], + [MiniMaxLLMAdapter, NvidiaBuildLLMAdapter, OpenRouterLLMAdapter], ) def test_branded_llm_adapter_is_registered(adapter: type) -> None: adapter_id = adapter.get_id() @@ -41,6 +44,13 @@ def test_nvidia_llm_prefixes_model_via_custom_openai() -> None: assert validated["api_base"] == _NVIDIA_BUILD_API_BASE +def test_minimax_llm_prefixes_model_via_custom_openai() -> None: + validated = MiniMaxLLMParameters.validate({"model": "MiniMax-M3", "api_key": "k"}) + + assert validated["model"] == "custom_openai/MiniMax-M3" + assert validated["api_base"] == _MINIMAX_API_BASE + + def test_openrouter_llm_routes_via_native_openrouter_provider() -> None: from litellm import get_llm_provider @@ -106,6 +116,7 @@ def test_openrouter_reasoning_survives_revalidation() -> None: @pytest.mark.parametrize( ("params", "default_base"), [ + (MiniMaxLLMParameters, _MINIMAX_API_BASE), (NvidiaBuildLLMParameters, _NVIDIA_BUILD_API_BASE), (OpenRouterLLMParameters, _OPENROUTER_API_BASE), ], @@ -120,7 +131,7 @@ def test_branded_llm_blank_api_base_falls_back_to_default( @pytest.mark.parametrize( "params", - [NvidiaBuildLLMParameters, OpenRouterLLMParameters], + [MiniMaxLLMParameters, NvidiaBuildLLMParameters, OpenRouterLLMParameters], ) def test_branded_llm_honours_api_base_override(params: type) -> None: validated = params.validate( @@ -133,6 +144,7 @@ def test_branded_llm_honours_api_base_override(params: type) -> None: @pytest.mark.parametrize( ("adapter", "default_base"), [ + (MiniMaxLLMAdapter, _MINIMAX_API_BASE), (NvidiaBuildLLMAdapter, _NVIDIA_BUILD_API_BASE), (OpenRouterLLMAdapter, _OPENROUTER_API_BASE), ],