fix(llm): MiniMax integration polish vs official docs

M2.x tool_choice is enum-only (none/auto), so route through the
no-tool_choice dispatch. MinimaxChatOpenAI injects reasoning_split
so <think> blocks stay out of content. Catalog rounded out to the
full official M2.x lineup plus forward-compat regex.
This commit is contained in:
Yijia-Xiao
2026-05-11 02:40:33 +00:00
parent 9482cae188
commit e1316686f8
6 changed files with 172 additions and 14 deletions

View File

@@ -61,6 +61,21 @@ _DEEPSEEK_CHAT = ModelCapabilities(
preferred_structured_method="function_calling",
)
# MiniMax M2.x reasoning models accept the tools array, but their
# tool_choice parameter is restricted to the enum {"none", "auto"}
# (platform.minimax.io/docs/api-reference/text-post). Langchain's
# function_calling path sends tool_choice as a function-spec dict, which
# MiniMax 400s — same shape as the DeepSeek bug. supports_tool_choice=False
# makes the dispatch in NormalizedChatOpenAI suppress the kwarg; the schema
# still ships as a tool. json_mode response_format is only for
# MiniMax-Text-01, not M2.x.
_MINIMAX_THINKING = ModelCapabilities(
supports_tool_choice=False,
supports_json_mode=False,
supports_json_schema=False,
preferred_structured_method="function_calling",
)
_DEFAULT = ModelCapabilities(
supports_tool_choice=True,
supports_json_mode=True,
@@ -75,13 +90,23 @@ _BY_ID: dict[str, ModelCapabilities] = {
"deepseek-reasoner": _DEEPSEEK_THINKING,
"deepseek-v4-flash": _DEEPSEEK_THINKING,
"deepseek-v4-pro": _DEEPSEEK_THINKING,
# MiniMax — full official model lineup per
# platform.minimax.io/docs/api-reference/text-openai-api
"MiniMax-M2.7": _MINIMAX_THINKING,
"MiniMax-M2.7-highspeed": _MINIMAX_THINKING,
"MiniMax-M2.5": _MINIMAX_THINKING,
"MiniMax-M2.5-highspeed": _MINIMAX_THINKING,
"MiniMax-M2.1": _MINIMAX_THINKING,
"MiniMax-M2.1-highspeed": _MINIMAX_THINKING,
"MiniMax-M2": _MINIMAX_THINKING,
}
# Forward-compat patterns. A new ``deepseek-v5-*`` or ``deepseek-reasoner-*``
# variant inherits the thinking-mode quirks automatically.
# Forward-compat patterns. New ``deepseek-v5-*`` / ``deepseek-reasoner-*``
# or ``MiniMax-M3*`` variants inherit the thinking-mode quirks automatically.
_BY_PATTERN: list[tuple[re.Pattern[str], ModelCapabilities]] = [
(re.compile(r"^deepseek-v\d"), _DEEPSEEK_THINKING),
(re.compile(r"^deepseek-reasoner"), _DEEPSEEK_THINKING),
(re.compile(r"^MiniMax-M\d"), _MINIMAX_THINKING),
]

View File

@@ -8,17 +8,22 @@ ModelOption = Tuple[str, str]
ProviderModeOptions = Dict[str, Dict[str, List[ModelOption]]]
# Shared model list for MiniMax's global and CN endpoints (same model IDs).
# Shared model list for MiniMax's global and CN endpoints (same IDs).
# Full official lineup per platform.minimax.io/docs/api-reference/text-openai-api.
# All M2.x models share a 204,800-token context window.
_MINIMAX_MODELS: Dict[str, List[ModelOption]] = {
"quick": [
("MiniMax M2.7 Highspeed Fast, 204K ctx", "MiniMax-M2.7-highspeed"),
("MiniMax M2.5 Highspeed Previous-gen fast", "MiniMax-M2.5-highspeed"),
("MiniMax-M2.7-highspeed - Faster M2.7, 204K ctx, ~100 TPS", "MiniMax-M2.7-highspeed"),
("MiniMax-M2.5-highspeed - Previous-gen highspeed, 204K ctx", "MiniMax-M2.5-highspeed"),
("MiniMax-M2.1-highspeed - M2.1 highspeed, 204K ctx", "MiniMax-M2.1-highspeed"),
("Custom model ID", "custom"),
],
"deep": [
("MiniMax M2.7 Flagship, 204K ctx", "MiniMax-M2.7"),
("MiniMax M2.5 — Previous-gen flagship", "MiniMax-M2.5"),
("MiniMax M2.7 Highspeed — Faster M2.7, 204K ctx", "MiniMax-M2.7-highspeed"),
("MiniMax-M2.7 - Flagship, SOTA on coding/agent benchmarks, 204K ctx", "MiniMax-M2.7"),
("MiniMax-M2.7-highspeed - Same quality as M2.7, ~100 TPS", "MiniMax-M2.7-highspeed"),
("MiniMax-M2.5 - Previous-gen flagship, 204K ctx", "MiniMax-M2.5"),
("MiniMax-M2.1 - Earlier M2 line, 204K ctx", "MiniMax-M2.1"),
("MiniMax-M2 - Base M2, 204K ctx", "MiniMax-M2"),
("Custom model ID", "custom"),
],
}

View File

@@ -107,6 +107,28 @@ class DeepSeekChatOpenAI(NormalizedChatOpenAI):
generation.message.additional_kwargs["reasoning_content"] = reasoning
return chat_result
class MinimaxChatOpenAI(NormalizedChatOpenAI):
"""MiniMax-specific overrides on top of the OpenAI-compatible client.
M2.x reasoning models embed ``<think>...</think>`` blocks directly in
``message.content`` by default, which would pollute saved reports.
Per platform.minimax.io/docs/api-reference/text-openai-api, setting
``reasoning_split=True`` in the request body redirects the thinking
block into ``reasoning_details`` so ``content`` stays clean.
Tool-choice handling for M2.x — those models accept only the string
enum ``{"none", "auto"}`` and reject langchain's function-spec dict —
is handled by the capability dispatch in
``NormalizedChatOpenAI.with_structured_output``, not here.
"""
def _get_request_payload(self, input_, *, stop=None, **kwargs):
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
payload.setdefault("reasoning_split", True)
return payload
# Kwargs forwarded from user config to ChatOpenAI
_PASSTHROUGH_KWARGS = (
"timeout", "max_retries", "reasoning_effort",
@@ -183,9 +205,14 @@ class OpenAIClient(BaseLLMClient):
if self.provider == "openai":
llm_kwargs["use_responses_api"] = True
# DeepSeek's thinking-mode quirks live in their own subclass so the
# base NormalizedChatOpenAI stays free of provider-specific branches.
chat_cls = DeepSeekChatOpenAI if self.provider == "deepseek" else NormalizedChatOpenAI
# Provider-specific quirks live in their own subclasses so the
# base NormalizedChatOpenAI stays free of provider branches.
if self.provider == "deepseek":
chat_cls = DeepSeekChatOpenAI
elif self.provider in ("minimax", "minimax-cn"):
chat_cls = MinimaxChatOpenAI
else:
chat_cls = NormalizedChatOpenAI
return chat_cls(**llm_kwargs)
def validate_model(self) -> bool: