fix: normalize reasoning effort ordering in UI

This commit is contained in:
Greer Guthrie 2026-04-09 11:06:39 -05:00 committed by Teknium
parent 6f8e426275
commit 775a46ce75
9 changed files with 60 additions and 12 deletions

View file

@ -1158,7 +1158,7 @@ def main(
providers_order (str): Comma-separated list of OpenRouter providers to try in order (e.g. "anthropic,openai,google")
provider_sort (str): Sort providers by "price", "throughput", or "latency" (OpenRouter only)
max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set)
reasoning_effort (str): OpenRouter reasoning effort level: "xhigh", "high", "medium", "low", "minimal", "none" (default: "medium")
reasoning_effort (str): OpenRouter reasoning effort level: "none", "minimal", "low", "medium", "high", "xhigh" (default: "medium")
reasoning_disabled (bool): Completely disable reasoning/thinking tokens (default: False)
prefill_messages_file (str): Path to JSON file containing prefill messages (list of {role, content} dicts)
max_samples (int): Only process the first N samples from the dataset (optional, processes all if not set)
@ -1227,7 +1227,7 @@ def main(
print("🧠 Reasoning: DISABLED (effort=none)")
elif reasoning_effort:
# Use specified effort level
valid_efforts = ["xhigh", "high", "medium", "low", "minimal", "none"]
valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"]
if reasoning_effort not in valid_efforts:
print(f"❌ Error: --reasoning_effort must be one of: {', '.join(valid_efforts)}")
return

6
cli.py
View file

@ -5259,7 +5259,7 @@ class HermesCLI:
Usage:
/reasoning Show current effort level and display state
/reasoning <level> Set reasoning effort (none, low, medium, high, xhigh)
/reasoning <level> Set reasoning effort (none, minimal, low, medium, high, xhigh)
/reasoning show|on Show model thinking/reasoning in output
/reasoning hide|off Hide model thinking/reasoning from output
"""
@ -5277,7 +5277,7 @@ class HermesCLI:
display_state = "on ✓" if self.show_reasoning else "off"
_cprint(f" {_GOLD}Reasoning effort: {level}{_RST}")
_cprint(f" {_GOLD}Reasoning display: {display_state}{_RST}")
_cprint(f" {_DIM}Usage: /reasoning <none|low|medium|high|xhigh|show|hide>{_RST}")
_cprint(f" {_DIM}Usage: /reasoning <none|minimal|low|medium|high|xhigh|show|hide>{_RST}")
return
arg = parts[1].strip().lower()
@ -5303,7 +5303,7 @@ class HermesCLI:
parsed = _parse_reasoning_config(arg)
if parsed is None:
_cprint(f" {_DIM}(._.) Unknown argument: {arg}{_RST}")
_cprint(f" {_DIM}Valid levels: none, low, minimal, medium, high, xhigh{_RST}")
_cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh{_RST}")
_cprint(f" {_DIM}Display: show, hide{_RST}")
return

View file

@ -1595,7 +1595,7 @@ class DiscordAdapter(BasePlatformAdapter):
await self._run_simple_slash(interaction, f"/model {name}".strip())
@tree.command(name="reasoning", description="Show or change reasoning effort")
@discord.app_commands.describe(effort="Reasoning effort: xhigh, high, medium, low, minimal, or none.")
@discord.app_commands.describe(effort="Reasoning effort: none, minimal, low, medium, high, or xhigh.")
async def slash_reasoning(interaction: discord.Interaction, effort: str = ""):
await self._run_simple_slash(interaction, f"/reasoning {effort}".strip())

View file

@ -4840,7 +4840,7 @@ class GatewayRunner:
Usage:
/reasoning Show current effort level and display state
/reasoning <level> Set reasoning effort (none, low, medium, high, xhigh)
/reasoning <level> Set reasoning effort (none, minimal, low, medium, high, xhigh)
/reasoning show|on Show model reasoning in responses
/reasoning hide|off Hide model reasoning from responses
"""
@ -4885,7 +4885,7 @@ class GatewayRunner:
"🧠 **Reasoning Settings**\n\n"
f"**Effort:** `{level}`\n"
f"**Display:** {display_state}\n\n"
"_Usage:_ `/reasoning <none|low|medium|high|xhigh|show|hide>`"
"_Usage:_ `/reasoning <none|minimal|low|medium|high|xhigh|show|hide>`"
)
# Display toggle
@ -4908,7 +4908,7 @@ class GatewayRunner:
else:
return (
f"⚠️ Unknown argument: `{effort}`\n\n"
"**Valid levels:** none, low, minimal, medium, high, xhigh\n"
"**Valid levels:** none, minimal, low, medium, high, xhigh\n"
"**Display:** show, hide"
)

View file

@ -99,7 +99,7 @@ COMMAND_REGISTRY: list[CommandDef] = [
"Configuration"),
CommandDef("reasoning", "Manage reasoning effort and display", "Configuration",
args_hint="[level|show|hide]",
subcommands=("none", "low", "minimal", "medium", "high", "xhigh", "show", "hide", "on", "off")),
subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "show", "hide", "on", "off")),
CommandDef("skin", "Show or change the display skin/theme", "Configuration",
cli_only=True, args_hint="[name]"),
CommandDef("voice", "Toggle voice mode", "Configuration",

View file

@ -1811,7 +1811,10 @@ def _set_reasoning_effort(config, effort: str) -> None:
def _prompt_reasoning_effort_selection(efforts, current_effort=""):
"""Prompt for a reasoning effort. Returns effort, 'none', or None to keep current."""
ordered = list(dict.fromkeys(str(effort).strip().lower() for effort in efforts if str(effort).strip()))
deduped = list(dict.fromkeys(str(effort).strip().lower() for effort in efforts if str(effort).strip()))
canonical_order = ("minimal", "low", "medium", "high", "xhigh")
ordered = [effort for effort in canonical_order if effort in deduped]
ordered.extend(effort for effort in deduped if effort not in canonical_order)
if not ordered:
return None

View file

@ -78,7 +78,7 @@ VALID_REASONING_EFFORTS = ("xhigh", "high", "medium", "low", "minimal")
def parse_reasoning_effort(effort: str) -> dict | None:
"""Parse a reasoning effort level into a config dict.
Valid levels: "xhigh", "high", "medium", "low", "minimal", "none".
Valid levels: "none", "minimal", "low", "medium", "high", "xhigh".
Returns None when the input is empty or unrecognized (caller uses default).
Returns {"enabled": False} for "none".
Returns {"enabled": True, "effort": <level>} for valid effort levels.

View file

@ -68,6 +68,17 @@ class TestCommandRegistry:
for cmd in COMMAND_REGISTRY:
assert cmd.category in valid_categories, f"{cmd.name} has invalid category '{cmd.category}'"
def test_reasoning_subcommands_are_in_logical_order(self):
reasoning = next(cmd for cmd in COMMAND_REGISTRY if cmd.name == "reasoning")
assert reasoning.subcommands[:6] == (
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
)
def test_cli_only_and_gateway_only_are_mutually_exclusive(self):
for cmd in COMMAND_REGISTRY:
assert not (cmd.cli_only and cmd.gateway_only), \

View file

@ -0,0 +1,34 @@
import sys
import types
from hermes_cli.main import _prompt_reasoning_effort_selection
class _FakeTerminalMenu:
last_choices = None
def __init__(self, choices, **kwargs):
_FakeTerminalMenu.last_choices = choices
self._cursor_index = kwargs.get("cursor_index")
def show(self):
return self._cursor_index
def test_reasoning_menu_orders_minimal_before_low(monkeypatch):
fake_module = types.SimpleNamespace(TerminalMenu=_FakeTerminalMenu)
monkeypatch.setitem(sys.modules, "simple_term_menu", fake_module)
selected = _prompt_reasoning_effort_selection(
["low", "minimal", "medium", "high"],
current_effort="medium",
)
assert selected == "medium"
assert _FakeTerminalMenu.last_choices[:4] == [
" minimal",
" low",
" medium ← currently in use",
" high",
]