Skip to content

fix(llm): honor prompt-open MiniMax M3 reasoning with guided decoding - #18017

Merged
furionw merged 3 commits into
mainfrom
qiwa/dis-3338-remove-m3-explicit-guidance
Nov 4, 2026
Merged

furionw merged 3 commits into
mainfrom
qiwa/dis-3338-remove-m3-explicit-guidance

Conversation

@furionw

@furionw furionw commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Overview

MiniMax M3 explicit guided_* overrides a prompt-open reasoning state, buffering thoughts and returning truncated or bracket-led reasoning as content. Trust the rendered prompt and defer SGLang's answer grammar through its existing request signal. Workers must configure both Dynamo and engine reasoning parsers.

Key Changes

32 added core-logic lines, excluding tests, comments, and whitespace; 56 removed.

  • preprocessor.rs: retain prompt-open reasoning and send SGLang's existing gate.
  • unified_parser.rs: remove explicit_guidance and its output probes.
  • Replace workaround tests with channel/streaming coverage; document parser prerequisites and M3 Auto routing.

Validation

test_minimax_m3_explicit_guidance_keeps_prompt_seeded_reasoning and test_minimax_m3_explicit_guidance_defers_grammar_only_for_prompt_opened_reasoning fail on base 182039dbb5 and pass here. All 18 focused M3 tests pass, including isolated Auto/V1/V2 runs and HTTP aggregation options.

Explicit guided_*, prompt ends in <mm:think> Before After
First thought chunk Buffered until closer Reasoning emitted immediately
Truncated or bracket-led thought May become content Remains reasoning
SGLang prompt-open explicit grammar require_reasoning=false require_reasoning=true

The earlier full-library run at 1590553f84 had 3,773 passes, 13 ignored tests, and five failures. Three networking failures passed isolated reruns. The remaining gRPC metrics-bind timeout and mocker handoff KV-visibility assertion also fail on unchanged base production code.

CPU frontend verification only; engine fixes and model qualification remain separate.

Issues and Ongoing Work

Closes DIS-3338. Routing documentation also refs DYN-5376.

Follows #17236, which already streams answers to prompts without an open thought on the default route; this PR does not change that, and DYN_PARSER_VERSION=1 still delivers them in the final chunk. #18015 and #14934 cover keep-alives and generic EOF handling.

Pull Request Checklist
  • I used Dynamo's /pr-checklist skill.

Summary by CodeRabbit

  • Bug Fixes
    • MiniMax M3 requests can now preserve prompt-prefilled reasoning when explicit guided decoding is used, when reasoning is enabled and the prompt opens a reasoning section.
    • Truncated reasoning remains available in reasoning content, and parser behavior is more consistent across supported parsing modes.
  • Documentation
    • Updated MiniMax M3 guidance to clarify parser selection, reasoning controls, prompt-prefilled reasoning, and frontend signaling requirements.

@github-actions github-actions Bot added fix documentation Improvements or additions to documentation frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

@devin-ai-integration

Copy link
Copy Markdown
Contributor

❌ Dynamo PR CI failed — run 41786205172 (attempt 1) on 81db071276

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

Other Jobs
dynamo-runtime ❌ 2 ✅ 5
planner ❌ 2 ✅ 3

Failure details

4 jobs failed on both amd64 and arm64. 2 planner tests fail because the frontend/worker parser rejects --router-mode power-of-two (invalid choice). The dynamo-runtime sequential jobs also fail test_no_registered_env_var_is_read_from_os_environ, where reject_media_status_code reads 0 from the environment instead of the default 415. No jobs were cancelled.

❌ planner / CPU Test cpu, arm64: --router-mode invalid choice 'power-of-two'

Job: planner / CPU Test cpu, arm64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126433382032 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (choose from 'round-robin', 'random', 'kv', 'direct', 'least-loaded', 'device-aware-weighted')
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
============ 2 failed, 4170 passed, 30 skipped in 589.99s (0:09:49) ============

Two tests in test_live_retune_frontend_config.py pass --router-mode power-of-two, and argparse rejects it because power-of-two is not in the --router-mode choices (round-robin, random, kv, direct, least-loaded, device-aware-weighted). The SystemExit: 2 in the worker-parser test is the same argparse error.

❌ planner / CPU Test cpu, amd64: --router-mode invalid choice 'power-of-two'

Job: planner / CPU Test cpu, amd64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126433382212 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
============ 2 failed, 4170 passed, 30 skipped in 448.91s (0:07:28) ============

Same 2 tests fail with the same error as on arm64.

❌ dynamo-runtime / test / sequential cuda13.0, amd64: reject_media_status_code 0 != 415 + router-mode 'power-of-two'

Job: dynamo-runtime / test / sequential cuda13.0, amd64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126434707536 -R ai-dynamo/dynamo --log-failed

components/src/dynamo/frontend/tests/test_frontend_parser_builder.py:62: in test_no_registered_env_var_is_read_from_os_environ
    assert vars(parser.parse_args([])) == defaults, sentinel
E   AssertionError: 0
E     Differing items:
E     {'reject_media_status_code': 0} != {'reject_media_status_code': 415}
...
FAILED components/src/dynamo/frontend/tests/test_frontend_parser_builder.py::test_no_registered_env_var_is_read_from_os_environ - AssertionError: 0
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
== 3 failed, 7783 passed, 170 skipped, 2854 deselected in 1534.51s (0:25:34) ===

This job has the same 2 planner power-of-two failures. It also fails test_no_registered_env_var_is_read_from_os_environ: with the sentinel 0 set for each registered env var, the parser still reads one of them for reject_media_status_code and returns 0 instead of the default 415.

❌ dynamo-runtime / test / sequential cuda13.0, arm64: reject_media_status_code 0 != 415 + router-mode 'power-of-two'

Job: dynamo-runtime / test / sequential cuda13.0, arm64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126434707578 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/frontend/tests/test_frontend_parser_builder.py::test_no_registered_env_var_is_read_from_os_environ - AssertionError: 0
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
== 3 failed, 7783 passed, 170 skipped, 2854 deselected in 1954.32s (0:32:34) ===

Same 3 failures as the amd64 sequential job.

