Skip to content

Treat MiniMax M2 thinking as always on - #2238

Open
octo-patch wants to merge 2 commits into
Zipstack:mainfrom
octo-patch:octo/20260812-parameter-refresh-recvrNgVrvUya9
Open

Treat MiniMax M2 thinking as always on#2238
octo-patch wants to merge 2 commits into
Zipstack:mainfrom
octo-patch:octo/20260812-parameter-refresh-recvrNgVrvUya9

Conversation

@octo-patch

Copy link
Copy Markdown
Contributor

Reason: Align MiniMax-M2.7 requests with its always-on thinking contract.

  • Stop injecting an adaptive thinking payload for always-on M2-family models.
  • Accept an explicit enabled setting without forwarding it, while rejecting disabled or configurable thinking payloads.
  • Constrain the adapter schema and cover the always-on behavior with focused tests.

Checks:

  • uv run --project unstract/sdk1 --locked ruff check --ignore I001 unstract/sdk1/src/unstract/sdk1/adapters/base1.py unstract/sdk1/tests/test_branded_openai_adapters.py
  • uv run --project unstract/sdk1 --locked ruff format --check unstract/sdk1/src/unstract/sdk1/adapters/base1.py unstract/sdk1/tests/test_branded_openai_adapters.py
  • uv run --project unstract/sdk1 --locked pytest -q unstract/sdk1/tests/test_branded_openai_adapters.py -k 'minimax_m2 or minimax_schema_covers'
  • git diff --check

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR aligns MiniMax M2-family requests with their always-on thinking behavior.

  • Removes the generated thinking request parameter for M2 models, including after revalidation.
  • Accepts enable_thinking: true without forwarding it and rejects disabled or configurable thinking.
  • Aligns schema validation with supported casing and provider-prefixed model identifiers.
  • Adds focused runtime and JSON Schema coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
unstract/sdk1/src/unstract/sdk1/adapters/base1.py Centralizes MiniMax thinking normalization and removes the materialized null thinking field from M2 validation output.
unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json Constrains M2-family configurations to explicit enabled thinking across the supported model identifier forms.
unstract/sdk1/tests/test_branded_openai_adapters.py Covers omitted, explicitly enabled, configurable, boundary, casing, and provider-prefixed M2 behavior.

Reviews (2): Last reviewed commit: "Align MiniMax M2 thinking validation" | Re-trigger Greptile

Comment thread unstract/sdk1/src/unstract/sdk1/adapters/base1.py
Comment thread unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/minimax.json Outdated
@sonarqubecloud

Copy link
Copy Markdown

@octo-patch

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Commit a235538 now omits the thinking parameter for MiniMax M2 requests and aligns schema matching with runtime handling. I ran 64 adapter tests plus Ruff format and lint checks.

@chandrasekharan-zipstack chandrasekharan-zipstack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review angle, so the inline comments make sense together.

The underlying fact checks out: per MiniMax's docs, M2.x thinking is always on. But the same docs also say thinking: {"type": "disabled"} is accepted and ignored, not rejected. So the previously-injected {"type": "adaptive"} was a no-op on the wire, and this is a hygiene change rather than a bug fix. Worth being explicit about that since there's no ticket behind the PR.

Happy to take the Python refactor. Extracting _normalize_minimax_thinking() is a genuine readability win over the inline block it replaces, and no longer sending a parameter the model ignores is correct.

Asking to drop three things that add failure surface without buying behaviour: the new raise on an explicit thinking payload, the validated.pop, and the allOf conditional in the schema. Details inline. That should land around +25/-20 instead of +98/-24.

Verified locally: the suite passes on this branch (64 tests), and MiniMaxLLMParameters(model='minimax/MiniMax-M2.7', api_key='k').model_dump() returns thinking: None — which is what the pop is deleting.

Comment on lines +586 to +590
if is_m2_model:
raise ValueError(
f"{model_id} uses always-on thinking and does not accept "
"thinking configuration."
)

Copy link
Copy Markdown
Contributor

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 no thinking property, 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: false raise above is the one worth keeping — that's a user explicitly asking for something impossible.

Suggested change
if is_m2_model:
raise ValueError(
f"{model_id} uses always-on thinking and does not accept "
"thinking configuration."
)
if is_m2_model:
# M2.x always thinks; the provider accepts this parameter but ignores it.
adapter_metadata.pop("thinking")
return

Comment on lines +637 to +638
if _is_minimax_m2_model(model_id):
validated.pop("thinking", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only ever deletes a None.

By the time it runs, thinking is guaranteed absent from adapter_metadata for M2 — the helper raises if it was passed, and the enable_thinking branch skips M2 — so Pydantic emits the declared field as None:

MiniMaxLLMParameters(model='minimax/MiniMax-M2.7', api_key='k').model_dump()
  → thinking key present: True | value: None

That's the same None the M3 path keeps and has shipped with since #2166. Popping it only for M2 leaves two shapes for "no thinking" (M2: key absent, M3: key None), which is an easy thing to trip over later.

Suggest deleting both lines. Re-validation stays safe — the helper short-circuits on thinking is None, confirmed with validate({'model': 'MiniMax-M2.7', 'api_key': 'k', 'thinking': None}).

The assertion at line 192 then becomes assert validated["thinking"] is None.

Comment on lines +79 to +99
"allOf": [
{
"if": {
"properties": {
"model": {
"pattern": "^(?:(?:minimax|anthropic)/)?[Mm][Ii][Nn][Ii][Mm][Aa][Xx]-[Mm]2(?:$|[.-])"
}
},
"required": [
"model"
]
},
"then": {
"properties": {
"enable_thinking": {
"const": true
}
}
}
}
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 — _is_minimax_m2_model (base1.py:557) and the prefix-stripping in validate_model are the other two — in a different language, with hand-expanded case-insensitivity because JSON Schema has no /i flag. Nothing fails if the two drift, so the next model family silently loses the hint while tests stay green.

One thing worth knowing before merging either way: I ran this schema through @rjsf/utils@5 + validator-ajv8@5, which is what RjsfFormLayout.jsx uses. The condition does resolve correctly — M2 gets const: true and the checkbox defaults to checked — but unchecking yields two errors, and the second is raw ajv output:

[".enable_thinking must be equal to constant", "must match \"then\" schema"]

transformErrors (RjsfFormLayout.jsx:177) has no if case, so must match "then" schema reaches the user. No other adapter schema in the repo uses allOf/if, so this would become the pattern others copy.

The reworded description just above already conveys always-on, and the backend still rejects enable_thinking: false. If we want form-time feedback later, ui:widget: hidden for M2 reads better than a checkbox that accepts only one value — but it needs the same duplication solved, so it belongs in its own change.

"standard",
"priority",
]
assert schema["allOf"][0]["then"]["properties"]["enable_thinking"] == {"const": True}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allOf[0] couples this to schema layout — add a second conditional and it silently asserts the wrong branch. The Draft202012Validator loop just below already covers the same rule through behaviour, which is the version worth keeping.

Also from jsonschema import Draft202012Validator at line 329 should sit at the top of the file per our import convention.

Both go away if the allOf block is dropped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants