Skip to content

feat(kimi): configurable thinking effort and /effort command#2509

Open
n-WN wants to merge 1 commit into
MoonshotAI:mainfrom
n-WN:feat/thinking-effort
Open

feat(kimi): configurable thinking effort and /effort command#2509
n-WN wants to merge 1 commit into
MoonshotAI:mainfrom
n-WN:feat/thinking-effort

Conversation

@n-WN

@n-WN n-WN commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Related Issue

Resolve #2501

Related: #318 (closed — reasoning_effort support), #2499 (kept the explicit legacy reasoning_effort passthrough this PR builds on). Approach was announced in #2501 (comment).

Description

Adds a first-class, quick way to view and switch the thinking effort level, implementing option 1 from #2501 (slash command) under the name /effort — chosen to avoid confusion with the existing thinking on/off switch (Tab). Happy to rename to /thinking <level> if preferred.

User-facing surface

  • /effort — interactive picker (marks the current level); /effort <level> sets directly; /effort default clears the override. The level is persisted to models.<name>.thinking_effort (falling back to the global default_thinking_effort when the runtime model can't be matched to the config) and applied on reload.
  • --thinking-effort CLI flag, plus default_thinking_effort (global) and models.<name>.thinking_effort (per-model) config. Precedence: CLI > per-model > global.

Semantics (the part worth reviewing)

  • An explicitly chosen level implies the thinking switch: the CLI flag and /effort flip thinking on/off to match (off disables thinking). This avoids the "picked a level but thinking is off, so nothing happens" trap. /effort off is rejected for always-thinking models.
  • Config-file levels never flip the switch by themselves — they apply only when thinking is already on, so a standing per-model level can't silently resurrect thinking after the user turned it off via /model.
  • Kimi providers: an explicit level is forwarded via the legacy reasoning_effort passthrough (retained by fix(kosong): stop sending Kimi reasoning effort implicitly #2499) so the server can actually honor it — low/medium/high genuinely reduce reasoning; max is forwarded verbatim and the server currently ignores it, falling back to the model default (its maximum thinking). The default path sends no effort parameter at all, preserving current behavior exactly unless a level is opted into.
  • Other providers receive the level through the existing with_thinking(effort) state (e.g. Anthropic xhigh/max), unchanged.

Notes: typer can't resolve kosong's PEP 695 type ThinkingEffort = Literal[...] alias in option annotations, so the levels are spelled inline at --thinking-effort with an import-time assert guarding against drift. Changelog entries were added manually to CHANGELOG.md and the en/zh docs changelogs in the established format (I did not run the LLM-driven make gen-changelog/gen-docs). No new telemetry events are introduced.

Testing

  • uv run pytest tests/core/test_thinking_effort_flag.py tests/ui_and_conv/test_shell_effort_slash.py tests/core/test_create_llm.py tests/core/test_config.py -q — 78 passed (29 new tests: flag/config precedence & switch implication in KimiCLI.create(), create_llm effort application & Kimi passthrough, /effort registration/persistence/switch-sync/reload flow, config parsing & validation)
  • uv run pytest tests/core tests/ui_and_conv -q — 1628 passed
  • uv run ruff check && uv run ruff format --check && uv run pyright — clean

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog. (updated CHANGELOG.md + en/zh docs changelogs manually instead)
  • I have run make gen-docs to update the user documentation. (same as above)

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 2 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +7 to +8
- Shell: Add `/effort` slash command to view or change the thinking effort level (`off`/`low`/`medium`/`high`/`xhigh`/`max`) — interactive picker when run without arguments, direct set via `/effort <level>`, and `/effort default` to clear the override; the level is saved per model (falling back to the global default), picking an explicit level also switches thinking on/off to match, and the change is applied on reload
- Core: Add configurable thinking effort — `default_thinking_effort` config, per-model `thinking_effort`, and a `--thinking-effort` CLI flag. The CLI flag implies the thinking switch (`off` disables thinking) while config-file levels apply only when thinking is on; on Kimi providers an explicit level is forwarded via the legacy `reasoning_effort` passthrough while the default path sends nothing, so the model's own default (its maximum thinking) always applies unless you opt into a level

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.

🟡 Auto-generated English changelog page edited by hand

The English changelog page is edited by hand (docs/en/release-notes/changelog.md:7-8), but the repository rules state this page is auto-generated from the root CHANGELOG.md via the sync script and must not be edited manually.
Impact: The hand-written entries can be overwritten or produce inconsistent output the next time the changelog sync script runs.

Rule source and mechanism

docs/AGENTS.md states: "The English changelog (docs/en/release-notes/changelog.md) is auto-generated from the root CHANGELOG.md. Do not edit it manually." and "It is auto-synced from the root CHANGELOG.md via script." The sync script is docs/scripts/sync-changelog.mjs (run by npm run sync). The PR adds bullet lines directly to docs/en/release-notes/changelog.md rather than editing only the root CHANGELOG.md and running the sync. The PR description also acknowledges the LLM-driven gen-changelog/gen-docs steps were skipped.

Prompt for agents
The English changelog page docs/en/release-notes/changelog.md is auto-generated from the root CHANGELOG.md by docs/scripts/sync-changelog.mjs (npm run sync), and docs/AGENTS.md forbids editing it by hand. The PR added two Unreleased bullets directly to this file. Instead, keep the entries only in the root CHANGELOG.md and regenerate the English docs changelog via the sync script so the file content matches the generator output. Revert the manual edits to docs/en/release-notes/changelog.md and produce it through the sync tooling.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +392 to +396
if selected_effort == configured_effort:
console.print(
f"[yellow]Thinking effort is already {selected_effort or 'default'}.[/yellow]"
)
return

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.

🟡 Setting the effort to an already-configured level cannot turn thinking back on

When the chosen effort equals the level already saved in the config, the command reports it is unchanged and stops early (slash.py:392-396) before the step that keeps the thinking switch in sync, so a standing per-model level with thinking currently off can never be re-applied to switch thinking on.
Impact: A user who configured a per-model effort but has thinking off is told the effort is "already" set and cannot use the command to turn thinking back on at that level.

Mechanism

configured_effort is derived from the persisted per-model / global level (src/kimi_cli/ui/shell/slash.py:342-344), independent of the thinking switch. The early-return at src/kimi_cli/ui/shell/slash.py:392-396 fires whenever selected_effort == configured_effort, bypassing the switch-implication block at src/kimi_cli/ui/shell/slash.py:415-419 that would set default_thinking = selected_effort != "off". So if e.g. a model has thinking_effort="high" saved while default_thinking is False, running /effort high prints "already high" and does nothing, even though thinking is actually off and the selected level implies it should be on. The noop condition compares the persisted override rather than the effective runtime state.

Prompt for agents
In the /effort slash command in src/kimi_cli/ui/shell/slash.py, the early no-op return at lines 392-396 fires whenever selected_effort equals the persisted configured_effort. Because an explicitly chosen non-off level is supposed to imply the thinking switch (the block at lines 415-419 sets default_thinking accordingly), returning early here means that when a per-model/global level is already persisted but thinking is currently off, the user cannot re-select that same level to turn thinking on again; the command just says it is already set. Consider making the no-op check also account for whether the resulting thinking switch state would change (e.g. only treat it as a no-op when both the effort and the implied default_thinking match the current state), so that re-selecting a level can still flip the thinking switch on/off.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Add `default_thinking_effort` config and per-model `thinking_effort`,
  plus a `--thinking-effort` CLI flag (precedence: CLI > per-model > global)
- Add `/effort` slash command: interactive picker when run without args,
  direct set via `/effort <level>`, `/effort default` to clear; the level
  is saved per model (falling back to the global default) and applied on
  reload
- An explicit level implies the thinking switch: the CLI flag and the
  /effort command flip thinking on/off to match (`off` disables thinking),
  while config-file levels apply only when thinking is already on
- On Kimi providers an explicit level is forwarded via the legacy
  `reasoning_effort` passthrough (retained by MoonshotAI#2499) so the server can
  actually honor it; the default path sends nothing, preserving the
  model's own default (its maximum thinking)

Closes MoonshotAI#2501
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.

## [Feature Request] 支持在 TUI 主界面直接快捷切换 Reasoning Level / Thinking Effort

1 participant