Skip to content

fix(frontend): adopt MiniMax M2 thinking toggle fix - #15586

Open
mattwittwer wants to merge 4 commits into
mainfrom
mwittwer/minimax-m2-thinking-false__DIS-2734
Open

mattwittwer wants to merge 4 commits into
mainfrom
mwittwer/minimax-m2-thinking-false__DIS-2734

Conversation

@mattwittwer

@mattwittwer mattwittwer commented Oct 2, 2026 •

Copy link
Copy Markdown

Overview:

MiniMax M2.7 requests with thinking disabled could return reasoning text, a stray </think>, and tool arguments as plain content instead of a tool call. The stock template opened a reasoning block while Dynamo disabled its reasoning parser.

Add regression tests for the template correction in frontend-crates #327. The current Dynamo diff contains tests only; adoption of the corrected renderer release remains pending.

Details:

  • Disabled thinking via thinking: false or enable_thinking: false closes the prompt, disables frontend reasoning parsing, and forwards no reasoning_ended override. Named and omitted tool choices exercise guided-JSON selection.
  • Default and enabled thinking preserve the open prompt and existing reasoning_ended: false behavior.
  • Synthetic JSON output, whole or fragmented, is converted into a named tool call with finish_reason: tool_calls. This verifies postprocessing, not backend generation.

Where should the reviewer start?

Start with the minimax_m2_* tests in lib/llm/src/preprocessor.rs. They cover disabled/default/enabled thinking through preprocessing and named-tool postprocessing with synthetic output. The template fix is in the companion frontend-crates PR #327.

Related Issues

🚫 This PR is NOT linked to an issue:

  • Confirmed — no related issue

Depends on ai-dynamo/frontend-crates#327.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of prompts with unfinished reasoning across supported AI models, helping preserve the intended response behavior.
    • Improved recognition of completed reasoning in MiniMax M2 prompts and handling of guided tool-call prompts.

Signed-off-by: Matthew Wittwer <mwittwer@nvidia.com>
@mattwittwer
mattwittwer requested a review from a team as a code owner October 2, 2026 23:12
@github-actions github-actions Bot added fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Oct 2, 2026
@mattwittwer mattwittwer self-assigned this Oct 2, 2026
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: ai-dynamo/dynamo/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: a616f663-b233-40a6-9c56-ada6b440cca7
📥 Commits

Reviewing files that changed from the base of the PR and between 0560832 and 8089810.

📒 Files selected for processing (1)
  • lib/llm/src/preprocessor.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.


Walkthrough

Preprocessing now carries RenderedPrompt through multimodal gathering and resolves reasoning state from renderer metadata or parser-specific suffix detection. Backend hints cover open states for selected parsers and closed states for MiniMax M2.

Changes

Reasoning state in preprocessing

Layer / File(s) Summary
Resolve reasoning state and backend hints
lib/llm/src/preprocessor.rs
Reasoning-state resolution prefers renderer metadata and otherwise detects an open state from parser-specific suffixes. Backend hints now account for supported open and closed states.
Pass rendered prompt through preprocessing
lib/llm/src/preprocessor.rs
Preprocessing passes RenderedPrompt to multimodal gathering and uses its text for serialization and image-budget calculation. Backend hint calculation receives the resolved state.
Validate MiniMax M2 prompt handling
lib/llm/src/preprocessor.rs
Tests cover MiniMax M2 open and closed prompts, parser-specific closed-state handling, and named-tool guided JSON parsing for complete and fragmented output.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 80898

This change aligns MiniMax reasoning state with the rendered prompt and is covered by new tests. No merge-blocking risk was identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the overview, implementation details, and reviewer starting point. It does not meet the required issue-link requirement: it confirms that no issue is linked, and a dependency on… Link the issue this PR implements using the required format, such as Closes #1234 or Closes DYN-1234. If no issue exists, create one and reference it in the description or branch name.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title identifies the MiniMax thinking-toggle change, which relates to the PR’s reasoning-state work. The fix(frontend) prefix is misleading because the changes are in lib/llm and update prepro…
Full details: Description check

