-
Notifications
You must be signed in to change notification settings - Fork 702
Treat MiniMax M2 thinking as always on #2238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -565,6 +565,36 @@ def _minimax_context_window(model_id: str) -> int | None: | |
| return None | ||
|
|
||
|
|
||
| def _normalize_minimax_thinking( | ||
| adapter_metadata: dict[str, "Any"], model_id: str | ||
| ) -> None: | ||
| is_m2_model = _is_minimax_m2_model(model_id) | ||
| 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.") | ||
| if is_m2_model and not enable_thinking: | ||
| raise ValueError(f"{model_id} does not support disabling thinking.") | ||
| if not is_m2_model: | ||
| adapter_metadata["thinking"] = { | ||
| "type": "adaptive" if enable_thinking else "disabled" | ||
| } | ||
|
|
||
| thinking = adapter_metadata.get("thinking") | ||
| if thinking is None: | ||
| return | ||
| if is_m2_model: | ||
| raise ValueError( | ||
| f"{model_id} uses always-on thinking and does not accept " | ||
| "thinking configuration." | ||
| ) | ||
| if not isinstance(thinking, dict) or thinking.get("type") not in { | ||
| "adaptive", | ||
| "disabled", | ||
| }: | ||
| raise ValueError("thinking.type must be adaptive or disabled.") | ||
|
|
||
|
|
||
| class NvidiaBuildLLMParameters(OpenAICompatibleLLMParameters): | ||
| """OpenAI-compatible adapter for NVIDIA's hosted models (build.nvidia.com).""" | ||
|
|
||
|
|
@@ -601,28 +631,11 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: | |
| 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 _is_minimax_m2_model(model_id): | ||
| 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 _is_minimax_m2_model(model_id) and thinking["type"] == "disabled": | ||
| raise ValueError(f"{model_id} does not support disabling thinking.") | ||
| _normalize_minimax_thinking(adapter_metadata, model_id) | ||
|
|
||
| validated = MiniMaxLLMParameters(**adapter_metadata).model_dump() | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| if _is_minimax_m2_model(model_id): | ||
| validated.pop("thinking", None) | ||
|
Comment on lines
+637
to
+638
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only ever deletes a By the time it runs, That's the same Suggest deleting both lines. Re-validation stays safe — the helper short-circuits on The assertion at line 192 then becomes |
||
| validated["cost_model"] = f"{_MINIMAX_PROVIDER_PREFIX}{model_id}" | ||
| if context_window := _minimax_context_window(model_id): | ||
| validated["context_window"] = context_window | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,7 +73,28 @@ | |
| "enable_thinking": { | ||
| "type": "boolean", | ||
| "title": "Enable Thinking", | ||
| "description": "Override the protocol default for MiniMax-M3: OpenAI-compatible requests default to adaptive thinking, while Anthropic-compatible requests default to disabled thinking. MiniMax-M2.x models always keep thinking enabled. See [MiniMax API docs](https://platform.minimax.io/docs/api-reference/text-openai-api)." | ||
| "description": "Override the protocol default for MiniMax-M3: OpenAI-compatible requests default to adaptive thinking, while Anthropic-compatible requests default to disabled thinking. MiniMax-M2.x models use always-on thinking and only accept this setting as true. See [MiniMax API docs](https://platform.minimax.io/docs/api-reference/text-openai-api)." | ||
| } | ||
| } | ||
| }, | ||
| "allOf": [ | ||
| { | ||
| "if": { | ||
| "properties": { | ||
| "model": { | ||
| "pattern": "^(?:(?:minimax|anthropic)/)?[Mm][Ii][Nn][Ii][Mm][Aa][Xx]-[Mm]2(?:$|[.-])" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "model" | ||
| ] | ||
| }, | ||
| "then": { | ||
| "properties": { | ||
| "enable_thinking": { | ||
| "const": true | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
|
Comment on lines
+79
to
+99
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest dropping this block. It encodes "what is an M2 model" a third time — One thing worth knowing before merging either way: I ran this schema through
The reworded description just above already conveys always-on, and the backend still rejects |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -186,10 +186,27 @@ def test_minimax_m2_rejects_disabling_thinking() -> None: | |
| ) | ||
|
|
||
|
|
||
| def test_minimax_m2_defaults_to_adaptive_thinking() -> None: | ||
| def test_minimax_m2_uses_always_on_thinking_without_request_parameter() -> None: | ||
| validated = MiniMaxLLMParameters.validate({"model": "MiniMax-M2.7", "api_key": "k"}) | ||
|
|
||
| assert validated["thinking"] == {"type": "adaptive"} | ||
| assert "thinking" not in validated | ||
| assert "thinking" not in MiniMaxLLMParameters.validate(dict(validated)) | ||
|
|
||
| explicitly_enabled = MiniMaxLLMParameters.validate( | ||
| {"model": "MiniMax-M2.7", "api_key": "k", "enable_thinking": True} | ||
| ) | ||
| assert "thinking" not in explicitly_enabled | ||
|
|
||
|
|
||
| def test_minimax_m2_rejects_configurable_thinking_payload() -> None: | ||
| with pytest.raises(ValueError, match="uses always-on thinking"): | ||
| MiniMaxLLMParameters.validate( | ||
| { | ||
| "model": "MiniMax-M2.7", | ||
| "api_key": "k", | ||
| "thinking": {"type": "adaptive"}, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| def test_minimax_m2_thinking_rules_require_model_family_boundary() -> None: | ||
|
|
@@ -309,6 +326,8 @@ def test_branded_llm_schema_exposes_api_base_with_default( | |
|
|
||
|
|
||
| def test_minimax_schema_covers_models_thinking_and_regions() -> None: | ||
| from jsonschema import Draft202012Validator | ||
|
|
||
| schema = json.loads(MiniMaxLLMAdapter.get_json_schema()) | ||
|
|
||
| assert schema["properties"]["model"]["examples"] == [ | ||
|
|
@@ -321,6 +340,27 @@ def test_minimax_schema_covers_models_thinking_and_regions() -> None: | |
| "standard", | ||
| "priority", | ||
| ] | ||
| assert schema["allOf"][0]["then"]["properties"]["enable_thinking"] == {"const": True} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Also Both go away if the |
||
| validator = Draft202012Validator(schema) | ||
| config = { | ||
| "adapter_name": "m2", | ||
| "api_key": "k", | ||
| } | ||
| for model in ( | ||
| "MiniMax-M2.7", | ||
| "minimax-m2.7", | ||
| "minimax/MiniMax-M2.7", | ||
| "anthropic/minimax-m2.7", | ||
| ): | ||
| m2_config = {**config, "model": model} | ||
| assert not list(validator.iter_errors({**m2_config, "enable_thinking": True})) | ||
| assert list(validator.iter_errors({**m2_config, "enable_thinking": False})) | ||
|
|
||
| assert not list( | ||
| validator.iter_errors( | ||
| {**config, "model": "MiniMax-M20", "enable_thinking": False} | ||
| ) | ||
| ) | ||
| assert "reasoning_effort" not in json.dumps(schema) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest stripping rather than raising here.
The MiniMax docs say the opposite of this message: "For M2.x models, thinking cannot be disabled;
thinking: {"type": "disabled"}is accepted but thinking remains on." The API takes the parameter, it just ignores it.Practical cost: this turns a currently-working call into a hard failure at
LLM.__init__(llm.py:250), which runs on every completion and on Test Connection — not only at save time. And the form schema has nothinkingproperty, so it can only ever fire for SDK/API callers, who are the least served by a message that contradicts the provider.The
enable_thinking: falseraise above is the one worth keeping — that's a user explicitly asking for something impossible.