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
Binary file added frontend/public/icons/adapter-icons/MiniMax.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 88 additions & 2 deletions unstract/sdk1/src/unstract/sdk1/adapters/base1.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,17 @@

_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/"
_MINIMAX_PROVIDER_PREFIX = "minimax/"
_MINIMAX_ANTHROPIC_PROVIDER_PREFIX = "anthropic/"


def _minimax_provider_prefix(api_base: str) -> str:
path = urlparse(api_base).path.rstrip("/").lower()
if path.endswith("/anthropic"):
return _MINIMAX_ANTHROPIC_PROVIDER_PREFIX
return _MINIMAX_PROVIDER_PREFIX


class NvidiaBuildLLMParameters(OpenAICompatibleLLMParameters):
Expand All @@ -546,6 +556,83 @@
)


class MiniMaxLLMParameters(BaseChatCompletionParameters):
"""Adapter for MiniMax's OpenAI- and Anthropic-compatible APIs."""

api_key: str
api_base: str = _MINIMAX_API_BASE
temperature: float | None = Field(default=1, ge=0, le=2)
thinking: dict[str, str] | None = None
service_tier: str | None = None

@staticmethod
def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:

Check failure on line 569 in unstract/sdk1/src/unstract/sdk1/adapters/base1.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=Zipstack_unstract&issues=AZ9hLO49nQ6LQh7VP5r6&open=AZ9hLO49nQ6LQh7VP5r6&pullRequest=2166
adapter_metadata = dict(adapter_metadata)
api_base = adapter_metadata.get("api_base")
if not (isinstance(api_base, str) and api_base.strip()):
adapter_metadata["api_base"] = _MINIMAX_API_BASE

adapter_metadata["model"] = MiniMaxLLMParameters.validate_model(adapter_metadata)
model_id = adapter_metadata["model"].split("/", 1)[-1]

service_tier = adapter_metadata.get("service_tier")
if service_tier not in {None, "standard", "priority"}:
raise ValueError("service_tier must be standard or priority.")

if "enable_thinking" in adapter_metadata:
enable_thinking = adapter_metadata.pop("enable_thinking")
if not isinstance(enable_thinking, bool):
raise ValueError("enable_thinking must be a boolean.")
adapter_metadata["thinking"] = {
"type": "adaptive" if enable_thinking else "disabled"
}

thinking = adapter_metadata.get("thinking")
if thinking is None and model_id.lower().startswith("minimax-m2"):
thinking = {"type": "adaptive"}
adapter_metadata["thinking"] = thinking
if thinking is not None:
if not isinstance(thinking, dict) or thinking.get("type") not in {
"adaptive",
"disabled",
}:
raise ValueError("thinking.type must be adaptive or disabled.")
if (
model_id.lower().startswith("minimax-m2")
and thinking["type"] == "disabled"
):
raise ValueError(f"{model_id} does not support disabling thinking.")

validated = MiniMaxLLMParameters(**adapter_metadata).model_dump()
if (
_minimax_provider_prefix(validated["api_base"])
== _MINIMAX_ANTHROPIC_PROVIDER_PREFIX
and validated["temperature"] is not None
and validated["temperature"] > 1
):
raise ValueError(
"temperature must be between 0 and 1 for the Anthropic-compatible API."
)
validated["cost_model"] = f"{_MINIMAX_PROVIDER_PREFIX}{model_id}"
validated["allowed_openai_params"] = ["service_tier", "thinking"]
return validated

@staticmethod
def validate_model(adapter_metadata: dict[str, "Any"]) -> str:
model = str(adapter_metadata.get("model", "")).strip()
if not model:
raise ValueError("model is required for the MiniMax adapter.")
for prefix in (
_MINIMAX_PROVIDER_PREFIX,
_MINIMAX_ANTHROPIC_PROVIDER_PREFIX,
):
if model.startswith(prefix):
model = model[len(prefix) :]
break
api_base = str(adapter_metadata.get("api_base") or _MINIMAX_API_BASE)
return f"{_minimax_provider_prefix(api_base)}{model}"


class OpenRouterLLMParameters(BaseChatCompletionParameters):
"""Adapter for OpenRouter (openrouter.ai).

Expand Down Expand Up @@ -884,8 +971,7 @@
token = validated.pop(_BEDROCK_BEARER_TOKEN_FIELD, None)
if not isinstance(token, str) or not token.strip():
raise ValueError(
f"{_BEDROCK_BEARER_TOKEN_FIELD} is required when "
"auth_type is 'bearer_token'."
f"{_BEDROCK_BEARER_TOKEN_FIELD} is required when auth_type is 'bearer_token'."
)
validated[_BEDROCK_LITELLM_BEARER_KWARG] = token.strip()

Expand Down
2 changes: 2 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,6 +24,7 @@
"AnyscaleLLMAdapter",
"AWSBedrockLLMAdapter",
"AzureOpenAILLMAdapter",
"MiniMaxLLMAdapter",
"NvidiaBuildLLMAdapter",
"OllamaLLMAdapter",
"OpenAILLMAdapter",
Expand Down
45 changes: 45 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/minimax.py
Original file line number Diff line number Diff line change
@@ -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- and Anthropic-compatible APIs. "
"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
80 changes: 80 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"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",
"examples": [
"MiniMax-M3",
"MiniMax-M2.7"
],
"description": "MiniMax-M3 supports a 1,000,000-token context window with text, image, and video input. MiniMax-M2.7 supports a 204,800-token context window with text input."
},
"api_base": {
"type": "string",
"format": "url",
"title": "API Base",
"default": "https://api.minimax.io/v1",
"description": "The default https://api.minimax.io/v1 is the global OpenAI-compatible endpoint. Other supported bases are https://api.minimax.io/anthropic for global Anthropic-compatible requests, https://api.minimaxi.com/v1 for China OpenAI-compatible requests, and https://api.minimaxi.com/anthropic for China Anthropic-compatible requests."
},
"service_tier": {
"type": "string",
"title": "Service Tier",
"enum": [
"standard",
"priority"
],
"default": "standard",
"description": "Priority requests receive priority admission and use the provider's priority pricing."
},
"max_tokens": {
"type": "number",
"minimum": 0,
"multipleOf": 1,
"title": "Maximum Output Tokens",
"default": 4096,
"description": "Maximum output length, subject to the selected model's total context window."
},
"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_thinking": {
"type": "boolean",
"title": "Enable Thinking",
"default": true,
"description": "Enable adaptive thinking for MiniMax-M3. MiniMax-M2.7 always keeps thinking enabled."
}
}
}
Loading