Skip to content

fix(console): ask chat models for minimal reasoning effort - #1875

Closed
michaelneale wants to merge 3 commits into
mainfrom
fix/console-minimal-reasoning
Closed

michaelneale wants to merge 3 commits into
mainfrom
fix/console-minimal-reasoning

Conversation

@michaelneale

@michaelneale michaelneale commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Ask a reasoning model a casual question in the console today and it deliberates for tens of seconds before answering — and on a 9B at Q4 it sometimes never answers at all: the reasoning consumes the whole token budget, so finish_reason is length and content is null. The chat window shows a growing thinking trace and then nothing.

The console now sends reasoning_effort: "minimal". Thinking stays on and the trace still renders — the console has the UI for it — but the model is asked for the least of it.

This is a mitigation, not a cure

Effort is a hint to the chat template, not an enforced token budget. Measured on a live node, unsloth/Qwen3.5-9B-GGUF:Q4_K_M, prompt "what is updog", max_tokens: 4000:

Request Runs
reasoning_effort: "minimal" 2158 tokens answered / 4000 exhausted, no answer / 3888 answered
nothing set (today) 4000 / 4000 / 4000 — reasoning only, no answer
reasoning off ~350 tokens, 4.6 s, answered every time

So this makes the failure less likely, not impossible. Turning reasoning off is reliable but discards a feature the console exists to display, which is why it is not what this PR does.

Protocol

No API change. reasoning_effort, reasoning.enabled and chat_template_kwargs all work today and are untouched. Only the console's own requests carry the hint.

Note on why chat_template_kwargs is not sent alongside: an explicit template setting wins over an effort alias in normalize_reasoning_template_options, so sending both would override the hint this change exists to send.

Validation

pnpm run typecheck clean; chat API tests pass (9 files, 57 tests), including a new one asserting the request carries minimal and no conflicting template kwargs. Live behaviour is the table above.

Not covered: no screenshot — the visible change is timing and the absence of an unanswered thinking trace, not new UI.

Summary by CodeRabbit

  • New Features

    • Chat requests now use minimal reasoning effort by default, helping casual questions receive faster responses.
    • Requests support configurable reasoning effort levels.
  • Tests

    • Added coverage verifying the default reasoning setting for single-message requests.

Ask a reasoning model a casual question in the console today and it spends
tens of seconds deliberating before it answers. On a 9B at Q4 it often never
answers at all: the reasoning consumes the whole token budget, `finish_reason`
comes back `length` and `content` is null, so the chat window shows a growing
thinking trace and then nothing. Worse, the deliberation degenerates -- one
measured run repeated "Wait, could it be ..." 39 times before terminating.

Measured on Qwen3.5-9B-GGUF:Q4_K_M, prompt "what is updog", max_tokens 4000:

    thinking off   350 tokens,  4.6 s, answered
    default         4000 tokens, 38 s+, 13.5 KB of reasoning, no answer

The console now asks for a direct answer, as it did before the UI rewrite:
`00296cc3a` added `chat_template_kwargs: {enable_thinking: false}` to console
chat requests in March for exactly this symptom, and #545 dropped it when the
request builder was rewritten.

API callers are unaffected and keep full control: `reasoning_effort`,
`reasoning.enabled` and `chat_template_kwargs` all work, verified against a
live node. Only the console's own requests change.

Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
A reasoning model asked a casual question in the console deliberates for tens
of seconds before answering, because nothing in the request asks it not to. On
a 9B at Q4 the deliberation sometimes never converges: it consumes the whole
token budget, `finish_reason` is `length` and `content` is null, so the window
shows a growing thinking trace and then nothing.

The console now sends `reasoning_effort: "minimal"`. Thinking stays on and the
trace still renders -- the console has the UI for it and it is worth showing --
but the model is asked for the least of it.

This is a mitigation, not a cure, and the honest numbers say so. Measured on
Qwen3.5-9B-GGUF:Q4_K_M, "what is updog", max_tokens 4000, three runs:

    minimal    2158 / 4000 / 3888 tokens -- answered, exhausted, answered

Effort is a hint to the chat template, not an enforced budget, so the failure
is less likely rather than impossible. The same prompt with reasoning off
answers every time in ~350 tokens; that was rejected because it throws away a
feature the console exists to display.

API callers are unaffected and keep full control: `reasoning_effort`,
`reasoning.enabled` and `chat_template_kwargs` all work. Only the console's own
requests change, and `chat_template_kwargs` is deliberately not sent with the
effort hint -- an explicit template setting wins over an effort alias
(`normalize_reasoning_template_options`), so sending both would silently
override the hint this change exists to send.

Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c4fcdc96-2ed0-4c04-9ebc-6fa809b675c7

📥 Commits

Reviewing files that changed from the base of the PR and between 39441b3 and 77c93c4.

📒 Files selected for processing (3)
  • crates/mesh-llm-ui/src/features/chat/api/build-input.test.ts
  • crates/mesh-llm-ui/src/features/chat/api/build-input.ts
  • crates/mesh-llm-ui/src/lib/api/types.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The chat request type now supports reasoning effort values. buildResponsesInput sets console requests to 'minimal'. A test verifies this value and confirms that text-only requests omit chat_template_kwargs.

Changes

Chat reasoning effort

Layer / File(s) Summary
Reasoning effort request flow
crates/mesh-llm-ui/src/lib/api/types.ts, crates/mesh-llm-ui/src/features/chat/api/build-input.ts, crates/mesh-llm-ui/src/features/chat/api/build-input.test.ts
ResponsesRequest supports reasoning effort values. buildResponsesInput sets 'minimal' while retaining usage options. The test verifies the generated request.

Priority: ⬇️ Low

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Bug fix

Suggested reviewers: ndizazzo

Merge Risk: ⚪ Minimal · up to 0bb90

The console requests minimal reasoning effort while retaining reasoning output, with no identified merge-blocking risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: configuring console chat requests to use minimal reasoning effort.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/console-minimal-reasoning

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Merge origin/main at 2d83baa without rewriting the console minimal-reasoning commits. This synchronizes the source slice catalog with the protected planner; no CI gates or console behavior are changed.

Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
Signed-off-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
// when nothing asks them not to. The console keeps thinking -- it renders
// the trace -- but asks for the least of it. API callers are unaffected:
// they choose their own reasoning controls.
reasoning_effort: 'minimal'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@michaelneale would low be safer here? I've infrequently seen minimal used

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

hrm yeah - don't know, takes so long to test this so not sure.

@i386

i386 commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

I think this is the correct way to fix this #1876

@i386 i386 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes because the new request field does not change Qwen3.5 behavior and it bypasses the precedence required by #1876.

1. [P1] minimal is a no-op for the reported Qwen3.5 path

At build-input.ts:194, reasoning_effort: "minimal" reaches normalize_reasoning_template_options, which maps every non-none effort to enable_thinking=true and forwards the effort string as a template kwarg. The Qwen3.5 template at the tested source revision reads enable_thinking, but contains no reference to reasoning_effort, reasoning_budget, or thinking_budget. The prior/default request already gave that template enable_thinking=true, so minimal, low, and an omitted effort render the same prompt for this model.

That means the 2/3 versus 0/3 live result does not establish that this patch mitigates the failure; it is consistent with sampling variance, and one of the three patched runs still exhausted all 4,000 tokens without an answer. Changing this line to low would have the same behavior for Qwen3.5.

Source template: https://huggingface.co/Qwen/Qwen3.5-9B/blob/c202236235762e1c871ad0ccb60c8ee5ba337b9a/tokenizer_config.json

2. [P1] the unconditional request override defeats operator and package policy

chat_template_options resolves the request's enable_thinking before defaults.reasoning_enabled, and request template kwargs overwrite default kwargs. Consequently, a console request from this PR turns thinking on even when an operator configured reasoning_enabled="off". It would also override the per-model thinking/direct profiles proposed in #1876, because request values are intentionally the highest-precedence layer.

The console should omit a reasoning override unless the user explicitly selected one. Model/operator/package defaults then remain effective.

3. [P1] the original unbounded failure remains

This PR adds neither max_output_tokens nor a hard reasoning-token budget. When Qwen does not emit </think>, generation can still use the remaining context and return no visible answer. A semantic effort hint cannot reserve output tokens for the answer.

Recommended change

  • Implement #1876 as the durable path: package-owned model profiles, request → operator → package → global precedence, a finite total-output fallback, and sampler-enforced reasoning budgets.
  • Remove the unconditional reasoning_effort from the request builder.
  • If a console hotfix must ship before hard budget enforcement, use the only deterministic Qwen3.5 control currently available: default thinking off with an explicit visible opt-in, and send a finite max_output_tokens. Keep that clearly scoped as an interim console policy.
  • Test resolved server/template behavior for the target model and operator-default precedence, rather than only asserting that the UI object contains the field.

Validation at PR head 0bb908ba5cf8e2bd96b38dac7848b0ade1a2fa3a: UI typecheck, all 1,751 tests (3 skipped), and production build pass. Those checks establish code health; they do not cover the two behavioral failures above.

@michaelneale

Copy link
Copy Markdown
Collaborator Author

@i386 not sure if it would - llama.cpp and other things have same issue - its just how they work I guess? expectation is that harness would set it maybe?

@michaelneale

Copy link
Copy Markdown
Collaborator Author

"minimal" does work - when I tried it, however, so not sure the agents are correct here

@i386

i386 commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

@i386 not sure if it would - llama.cpp and other things have same issue - its just how they work I guess? expectation is that harness would set it maybe?

llama would have the same problem as its thinking budget also defaults to -1

@michaelneale

Copy link
Copy Markdown
Collaborator Author

yeah ideally shouldn't use this I think - just use the correct recommendations?

@i386

i386 commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

yeah ideally shouldn't use this I think - just use the correct recommendations?

yep needs recommendations. thats where our model packages can come in handy. we can do that testing and encode whats right in it.

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.

3 participants