For agents
{"pr": 18017, "run_id": 41786205172, "run_attempt": 1, "head_sha": "81db071276622e1c29447886f35773ffeafb6a50",
 "failures": [
  {"job": "planner / CPU Test cpu, arm64", "job_id": 126433382032, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126433382032 -R ai-dynamo/dynamo --log-failed"},
  {"job": "planner / CPU Test cpu, amd64", "job_id": 126433382212, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126433382212 -R ai-dynamo/dynamo --log-failed"},
  {"job": "dynamo-runtime / test / sequential cuda13.0, amd64", "job_id": 126434707536, "failed_step": "Run CPU-only tests (parallelized)", "signature": "reject_media_status_code 0 != 415; --router-mode invalid choice 'power-of-two'", "tests": ["components/src/dynamo/frontend/tests/test_frontend_parser_builder.py::test_no_registered_env_var_is_read_from_os_environ", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126434707536 -R ai-dynamo/dynamo --log-failed"},
  {"job": "dynamo-runtime / test / sequential cuda13.0, arm64", "job_id": 126434707578, "failed_step": "Run CPU-only tests (parallelized)", "signature": "reject_media_status_code 0 != 415; --router-mode invalid choice 'power-of-two'", "tests": ["components/src/dynamo/frontend/tests/test_frontend_parser_builder.py::test_no_registered_env_var_is_read_from_os_environ", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126434707578 -R ai-dynamo/dynamo --log-failed"}
 ]}

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

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

MiniMax M3 explicit guided requests now preserve reasoning when the rendered prompt opens a reasoning section across Unified and legacy parsing. Unified parser prefill decisions no longer use the separate explicit_guidance policy field. Tests and documentation cover the updated behavior.

Changes

MiniMax M3 guided reasoning

Layer / File(s) Summary
Unified parser policy and prefill handling
lib/llm/src/protocols/openai/chat_completions/unified_parser.rs, lib/llm/src/protocols/openai/chat_completions/aggregator.rs
UnifiedRequestPolicy no longer contains explicit_guidance. Stream prefill resolution uses explicit guided-decoding state, and JSON-prefill inspection and response classification use structured-response policy or guided constraints. The M3 split test now uses the default policy and checks prompt-seed channel classification.
M3 prompt-open reasoning and guidance
lib/llm/src/preprocessor.rs, lib/llm/src/preprocessor/tool_choice.rs, lib/llm/PREPROCESSOR_CASES.md, docs/fern/pages/use-cases/tool-calling-and-reasoning/*
Explicit guided M3 requests preserve reasoning when the rendered prompt opens <mm:think>. The preprocessor sets require_reasoning for this case and passes prompt-prefill state to the reasoning parser. Tests cover parser routes, templates, streamed output, truncation, and backend errors. Documentation updates parser selection and M3 configuration guidance.

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

Merge Risk: Minimal · up to 81db0

MiniMax M3 requests with guided decoding now keep prompt-open reasoning in reasoning_content, and the SGLang grammar is deferred through the existing signal. Workers must configure both the Dynamo and engine reasoning parsers, which the docs now state. No concrete merge-blocking defect was found in the supplied changes.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: preserving prompt-open MiniMax M3 reasoning when guided decoding is used.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
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.
Description check Passed The description states the PR fixes MiniMax M3 explicit guided_* overrides of prompt-open reasoning, names the changed files (preprocessor.rs, unified_parser.rs), lists validation runs with specific t…
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 1 files. (5 skipped: 3 unsupported, 2 too large.)

Full details: Description check

Explanation

The description states the PR fixes MiniMax M3 explicit guided_* overrides of prompt-open reasoning, names the changed files (preprocessor.rs, unified_parser.rs), lists validation runs with specific test names, base commit, and results, and links to closed issue DIS-3338. It covers the Overview, Key Changes, Validation, and Issues and Ongoing Work sections, and includes the checklist with the /pr-checklist item marked. Required structure is present. The description is mostly complete and specific to the change.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 182039d and 81db071.

Files selected for processing: 7
  • docs/fern/pages/use-cases/tool-calling-and-reasoning/reasoning-parsing.mdx
  • docs/fern/pages/use-cases/tool-calling-and-reasoning/troubleshooting-tool-calls.md
  • lib/llm/PREPROCESSOR_CASES.md
  • lib/llm/src/preprocessor.rs
  • lib/llm/src/preprocessor/tool_choice.rs
  • lib/llm/src/protocols/openai/chat_completions/aggregator.rs
  • lib/llm/src/protocols/openai/chat_completions/unified_parser.rs
Files with no reviewable changes: 1
  • lib/llm/src/protocols/openai/chat_completions/aggregator.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.

@furionw
furionw enabled auto-merge (squash) November 4, 2026 20:09
@furionw
furionw disabled auto-merge November 4, 2026 20:11
furionw and others added 3 commits November 4, 2026 12:11
The routing table's auto row pointed readers at the reasoning-only
Qwen/Nemotron exception, which reviewers found more confusing than
helpful. The exception stays documented in the paragraph below.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: furionw <qiwa@nvidia.com>
@furionw
furionw force-pushed the qiwa/dis-3338-remove-m3-explicit-guidance branch from 81db071 to 794fc22 Compare November 4, 2026 20:17
@furionw
furionw enabled auto-merge (squash) November 4, 2026 20:25
@furionw
furionw merged commit 4ff9539 into main Nov 4, 2026
200 of 202 checks passed
@furionw
furionw deleted the qiwa/dis-3338-remove-m3-explicit-guidance branch November 4, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants