mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(cli): surface local STT model preparation
This commit is contained in:
parent
1818d63052
commit
3524b20728
2 changed files with 85 additions and 4 deletions
39
cli.py
39
cli.py
|
|
@ -11826,14 +11826,37 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
threading.Thread(target=_refresh_level, daemon=True).start()
|
||||
|
||||
def _voice_stt_model(self) -> Optional[str]:
|
||||
"""STT model override from config, or None for the provider default."""
|
||||
"""STT model override from config, or None for the provider default.
|
||||
|
||||
For the local provider, prefer stt.local.model (default ``base``) so the
|
||||
CLI passes a real model name into the local STT backend.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
stt_config = load_config().get("stt", {})
|
||||
return stt_config.get("model") if isinstance(stt_config, dict) else None
|
||||
if not isinstance(stt_config, dict):
|
||||
return None
|
||||
provider = str(stt_config.get("provider") or "").strip().lower()
|
||||
if provider == "local":
|
||||
local_config = stt_config.get("local") or {}
|
||||
if not isinstance(local_config, dict):
|
||||
local_config = {}
|
||||
return local_config.get("model") or "base"
|
||||
return stt_config.get("model")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _voice_stt_provider(self) -> str:
|
||||
"""Configured STT provider name (lowercased), or empty string."""
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
stt_config = load_config().get("stt", {})
|
||||
if not isinstance(stt_config, dict):
|
||||
return ""
|
||||
return str(stt_config.get("provider") or "").strip().lower()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _voice_restart_recording_async(self) -> None:
|
||||
"""Restart continuous-mode recording off-thread (start() can block)."""
|
||||
def _restart_recording():
|
||||
|
|
@ -11880,10 +11903,18 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
# _voice_processing is already True (set atomically above)
|
||||
if hasattr(self, '_app') and self._app:
|
||||
self._app.invalidate()
|
||||
_cprint(f"{_DIM}Transcribing...{_RST}")
|
||||
|
||||
stt_model = self._voice_stt_model()
|
||||
if self._voice_stt_provider() == "local":
|
||||
_cprint(
|
||||
f"{_DIM}Preparing local STT model '{stt_model}' "
|
||||
f"(first use may download it from Hugging Face)...{_RST}"
|
||||
)
|
||||
else:
|
||||
_cprint(f"{_DIM}Transcribing...{_RST}")
|
||||
|
||||
from tools.voice_mode import transcribe_recording
|
||||
result = transcribe_recording(wav_path, model=self._voice_stt_model())
|
||||
result = transcribe_recording(wav_path, model=stt_model)
|
||||
|
||||
if result.get("success") and result.get("transcript", "").strip():
|
||||
transcript = result["transcript"].strip()
|
||||
|
|
|
|||
|
|
@ -1299,6 +1299,56 @@ class TestVoiceStopAndTranscribeReal:
|
|||
cli._voice_stop_and_transcribe()
|
||||
cli._voice_start_recording.assert_not_called()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("stt_config", "expected_model"),
|
||||
[
|
||||
({"provider": "local", "model": "whisper-1", "local": {"model": "small"}}, "small"),
|
||||
({"provider": "local", "local": {"model": "tiny"}}, "tiny"),
|
||||
({"provider": "local", "model": "whisper-1"}, "base"),
|
||||
],
|
||||
)
|
||||
def test_local_stt_shows_model_download_status(self, stt_config, expected_model):
|
||||
recorder = MagicMock()
|
||||
recorder.stop.return_value = "/tmp/test.wav"
|
||||
cli = _make_voice_cli(_voice_recording=True, _voice_recorder=recorder)
|
||||
|
||||
with patch("cli._cprint") as mock_print, \
|
||||
patch("cli.os.path.isfile", return_value=False), \
|
||||
patch("hermes_cli.config.load_config", return_value={"stt": stt_config}), \
|
||||
patch("tools.voice_mode.transcribe_recording",
|
||||
return_value={"success": True, "transcript": "hello"}) as mock_transcribe, \
|
||||
patch("tools.voice_mode.play_beep"):
|
||||
cli._voice_stop_and_transcribe()
|
||||
|
||||
messages = [call.args[0] for call in mock_print.call_args_list]
|
||||
assert any(
|
||||
f"local STT model '{expected_model}'" in message
|
||||
and "first use may download it from Hugging Face" in message
|
||||
for message in messages
|
||||
)
|
||||
mock_transcribe.assert_called_once_with("/tmp/test.wav", model=expected_model)
|
||||
|
||||
def test_non_local_stt_keeps_generic_transcribing_status(self):
|
||||
recorder = MagicMock()
|
||||
recorder.stop.return_value = "/tmp/test.wav"
|
||||
cli = _make_voice_cli(_voice_recording=True, _voice_recorder=recorder)
|
||||
|
||||
with patch("cli._cprint") as mock_print, \
|
||||
patch("cli.os.path.isfile", return_value=False), \
|
||||
patch(
|
||||
"hermes_cli.config.load_config",
|
||||
return_value={"stt": {"provider": "openai", "model": "whisper-1"}},
|
||||
), \
|
||||
patch("tools.voice_mode.transcribe_recording",
|
||||
return_value={"success": True, "transcript": "hello"}) as mock_transcribe, \
|
||||
patch("tools.voice_mode.play_beep"):
|
||||
cli._voice_stop_and_transcribe()
|
||||
|
||||
messages = [call.args[0] for call in mock_print.call_args_list]
|
||||
assert any("Transcribing..." in message for message in messages)
|
||||
assert all("Hugging Face" not in message for message in messages)
|
||||
mock_transcribe.assert_called_once_with("/tmp/test.wav", model="whisper-1")
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("cli.os.unlink")
|
||||
@patch("cli.os.path.isfile", return_value=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue