feat(kimi): configurable thinking effort and /effort command#2509
Conversation
| - 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 |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if selected_effort == configured_effort: | ||
| console.print( | ||
| f"[yellow]Thinking effort is already {selected_effort or 'default'}.[/yellow]" | ||
| ) | ||
| return |
There was a problem hiding this comment.
🟡 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.
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
18df5ee to
37f0376
Compare
Related Issue
Resolve #2501
Related: #318 (closed — reasoning_effort support), #2499 (kept the explicit legacy
reasoning_effortpassthrough 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 defaultclears the override. The level is persisted tomodels.<name>.thinking_effort(falling back to the globaldefault_thinking_effortwhen the runtime model can't be matched to the config) and applied on reload.--thinking-effortCLI flag, plusdefault_thinking_effort(global) andmodels.<name>.thinking_effort(per-model) config. Precedence: CLI > per-model > global.Semantics (the part worth reviewing)
/effortflip thinking on/off to match (offdisables thinking). This avoids the "picked a level but thinking is off, so nothing happens" trap./effort offis rejected for always-thinking models./model.reasoning_effortpassthrough (retained by fix(kosong): stop sending Kimi reasoning effort implicitly #2499) so the server can actually honor it —low/medium/highgenuinely reduce reasoning;maxis 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.with_thinking(effort)state (e.g. Anthropicxhigh/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-effortwith an import-time assert guarding against drift. Changelog entries were added manually toCHANGELOG.mdand the en/zh docs changelogs in the established format (I did not run the LLM-drivenmake 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 inKimiCLI.create(),create_llmeffort application & Kimi passthrough,/effortregistration/persistence/switch-sync/reload flow, config parsing & validation)uv run pytest tests/core tests/ui_and_conv -q— 1628 passeduv run ruff check && uv run ruff format --check && uv run pyright— cleanChecklist
make gen-changelogto update the changelog. (updatedCHANGELOG.md+ en/zh docs changelogs manually instead)make gen-docsto update the user documentation. (same as above)