Explanation

The description covers the overview, implementation details, and reviewer starting point. It does not meet the required issue-link requirement: it confirms that no issue is linked, and a dependency on another pull request does not replace an issue reference.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

Comment thread lib/llm/src/preprocessor.rs Outdated
Comment thread lib/llm/src/preprocessor.rs Outdated
Comment thread lib/llm/src/preprocessor.rs Outdated
@devin-ai-integration

devin-ai-integration Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

❌ Dynamo PR CI failed — run 37394397068 (attempt 1) on 418fa306e6

Gate checks: ✅ backend-status-check · ✅ deploy-status-check · ❌ dynamo-status-check

Other Jobs
dynamo-runtime ❌ 1 ✅ 7

Failure details

1 job failed: one dynamo_llm unit test, minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override, fails an assertion in cargo test. No jobs were cancelled.

❌ dynamo-runtime / rust-gpu: minimax_m2 disabled-thinking test assertion failed (preprocessor.rs:10980)

Job: dynamo-runtime / rust-gpu · Failed step: Run Rust checks (block-manager + media-ffmpeg + MM routing + integration tests) · Logs: gh run view --job 112063124005 -R ai-dynamo/dynamo --log-failed

thread 'preprocessor::tests::minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override' panicked at lib/llm/src/preprocessor.rs:10980:17:
thinking/Some(Object {"type": String("function"), "function": Object {"name": String("calculate")}}): disabled thinking must close the empty block: "<minimax:tool_call>Use the calculator tool for 937 * 18 + 42.\n]~b]ai\n<think>\n"
test preprocessor::tests::minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override ... FAILED

failures:
    preprocessor::tests::minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override

test result: FAILED. 3117 passed; 1 failed; 8 ignored; 0 measured; 0 filtered out; finished in 37.00s

error: test failed, to rerun pass `--lib`

In the dynamo_llm lib unit tests (3117 passed, 1 failed), preprocessor::tests::minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override panics at lib/llm/src/preprocessor.rs:10980. The test case uses the named tool choice calculate. With thinking disabled, the rendered MiniMax-M2 prompt ends with an open <think>\n but the assertion expects the empty think block to be closed. The prompt also starts with <minimax:tool_call> in front of the user text. The other panics in the log come from passing tests that inject panics on purpose; they are not related.

For agents
{"pr": 15586, "run_id": 37394397068, "run_attempt": 1, "head_sha": "418fa306e6f1b5863291fbabb191439e5be5666c", "failures": [{"job": "dynamo-runtime / rust-gpu", "job_id": 112063124005, "failed_step": "Run Rust checks (block-manager + media-ffmpeg + MM routing + integration tests)", "signature": "dynamo_llm unit test panic: disabled thinking must close the empty block (preprocessor.rs:10980)", "tests": ["preprocessor::tests::minimax_m2_disabled_thinking_closes_prompt_and_forwards_no_override"], "log_cmd": "gh run view --job 112063124005 -R ai-dynamo/dynamo --log-failed"}]}

Posted automatically by Devin for run 37394397068. Updated on every full-CI run of this PR.

Signed-off-by: Matthew Wittwer <mwittwer@nvidia.com>
@mattwittwer
mattwittwer requested a review from indrajit96 October 5, 2026 17:18

@dynamo-review-agent dynamo-review-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Previously reported defects still present:

  • Original discussion: The compile failure remains at the reviewed head: Cargo.toml and Cargo.lock still pin dynamo-renderer 5.4.0, which does not export PromptReasoningState or the new RenderedPrompt reasoning-state methods used by preprocessor.rs. Update and lock dynamo-renderer to a published version containing the required API before merging.
  • Original discussion: The reviewed head still pins dynamo-renderer 5.4.0 in both Cargo.toml and Cargo.lock, but that release does not export PromptReasoningState; the new import therefore fails with E0432 and prevents dynamo-llm and dependent images from compiling. Update and lock the renderer dependency to a published version containing the required API.
  • Original discussion: The reviewed head still pins dynamo-renderer 5.4.0, which does not export PromptReasoningState, so the new import prevents dynamo-llm and dependent images from compiling. Rebase onto the supplied base, which pins renderer 6.0.0, or update the renderer dependency and lockfiles explicitly.

