From de34b2748bf73d2b92db6cee91ffeb5a5e321e79 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:59:29 +0800 Subject: [PATCH 1/3] Treat MiniMax M2 thinking as always on --- .../sdk1/src/unstract/sdk1/adapters/base1.py | 51 +++++++++++-------- .../sdk1/adapters/llm1/static/minimax.json | 25 ++++++++- .../tests/test_branded_openai_adapters.py | 31 ++++++++++- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py index 366c404f3a..166bc60c53 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py @@ -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: + 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_minimax_m2_model(model_id): + if not enable_thinking: + raise ValueError(f"{model_id} does not support disabling thinking.") + else: + adapter_metadata["thinking"] = { + "type": "adaptive" if enable_thinking else "disabled" + } + + thinking = adapter_metadata.get("thinking") + if thinking is None: + return + if _is_minimax_m2_model(model_id): + 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,26 +631,7 @@ 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() validated["cost_model"] = f"{_MINIMAX_PROVIDER_PREFIX}{model_id}" diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json index 3075271e97..f0e836e6bc 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json @@ -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-M2(?:$|[.-])" + } + }, + "required": [ + "model" + ] + }, + "then": { + "properties": { + "enable_thinking": { + "const": true + } + } + } + } + ] } diff --git a/unstract/sdk1/tests/test_branded_openai_adapters.py b/unstract/sdk1/tests/test_branded_openai_adapters.py index c1556a3dd7..0eecb22d28 100644 --- a/unstract/sdk1/tests/test_branded_openai_adapters.py +++ b/unstract/sdk1/tests/test_branded_openai_adapters.py @@ -186,10 +186,26 @@ 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 validated["thinking"] is None + + explicitly_enabled = MiniMaxLLMParameters.validate( + {"model": "MiniMax-M2.7", "api_key": "k", "enable_thinking": True} + ) + assert explicitly_enabled["thinking"] is None + + +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 +325,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 +339,15 @@ def test_minimax_schema_covers_models_thinking_and_regions() -> None: "standard", "priority", ] + assert schema["allOf"][0]["then"]["properties"]["enable_thinking"] == {"const": True} + validator = Draft202012Validator(schema) + m2_config = { + "adapter_name": "m2", + "api_key": "k", + "model": "MiniMax-M2.7", + } + assert not list(validator.iter_errors({**m2_config, "enable_thinking": True})) + assert list(validator.iter_errors({**m2_config, "enable_thinking": False})) assert "reasoning_effort" not in json.dumps(schema) From a2355388582e3f8e6e81aaaea33ffac01f1d6bdb Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:23:36 +0800 Subject: [PATCH 2/3] Align MiniMax M2 thinking validation --- .../sdk1/src/unstract/sdk1/adapters/base1.py | 12 +++++---- .../sdk1/adapters/llm1/static/minimax.json | 2 +- .../tests/test_branded_openai_adapters.py | 25 ++++++++++++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py index 166bc60c53..ece7d1a7cb 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py @@ -568,14 +568,14 @@ def _minimax_context_window(model_id: str) -> int | 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_minimax_m2_model(model_id): - if not enable_thinking: - raise ValueError(f"{model_id} does not support disabling thinking.") - else: + 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" } @@ -583,7 +583,7 @@ def _normalize_minimax_thinking( thinking = adapter_metadata.get("thinking") if thinking is None: return - if _is_minimax_m2_model(model_id): + if is_m2_model: raise ValueError( f"{model_id} uses always-on thinking and does not accept " "thinking configuration." @@ -634,6 +634,8 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: _normalize_minimax_thinking(adapter_metadata, model_id) validated = MiniMaxLLMParameters(**adapter_metadata).model_dump() + if _is_minimax_m2_model(model_id): + validated.pop("thinking", None) validated["cost_model"] = f"{_MINIMAX_PROVIDER_PREFIX}{model_id}" if context_window := _minimax_context_window(model_id): validated["context_window"] = context_window diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json index f0e836e6bc..9ae029d569 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json @@ -81,7 +81,7 @@ "if": { "properties": { "model": { - "pattern": "^MiniMax-M2(?:$|[.-])" + "pattern": "^(?:(?:minimax|anthropic)/)?[Mm][Ii][Nn][Ii][Mm][Aa][Xx]-[Mm]2(?:$|[.-])" } }, "required": [ diff --git a/unstract/sdk1/tests/test_branded_openai_adapters.py b/unstract/sdk1/tests/test_branded_openai_adapters.py index 0eecb22d28..1d34040f1d 100644 --- a/unstract/sdk1/tests/test_branded_openai_adapters.py +++ b/unstract/sdk1/tests/test_branded_openai_adapters.py @@ -189,12 +189,13 @@ def test_minimax_m2_rejects_disabling_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"] is None + 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 explicitly_enabled["thinking"] is None + assert "thinking" not in explicitly_enabled def test_minimax_m2_rejects_configurable_thinking_payload() -> None: @@ -341,13 +342,25 @@ def test_minimax_schema_covers_models_thinking_and_regions() -> None: ] assert schema["allOf"][0]["then"]["properties"]["enable_thinking"] == {"const": True} validator = Draft202012Validator(schema) - m2_config = { + config = { "adapter_name": "m2", "api_key": "k", - "model": "MiniMax-M2.7", } - assert not list(validator.iter_errors({**m2_config, "enable_thinking": True})) - assert list(validator.iter_errors({**m2_config, "enable_thinking": False})) + 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) From 4ad8ebd7e2f0206f90065dab4c60ad7f53b3d3f5 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:24:21 +0000 Subject: [PATCH 3/3] Strip MiniMax M2 thinking instead of raising Drop the raise on an explicit thinking payload, the M2-only validated.pop, and the allOf conditional in the adapter schema. --- .../sdk1/src/unstract/sdk1/adapters/base1.py | 9 ++-- .../sdk1/adapters/llm1/static/minimax.json | 23 +-------- .../tests/test_branded_openai_adapters.py | 50 ++++++------------- 3 files changed, 19 insertions(+), 63 deletions(-) diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py index ece7d1a7cb..504cce454e 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/base1.py +++ b/unstract/sdk1/src/unstract/sdk1/adapters/base1.py @@ -584,10 +584,9 @@ def _normalize_minimax_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." - ) + # M2.x always thinks; the provider accepts this parameter but ignores it. + adapter_metadata.pop("thinking") + return if not isinstance(thinking, dict) or thinking.get("type") not in { "adaptive", "disabled", @@ -634,8 +633,6 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]: _normalize_minimax_thinking(adapter_metadata, model_id) validated = MiniMaxLLMParameters(**adapter_metadata).model_dump() - if _is_minimax_m2_model(model_id): - validated.pop("thinking", None) validated["cost_model"] = f"{_MINIMAX_PROVIDER_PREFIX}{model_id}" if context_window := _minimax_context_window(model_id): validated["context_window"] = context_window diff --git a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json index 9ae029d569..e473cdb622 100644 --- a/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json +++ b/unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json @@ -75,26 +75,5 @@ "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 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 - } - } - } - } - ] + } } diff --git a/unstract/sdk1/tests/test_branded_openai_adapters.py b/unstract/sdk1/tests/test_branded_openai_adapters.py index 1d34040f1d..b0d38e585a 100644 --- a/unstract/sdk1/tests/test_branded_openai_adapters.py +++ b/unstract/sdk1/tests/test_branded_openai_adapters.py @@ -189,24 +189,27 @@ def test_minimax_m2_rejects_disabling_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 "thinking" not in validated - assert "thinking" not in MiniMaxLLMParameters.validate(dict(validated)) + assert validated["thinking"] is None + assert MiniMaxLLMParameters.validate(dict(validated))["thinking"] is None explicitly_enabled = MiniMaxLLMParameters.validate( {"model": "MiniMax-M2.7", "api_key": "k", "enable_thinking": True} ) - assert "thinking" not in explicitly_enabled + assert explicitly_enabled["thinking"] is None -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_strips_explicit_thinking_payload() -> None: + # The provider accepts a thinking payload for M2.x but ignores it, so drop it + # instead of failing a call that works today. + validated = MiniMaxLLMParameters.validate( + { + "model": "MiniMax-M2.7", + "api_key": "k", + "thinking": {"type": "disabled"}, + } + ) + + assert validated["thinking"] is None def test_minimax_m2_thinking_rules_require_model_family_boundary() -> None: @@ -326,8 +329,6 @@ 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"] == [ @@ -340,27 +341,6 @@ def test_minimax_schema_covers_models_thinking_and_regions() -> None: "standard", "priority", ] - assert schema["allOf"][0]["then"]["properties"]["enable_thinking"] == {"const": True} - 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)