diff --git a/skillopt/model/__init__.py b/skillopt/model/__init__.py index bd33aa1..cbd5358 100644 --- a/skillopt/model/__init__.py +++ b/skillopt/model/__init__.py @@ -6,6 +6,7 @@ from typing import Any from skillopt.model import azure_openai as _openai from skillopt.model import claude_backend as _claude +from skillopt.model import minimax_backend as _minimax from skillopt.model import qwen_backend as _qwen from skillopt.model.backend_config import ( # noqa: F401 configure_claude_code_exec, @@ -50,6 +51,10 @@ def set_backend(name: str | None) -> str: set_optimizer_backend("openai_chat") set_target_backend("qwen_chat") return "qwen_chat" + if normalized in {"minimax", "minimax_chat"}: + set_optimizer_backend("openai_chat") + set_target_backend("minimax_chat") + return "minimax_chat" raise ValueError(f"Unsupported legacy backend: {name!r}") @@ -65,6 +70,8 @@ def get_backend_name() -> str: return "codex" if optimizer == "openai_chat" and target == "qwen_chat": return "qwen_chat" + if optimizer == "openai_chat" and target == "minimax_chat": + return "minimax_chat" return f"{optimizer}+{target}" @@ -124,9 +131,18 @@ def chat_target( stage=stage, reasoning_effort=reasoning_effort, ) + if get_target_backend() == "minimax_chat": + return _minimax.chat_target( + system=system, + user=user, + max_completion_tokens=max_completion_tokens, + retries=retries, + stage=stage, + reasoning_effort=reasoning_effort, + ) if not is_target_chat_backend(): raise NotImplementedError( - "chat_target is only supported with target_backend=openai_chat, claude_chat, or qwen_chat. " + "chat_target is only supported with target_backend=openai_chat, claude_chat, qwen_chat, or minimax_chat. " "Exec backends are handled in environment-specific rollout code." ) return _openai.chat_target( @@ -210,9 +226,20 @@ def chat_target_messages( tool_choice=tool_choice, return_message=return_message, ) + if get_target_backend() == "minimax_chat": + return _minimax.chat_target_messages( + messages=messages, + max_completion_tokens=max_completion_tokens, + retries=retries, + stage=stage, + reasoning_effort=reasoning_effort, + tools=tools, + tool_choice=tool_choice, + return_message=return_message, + ) if not is_target_chat_backend(): raise NotImplementedError( - "chat_target_messages is only supported with target_backend=openai_chat, claude_chat, or qwen_chat. " + "chat_target_messages is only supported with target_backend=openai_chat, claude_chat, qwen_chat, or minimax_chat. " "Exec backends are handled in environment-specific rollout code." ) return _openai.chat_target_messages( @@ -301,6 +328,17 @@ def get_token_summary() -> dict: summary[stage]["prompt_tokens"] += values["prompt_tokens"] summary[stage]["completion_tokens"] += values["completion_tokens"] summary[stage]["total_tokens"] += values["total_tokens"] + minimax_summary = _minimax.get_token_summary() + for stage, values in minimax_summary.items(): + if stage == "_total": + continue + if stage not in summary: + summary[stage] = values + continue + summary[stage]["calls"] += values["calls"] + summary[stage]["prompt_tokens"] += values["prompt_tokens"] + summary[stage]["completion_tokens"] += values["completion_tokens"] + summary[stage]["total_tokens"] += values["total_tokens"] total = { "calls": 0, "prompt_tokens": 0, @@ -322,6 +360,7 @@ def reset_token_tracker() -> None: _openai.reset_token_tracker() _claude.reset_token_tracker() _qwen.reset_token_tracker() + _minimax.reset_token_tracker() def configure_azure_openai( @@ -386,16 +425,37 @@ def configure_qwen_chat( ) +def configure_minimax_chat( + *, + base_url: str | None = None, + api_key: str | None = None, + temperature: float | str | None = None, + timeout_seconds: float | str | None = None, + max_tokens: int | str | None = None, + enable_thinking: bool | str | None = None, +) -> None: + _minimax.configure_minimax_chat( + base_url=base_url, + api_key=api_key, + temperature=temperature, + timeout_seconds=timeout_seconds, + max_tokens=max_tokens, + enable_thinking=enable_thinking, + ) + + def set_reasoning_effort(effort: str | None) -> None: _openai.set_reasoning_effort(effort) _claude.set_reasoning_effort(effort) _qwen.set_reasoning_effort(effort) + _minimax.set_reasoning_effort(effort) def set_target_deployment(deployment: str) -> None: _openai.set_target_deployment(deployment) _claude.set_target_deployment(deployment) _qwen.set_target_deployment(deployment) + _minimax.set_target_deployment(deployment) def set_optimizer_deployment(deployment: str) -> None: diff --git a/skillopt/model/backend_config.py b/skillopt/model/backend_config.py index cf36b7e..2cdc8c3 100644 --- a/skillopt/model/backend_config.py +++ b/skillopt/model/backend_config.py @@ -49,10 +49,10 @@ CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS = max( def set_optimizer_backend(backend: str) -> None: global OPTIMIZER_BACKEND OPTIMIZER_BACKEND = normalize_backend_name(backend or "openai_chat") - if OPTIMIZER_BACKEND not in {"openai_chat", "claude_chat"}: + if OPTIMIZER_BACKEND not in {"openai_chat", "claude_chat", "minimax_chat"}: raise ValueError( f"Unsupported optimizer backend: {OPTIMIZER_BACKEND!r}. " - "Supported values are 'openai_chat' and 'claude_chat'." + "Supported values are 'openai_chat', 'claude_chat', and 'minimax_chat'." ) os.environ["OPTIMIZER_BACKEND"] = OPTIMIZER_BACKEND @@ -64,10 +64,10 @@ def get_optimizer_backend() -> str: def set_target_backend(backend: str) -> None: global TARGET_BACKEND TARGET_BACKEND = normalize_backend_name(backend or "openai_chat") - if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "codex_exec", "claude_code_exec"}: + if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "codex_exec", "claude_code_exec"}: raise ValueError( f"Unsupported target backend: {TARGET_BACKEND!r}. " - "Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'codex_exec', and 'claude_code_exec'." + "Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'minimax_chat', 'codex_exec', and 'claude_code_exec'." ) os.environ["TARGET_BACKEND"] = TARGET_BACKEND @@ -81,11 +81,11 @@ def is_target_exec_backend() -> bool: def is_optimizer_chat_backend() -> bool: - return OPTIMIZER_BACKEND in {"openai_chat", "claude_chat"} + return OPTIMIZER_BACKEND in {"openai_chat", "claude_chat", "minimax_chat"} def is_target_chat_backend() -> bool: - return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat"} + return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"} def configure_codex_exec( diff --git a/skillopt/model/common.py b/skillopt/model/common.py index ee90e38..c545579 100644 --- a/skillopt/model/common.py +++ b/skillopt/model/common.py @@ -25,6 +25,7 @@ _BACKEND_DEFAULT_MODELS = { "claude_chat": "claude-sonnet-4-6", "claude_code_exec": "claude-sonnet-4-6", "qwen_chat": "Qwen/Qwen3.5-4B", + "minimax_chat": "MiniMax/MiniMax-Text-01", } _BACKEND_ALIASES = { @@ -41,6 +42,8 @@ _BACKEND_ALIASES = { "anthropic": "claude_chat", "qwen": "qwen_chat", "qwen_chat": "qwen_chat", + "minimax": "minimax_chat", + "minimax_chat": "minimax_chat", }