Comment thread lib/llm/src/preprocessor.rs Outdated
Comment thread lib/llm/src/preprocessor.rs Outdated
Comment thread lib/llm/src/preprocessor.rs Outdated
Signed-off-by: Matthew Wittwer <mwittwer@nvidia.com>
Signed-off-by: Matthew Wittwer <mwittwer@nvidia.com>
@mattwittwer mattwittwer changed the title fix(llm): align MiniMax reasoning state with the rendered prompt fix(llm): pick up the MiniMax disabled-thinking renderer fix Oct 6, 2026
@mattwittwer mattwittwer changed the title fix(llm): pick up the MiniMax disabled-thinking renderer fix fix(frontend): adopt MiniMax M2 thinking toggle fix Oct 6, 2026
.preprocess_request(&request, None)
.await
.unwrap();
assert!(!injected, "{key}/{choice:?}");

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.

BEFORE:
                assert!(!injected, "{key}/{choice:?}");
...
                    .apply_tool_choice_guided_decoding(&request, &mut prepared, injected)
...
            assert!(injected, "{args}");

AFTER:
                assert!(!injected.unified, "{key}/{choice:?}");
...
                    .apply_tool_choice_guided_decoding(&request, &mut prepared, injected.unified)
...
            assert!(injected.unified, "{args}");

Since #14950, preprocess_request returns PromptReasoningPrefill, so after a rebase onto main this line and L11043 fail with E0600 and L11006 fails with E0308. Use injected.unified, the value generate passes to apply_tool_choice_guided_decoding.

Comment on lines +10969 to +10972
for key in ["thinking", "enable_thinking"] {
for choice in &choices {
let mut request =
minimax_m2_request(serde_json::json!({key: false}), choice.clone());

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.

BEFORE:
        for key in ["thinking", "enable_thinking"] {
            for choice in &choices {
                let mut request =
                    minimax_m2_request(serde_json::json!({key: false}), choice.clone());

AFTER:
        for args in [
            serde_json::json!({"thinking": false}),
            serde_json::json!({"enable_thinking": false}),
            serde_json::json!({"thinking_mode": "chat"}),
        ] {
            for choice in &choices {
                let mut request = minimax_m2_request(args.clone(), choice.clone());

normalize_thinking_arg writes both bools, so both keys reach the same template branch. {"thinking_mode": "chat"} stays a string, the one case where the renderer's 'chat' branch and is_reasoning_disabled_by_request must agree on their own, so it is worth a third row ({key} becomes {args} in the messages below).

}

#[tokio::test]
async fn minimax_m2_disabled_named_json_returns_tool_call() {

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.

    async fn minimax_m2_disabled_named_json_returns_tool_call() {
        // Bare JSON becomes a named tool call with thinking disabled,
        // both whole and fragmented across deltas.

This test never renders a prompt, so it passes without frontend-crates#327 (CI on renderer 6.0.0 passed it) and covers postprocessing rather than the template fix. Would it sit better next to tool_choice_minimax_m2_required_thinking_disabled_keeps_tool_xml in tests/postprocessor_parsing_stream.rs?

Comment on lines +10999 to +11002
assert!(OpenAIPreprocessor::is_reasoning_disabled_by_request(
Some("minimax_m2"),
request.chat_template_args.as_ref(),
));

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.

                assert!(OpenAIPreprocessor::is_reasoning_disabled_by_request(
                    Some("minimax_m2"),
                    request.chat_template_args.as_ref(),
                ));

Follow-up idea: this gate reads only request args, so a template that ignores the toggle (a custom MiniMax template, stock DeepSeek-R1) still leaks reasoning into content with thinking disabled. Keeping the parser on while the rendered prompt leaves <think> open would cover them; not for this PR, happy to leave it here.

This branch has not been deployed

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

Labels

fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants