mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 16:01:49 +08:00
Compare commits
1 Commits
fix/plugin
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6b325cc24 |
37
cli.py
37
cli.py
@@ -115,7 +115,7 @@ def _load_prefill_messages(file_path: str) -> List[Dict[str, Any]]:
|
|||||||
def _parse_reasoning_config(effort: str) -> dict | None:
|
def _parse_reasoning_config(effort: str) -> dict | None:
|
||||||
"""Parse a reasoning effort level into an OpenRouter reasoning config dict.
|
"""Parse a reasoning effort level into an OpenRouter reasoning config dict.
|
||||||
|
|
||||||
Valid levels: "xhigh", "high", "medium", "low", "minimal", "none".
|
Valid levels: "none", "low", "medium", "high", "xhigh".
|
||||||
Returns None to use the default (medium), or a config dict to override.
|
Returns None to use the default (medium), or a config dict to override.
|
||||||
"""
|
"""
|
||||||
if not effort or not effort.strip():
|
if not effort or not effort.strip():
|
||||||
@@ -123,7 +123,7 @@ def _parse_reasoning_config(effort: str) -> dict | None:
|
|||||||
effort = effort.strip().lower()
|
effort = effort.strip().lower()
|
||||||
if effort == "none":
|
if effort == "none":
|
||||||
return {"enabled": False}
|
return {"enabled": False}
|
||||||
valid = ("xhigh", "high", "medium", "low", "minimal")
|
valid = ("low", "medium", "high", "xhigh")
|
||||||
if effort in valid:
|
if effort in valid:
|
||||||
return {"enabled": True, "effort": effort}
|
return {"enabled": True, "effort": effort}
|
||||||
logger.warning("Unknown reasoning_effort '%s', using default (medium)", effort)
|
logger.warning("Unknown reasoning_effort '%s', using default (medium)", effort)
|
||||||
@@ -2366,6 +2366,37 @@ class HermesCLI:
|
|||||||
print(" Usage: /personality <name>")
|
print(" Usage: /personality <name>")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
def _handle_reasoning_command(self, cmd: str):
|
||||||
|
"""Handle the /reasoning command to view or set reasoning effort."""
|
||||||
|
parts = cmd.split(maxsplit=1)
|
||||||
|
valid_efforts = ("none", "low", "medium", "high", "xhigh")
|
||||||
|
|
||||||
|
if len(parts) == 1:
|
||||||
|
if self.reasoning_config is None:
|
||||||
|
current = "medium (default)"
|
||||||
|
elif self.reasoning_config.get("enabled") is False:
|
||||||
|
current = "none (reasoning disabled)"
|
||||||
|
else:
|
||||||
|
current = self.reasoning_config.get("effort", "medium")
|
||||||
|
|
||||||
|
print(f" Reasoning effort: {current}")
|
||||||
|
print(" Usage: /reasoning <none|low|medium|high|xhigh>")
|
||||||
|
return
|
||||||
|
|
||||||
|
effort = parts[1].strip().lower()
|
||||||
|
if effort not in valid_efforts:
|
||||||
|
print(f" (._.) Invalid reasoning level: '{effort}'")
|
||||||
|
print(" Valid levels: none, low, medium, high, xhigh")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.reasoning_config = _parse_reasoning_config(effort)
|
||||||
|
self.agent = None # Force re-init with new reasoning config
|
||||||
|
|
||||||
|
if save_config_value("agent.reasoning_effort", effort):
|
||||||
|
print(f" (^_^)b Reasoning effort set to: {effort} (saved to config)")
|
||||||
|
else:
|
||||||
|
print(f" (^_^) Reasoning effort set to: {effort} (session only)")
|
||||||
|
|
||||||
def _handle_cron_command(self, cmd: str):
|
def _handle_cron_command(self, cmd: str):
|
||||||
"""Handle the /cron command to manage scheduled tasks."""
|
"""Handle the /cron command to manage scheduled tasks."""
|
||||||
parts = cmd.split(maxsplit=2)
|
parts = cmd.split(maxsplit=2)
|
||||||
@@ -2830,6 +2861,8 @@ class HermesCLI:
|
|||||||
elif cmd_lower.startswith("/personality"):
|
elif cmd_lower.startswith("/personality"):
|
||||||
# Use original case (handler lowercases the personality name itself)
|
# Use original case (handler lowercases the personality name itself)
|
||||||
self._handle_personality_command(cmd_original)
|
self._handle_personality_command(cmd_original)
|
||||||
|
elif cmd_lower.startswith("/reasoning"):
|
||||||
|
self._handle_reasoning_command(cmd_original)
|
||||||
elif cmd_lower == "/retry":
|
elif cmd_lower == "/retry":
|
||||||
retry_msg = self.retry_last()
|
retry_msg = self.retry_last()
|
||||||
if retry_msg and hasattr(self, '_pending_input'):
|
if retry_msg and hasattr(self, '_pending_input'):
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ class GatewayRunner:
|
|||||||
"""Load reasoning effort from config or env var.
|
"""Load reasoning effort from config or env var.
|
||||||
|
|
||||||
Checks HERMES_REASONING_EFFORT env var first, then agent.reasoning_effort
|
Checks HERMES_REASONING_EFFORT env var first, then agent.reasoning_effort
|
||||||
in config.yaml. Valid: "xhigh", "high", "medium", "low", "minimal", "none".
|
in config.yaml. Valid: "none", "low", "medium", "high", "xhigh".
|
||||||
Returns None to use default (medium).
|
Returns None to use default (medium).
|
||||||
"""
|
"""
|
||||||
effort = os.getenv("HERMES_REASONING_EFFORT", "")
|
effort = os.getenv("HERMES_REASONING_EFFORT", "")
|
||||||
@@ -385,7 +385,7 @@ class GatewayRunner:
|
|||||||
effort = effort.lower().strip()
|
effort = effort.lower().strip()
|
||||||
if effort == "none":
|
if effort == "none":
|
||||||
return {"enabled": False}
|
return {"enabled": False}
|
||||||
valid = ("xhigh", "high", "medium", "low", "minimal")
|
valid = ("low", "medium", "high", "xhigh")
|
||||||
if effort in valid:
|
if effort in valid:
|
||||||
return {"enabled": True, "effort": effort}
|
return {"enabled": True, "effort": effort}
|
||||||
logger.warning("Unknown reasoning_effort '%s', using default (medium)", effort)
|
logger.warning("Unknown reasoning_effort '%s', using default (medium)", effort)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ COMMANDS_BY_CATEGORY = {
|
|||||||
"/provider": "Show available providers and current provider",
|
"/provider": "Show available providers and current provider",
|
||||||
"/prompt": "View/set custom system prompt",
|
"/prompt": "View/set custom system prompt",
|
||||||
"/personality": "Set a predefined personality",
|
"/personality": "Set a predefined personality",
|
||||||
|
"/reasoning": "Show or change reasoning effort (none|low|medium|high|xhigh)",
|
||||||
"/verbose": "Cycle tool progress display: off → new → all → verbose",
|
"/verbose": "Cycle tool progress display: off → new → all → verbose",
|
||||||
"/skin": "Show or change the display skin/theme",
|
"/skin": "Show or change the display skin/theme",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ EXPECTED_COMMANDS = {
|
|||||||
"/personality", "/clear", "/history", "/new", "/reset", "/retry",
|
"/personality", "/clear", "/history", "/new", "/reset", "/retry",
|
||||||
"/undo", "/save", "/config", "/cron", "/skills", "/platforms",
|
"/undo", "/save", "/config", "/cron", "/skills", "/platforms",
|
||||||
"/verbose", "/compress", "/title", "/usage", "/insights", "/paste",
|
"/verbose", "/compress", "/title", "/usage", "/insights", "/paste",
|
||||||
"/reload-mcp", "/rollback", "/background", "/skin", "/quit",
|
"/reload-mcp", "/rollback", "/background", "/skin", "/reasoning", "/quit",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
137
tests/test_cli_reasoning_command.py
Normal file
137
tests/test_cli_reasoning_command.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"""Tests for /reasoning slash command in HermesCLI."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cli(**kwargs):
|
||||||
|
"""Create a HermesCLI instance with minimal mocking."""
|
||||||
|
import cli as _cli_mod
|
||||||
|
from cli import HermesCLI
|
||||||
|
|
||||||
|
clean_config = {
|
||||||
|
"model": {
|
||||||
|
"default": "anthropic/claude-opus-4.6",
|
||||||
|
"base_url": "https://openrouter.ai/api/v1",
|
||||||
|
"provider": "auto",
|
||||||
|
},
|
||||||
|
"display": {"compact": False, "tool_progress": "all"},
|
||||||
|
"agent": {"reasoning_effort": "medium"},
|
||||||
|
"terminal": {"env_type": "local"},
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_env = {"LLM_MODEL": "", "HERMES_MAX_ITERATIONS": ""}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("cli.get_tool_definitions", return_value=[]),
|
||||||
|
patch.dict("os.environ", clean_env, clear=False),
|
||||||
|
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": clean_config}),
|
||||||
|
):
|
||||||
|
return HermesCLI(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# -- setting valid effort levels -------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("level", ["low", "medium", "high", "xhigh"])
|
||||||
|
def test_reasoning_command_sets_effort_and_persists(level):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj.agent = object() # ensure command forces re-init
|
||||||
|
|
||||||
|
with patch("cli.save_config_value", return_value=True) as mock_save:
|
||||||
|
keep_running = cli_obj.process_command(f"/reasoning {level}")
|
||||||
|
|
||||||
|
assert keep_running is True
|
||||||
|
assert cli_obj.reasoning_config == {"enabled": True, "effort": level}
|
||||||
|
assert cli_obj.agent is None
|
||||||
|
mock_save.assert_called_once_with("agent.reasoning_effort", level)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_command_sets_none_disables_reasoning():
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj.agent = object()
|
||||||
|
|
||||||
|
with patch("cli.save_config_value", return_value=True) as mock_save:
|
||||||
|
keep_running = cli_obj.process_command("/reasoning none")
|
||||||
|
|
||||||
|
assert keep_running is True
|
||||||
|
assert cli_obj.reasoning_config == {"enabled": False}
|
||||||
|
assert cli_obj.agent is None
|
||||||
|
mock_save.assert_called_once_with("agent.reasoning_effort", "none")
|
||||||
|
|
||||||
|
|
||||||
|
# -- rejecting invalid levels ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bad_level", ["ultra", "minimal", "max", "off", "0", "150"])
|
||||||
|
def test_reasoning_command_rejects_invalid_level(capsys, bad_level):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
before = cli_obj.reasoning_config
|
||||||
|
|
||||||
|
with patch("cli.save_config_value", return_value=True) as mock_save:
|
||||||
|
keep_running = cli_obj.process_command(f"/reasoning {bad_level}")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert keep_running is True
|
||||||
|
assert "Invalid reasoning level" in out
|
||||||
|
assert cli_obj.reasoning_config == before
|
||||||
|
mock_save.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
# -- display current level -------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_shows_current_effort(capsys):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj.reasoning_config = {"enabled": True, "effort": "high"}
|
||||||
|
|
||||||
|
cli_obj.process_command("/reasoning")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "Reasoning effort: high" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_shows_default_when_none_set(capsys):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj.reasoning_config = None
|
||||||
|
|
||||||
|
cli_obj.process_command("/reasoning")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "medium (default)" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_shows_disabled_when_none_effort(capsys):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj.reasoning_config = {"enabled": False}
|
||||||
|
|
||||||
|
cli_obj.process_command("/reasoning")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "none (reasoning disabled)" in out
|
||||||
|
|
||||||
|
|
||||||
|
# -- case insensitivity ----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_command_is_case_insensitive(capsys):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
|
||||||
|
with patch("cli.save_config_value", return_value=True):
|
||||||
|
cli_obj.process_command("/reasoning HIGH")
|
||||||
|
|
||||||
|
assert cli_obj.reasoning_config == {"enabled": True, "effort": "high"}
|
||||||
|
|
||||||
|
|
||||||
|
# -- config save failure ---------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_reasoning_shows_session_only_on_save_failure(capsys):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
|
||||||
|
with patch("cli.save_config_value", return_value=False):
|
||||||
|
cli_obj.process_command("/reasoning high")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "session only" in out
|
||||||
Reference in New Issue
Block a user