diff --git a/src/autogluon/assistant/configs/minimax.yaml b/src/autogluon/assistant/configs/minimax.yaml new file mode 100644 index 00000000..ac055330 --- /dev/null +++ b/src/autogluon/assistant/configs/minimax.yaml @@ -0,0 +1,88 @@ +# MiniMax Configuration + +per_execution_timeout: 86400 + +# Data Perception +max_file_group_size_to_show: 5 +num_example_files_to_show: 1 + +max_chars_per_file: 768 +num_tutorial_retrievals: 30 +max_num_tutorials: 5 +max_user_input_length: 2048 +max_error_message_length: 2048 +max_tutorial_length: 32768 +configure_env: false +condense_tutorials: True +use_tutorial_summary: True +continuous_improvement: False +optimize_system_resources: False +cleanup_unused_env: True +enable_meta_prompting: False + +llm: &default_llm + provider: minimax + model: MiniMax-M3 + # Regional route: global_en (api.minimax.io) or cn_zh (api.minimaxi.com) + region: global_en + # Compatibility route: openai or anthropic + api: openai + max_tokens: 65535 + proxy_url: null + temperature: 0.1 + top_p: 0.9 + verbose: True + multi_turn: False + template: null + add_coding_format_instruction: false + apply_meta_prompting: False + +# Ensure all agent types inherit the MiniMax LLM config +python_coder: + <<: *default_llm # Merge llm_config + multi_turn: True + apply_meta_prompting: True + +bash_coder: + <<: *default_llm # Merge llm_config + multi_turn: True + +executer: + <<: *default_llm # Merge llm_config + max_stdout_length: 8192 + max_stderr_length: 2048 + +meta_prompting: + <<: *default_llm # Merge llm_config + multi_turn: False + +reader: + <<: *default_llm # Merge llm_config + details: False + +error_analyzer: + <<: *default_llm # Merge llm_config + +retriever: + <<: *default_llm # Merge llm_config + +reranker: + <<: *default_llm # Merge llm_config + temperature: 0. + top_p: 1. + +description_file_retriever: + <<: *default_llm # Merge llm_config + temperature: 0. + top_p: 1. + +task_descriptor: + <<: *default_llm # Merge llm_config + max_description_files_length_to_show: 1024 + max_description_files_length_for_summarization: 16384 + apply_meta_prompting: True + +tool_selector: + <<: *default_llm # Merge llm_config + temperature: 0. + top_p: 1. diff --git a/src/autogluon/assistant/llm/llm_factory.py b/src/autogluon/assistant/llm/llm_factory.py index 443a7c58..dd163c63 100644 --- a/src/autogluon/assistant/llm/llm_factory.py +++ b/src/autogluon/assistant/llm/llm_factory.py @@ -8,6 +8,7 @@ from .azure_openai_chat import AssistantAzureChatOpenAI, create_azure_openai_chat, get_azure_models from .base_chat import GlobalTokenTracker from .bedrock_chat import AssistantChatBedrock, create_bedrock_chat, get_bedrock_models +from .minimax_chat import AssistantChatMiniMax, AssistantChatMiniMaxAnthropic, create_minimax_chat, get_minimax_models from .openai_chat import AssistantChatOpenAI, create_openai_chat, get_openai_models from .sagemaker_chat import SagemakerEndpointChat, create_sagemaker_chat, get_sagemaker_endpoints @@ -34,12 +35,14 @@ def get_valid_models(cls, provider): return get_anthropic_models() elif provider == "sagemaker": return get_sagemaker_endpoints() + elif provider == "minimax": + return get_minimax_models() else: raise ValueError(f"Unsupported provider: {provider}") @classmethod def get_valid_providers(cls): - return ["azure", "openai", "bedrock", "anthropic", "sagemaker"] + return ["azure", "openai", "bedrock", "anthropic", "sagemaker", "minimax"] @classmethod def get_chat_model(cls, config: DictConfig, session_name: str) -> Union[ @@ -48,6 +51,8 @@ def get_chat_model(cls, config: DictConfig, session_name: str) -> Union[ AssistantChatBedrock, AssistantChatAnthropic, SagemakerEndpointChat, + AssistantChatMiniMax, + AssistantChatMiniMaxAnthropic, ]: """Get a configured chat model instance using LangGraph patterns.""" provider = config.provider @@ -75,5 +80,7 @@ def get_chat_model(cls, config: DictConfig, session_name: str) -> Union[ return create_bedrock_chat(config, session_name) elif provider == "sagemaker": return create_sagemaker_chat(config, session_name) + elif provider == "minimax": + return create_minimax_chat(config, session_name) else: raise ValueError(f"Unsupported provider: {provider}") diff --git a/src/autogluon/assistant/llm/minimax_chat.py b/src/autogluon/assistant/llm/minimax_chat.py new file mode 100644 index 00000000..ea01e4c7 --- /dev/null +++ b/src/autogluon/assistant/llm/minimax_chat.py @@ -0,0 +1,134 @@ +import logging +import os +from typing import Any, Dict, List, Union + +from langchain_anthropic import ChatAnthropic +from langchain_openai import ChatOpenAI +from openai import OpenAI + +from .base_chat import BaseAssistantChat + +logger = logging.getLogger(__name__) + +# Regional routes exposed by MiniMax. Each region serves both an +# OpenAI-compatible endpoint and an Anthropic-compatible endpoint. +MINIMAX_REGIONAL_ENDPOINTS = { + "global_en": { + "openai_base_url": "https://api.minimax.io/v1", + "anthropic_base_url": "https://api.minimax.io/anthropic", + }, + "cn_zh": { + "openai_base_url": "https://api.minimaxi.com/v1", + "anthropic_base_url": "https://api.minimaxi.com/anthropic", + }, +} + +DEFAULT_MINIMAX_REGION = "global_en" + +# Supported compatibility routes. +MINIMAX_APIS = ("openai", "anthropic") +DEFAULT_MINIMAX_API = "openai" + +# Current MiniMax chat models, used as a fallback when live discovery is unavailable. +MINIMAX_MODELS = ["MiniMax-M3", "MiniMax-M2.7"] +DEFAULT_MINIMAX_MODEL = "MiniMax-M3" + + +class AssistantChatMiniMax(ChatOpenAI, BaseAssistantChat): + """MiniMax chat model over the OpenAI-compatible route with LangGraph support.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.initialize_conversation(self) + + def describe(self) -> Dict[str, Any]: + base_desc = super().describe() + return {**base_desc, "model": self.model_name, "base_url": self.openai_api_base} + + +class AssistantChatMiniMaxAnthropic(ChatAnthropic, BaseAssistantChat): + """MiniMax chat model over the Anthropic-compatible route with LangGraph support.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.initialize_conversation(self) + + def describe(self) -> Dict[str, Any]: + base_desc = super().describe() + return {**base_desc, "model": self.model} + + +def _resolve_region(region: str) -> Dict[str, str]: + if region not in MINIMAX_REGIONAL_ENDPOINTS: + raise ValueError( + f"Invalid MiniMax region: {region}. Must be one of {list(MINIMAX_REGIONAL_ENDPOINTS)}" + ) + return MINIMAX_REGIONAL_ENDPOINTS[region] + + +def get_minimax_models(region: str = DEFAULT_MINIMAX_REGION) -> List[str]: + """Get available MiniMax models via the OpenAI-compatible route with a static fallback.""" + endpoints = _resolve_region(region) + try: + client = OpenAI(api_key=os.environ.get("MINIMAX_API_KEY"), base_url=endpoints["openai_base_url"]) + models = client.models.list() + discovered = [model.id for model in models.data if model.id.startswith("MiniMax")] + if discovered: + return discovered + except Exception as e: + logger.warning(f"Failed to fetch MiniMax models: {e}") + return list(MINIMAX_MODELS) + + +def create_minimax_chat(config, session_name: str) -> Union[AssistantChatMiniMax, AssistantChatMiniMaxAnthropic]: + """Create a MiniMax chat model instance for the selected regional and compatibility route.""" + model = config.model + + if "MINIMAX_API_KEY" not in os.environ: + raise ValueError("MiniMax API key not found in environment") + + region = getattr(config, "region", DEFAULT_MINIMAX_REGION) + endpoints = _resolve_region(region) + + api = getattr(config, "api", DEFAULT_MINIMAX_API) + if api not in MINIMAX_APIS: + raise ValueError(f"Invalid MiniMax api: {api}. Must be one of {list(MINIMAX_APIS)}") + + api_key = os.environ["MINIMAX_API_KEY"] + logger.info(f"Using MiniMax model: {model} ({api} route, region {region}) for session: {session_name}") + + if api == "anthropic": + kwargs = { + "model": model, + "anthropic_api_key": api_key, + "anthropic_api_url": endpoints["anthropic_base_url"], + "session_name": session_name, + "max_tokens": config.max_tokens, + } + + if hasattr(config, "temperature"): + kwargs["temperature"] = config.temperature + + if hasattr(config, "verbose"): + kwargs["verbose"] = config.verbose + + if hasattr(config, "thinking") and hasattr(config.thinking, "enabled"): + kwargs["thinking"] = config.thinking + + return AssistantChatMiniMaxAnthropic(**kwargs) + + kwargs = { + "model_name": model, + "openai_api_key": api_key, + "openai_api_base": endpoints["openai_base_url"], + "session_name": session_name, + "max_tokens": config.max_tokens, + } + + if hasattr(config, "temperature"): + kwargs["temperature"] = config.temperature + + if hasattr(config, "verbose"): + kwargs["verbose"] = config.verbose + + return AssistantChatMiniMax(**kwargs) diff --git a/tests/unittests/llm/__init__.py b/tests/unittests/llm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unittests/llm/test_minimax_provider.py b/tests/unittests/llm/test_minimax_provider.py new file mode 100644 index 00000000..73dbce2f --- /dev/null +++ b/tests/unittests/llm/test_minimax_provider.py @@ -0,0 +1,32 @@ +import pytest + +# These imports pull in optional heavy runtime dependencies; skip the whole +# module when they are unavailable so the suite still collects cleanly. +pytest.importorskip("langchain_openai") +pytest.importorskip("langchain_anthropic") +pytest.importorskip("langchain_aws") +pytest.importorskip("langgraph") + + +def test_minimax_is_registered_provider(): + from autogluon.assistant.llm.llm_factory import ChatLLMFactory + + assert "minimax" in ChatLLMFactory.get_valid_providers() + + +def test_minimax_regional_endpoints(): + from autogluon.assistant.llm.minimax_chat import MINIMAX_REGIONAL_ENDPOINTS + + assert set(MINIMAX_REGIONAL_ENDPOINTS) == {"global_en", "cn_zh"} + assert MINIMAX_REGIONAL_ENDPOINTS["global_en"]["openai_base_url"] == "https://api.minimax.io/v1" + assert MINIMAX_REGIONAL_ENDPOINTS["global_en"]["anthropic_base_url"] == "https://api.minimax.io/anthropic" + assert MINIMAX_REGIONAL_ENDPOINTS["cn_zh"]["openai_base_url"] == "https://api.minimaxi.com/v1" + assert MINIMAX_REGIONAL_ENDPOINTS["cn_zh"]["anthropic_base_url"] == "https://api.minimaxi.com/anthropic" + + +def test_minimax_models_include_current_releases(): + from autogluon.assistant.llm.minimax_chat import DEFAULT_MINIMAX_MODEL, MINIMAX_MODELS + + assert DEFAULT_MINIMAX_MODEL == "MiniMax-M3" + assert "MiniMax-M3" in MINIMAX_MODELS + assert "MiniMax-M2.7" in MINIMAX_MODELS