mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
fix(wake): make the lazy-install path reachable on fresh installs
check_wake_word_requirements() gated 'available' on the audio probe, but the probe imports sounddevice + numpy — two of the packages the lazy installer would install. On a fresh machine deps_ok was False, so audio_ok was always False and /wake on printed the manual pip hint and bailed before the engine constructors' lazy_deps.ensure() could run. Now the audio probe only runs once deps are installed; with deps missing and lazy installs allowed (the default), /wake on proceeds and ensure() installs the pinned engine deps in-process — no restart. The manual pip hint remains for security.allow_lazy_installs=false, and a mic hint still blocks when deps are present but no audio device works. The CLI announces the one-time engine install so the pause is explained.
This commit is contained in:
parent
625d39632a
commit
7a87c6ffd6
3 changed files with 64 additions and 4 deletions
5
cli.py
5
cli.py
|
|
@ -12222,6 +12222,11 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
_cprint(f" {_DIM}{reqs['hint']}{_RST}")
|
||||
return False
|
||||
|
||||
if announce and not reqs.get("deps_available", True):
|
||||
# Fresh install: the engine constructor lazy-installs its deps
|
||||
# (onnxruntime is a large wheel) — tell the user why this is slow.
|
||||
_cprint(f"{_DIM}Installing wake word engine (first use — this may take a minute)...{_RST}")
|
||||
|
||||
self._wake_start_new_session = bool(cfg.get("start_new_session", True))
|
||||
try:
|
||||
start_listening(self._on_wake_word, owner=self, config=cfg)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,53 @@ def test_requirements_unavailable_without_audio(monkeypatch):
|
|||
assert r["audio_available"] is False
|
||||
|
||||
|
||||
def test_requirements_fresh_install_lazy_allowed(monkeypatch):
|
||||
"""Deps missing + lazy installs allowed → available, so /wake on can
|
||||
reach the engine constructor's ``lazy_deps.ensure()`` call.
|
||||
|
||||
Regression: the audio probe imports sounddevice/numpy — packages the
|
||||
lazy installer would fetch — so gating ``available`` on it made the
|
||||
lazy-install path unreachable on a fresh machine (the /wake on handler
|
||||
printed the pip hint and bailed before ensure() ever ran).
|
||||
"""
|
||||
def _boom():
|
||||
raise AssertionError("audio probe must not run while deps are missing")
|
||||
|
||||
monkeypatch.setattr(ww, "_audio_available", _boom)
|
||||
monkeypatch.setattr("tools.lazy_deps.is_available", lambda f: False)
|
||||
monkeypatch.setattr("tools.lazy_deps._allow_lazy_installs", lambda: True)
|
||||
r = ww.check_wake_word_requirements({"provider": "openwakeword"})
|
||||
assert r["available"] is True
|
||||
assert r["deps_available"] is False
|
||||
assert r["hint"] == ""
|
||||
|
||||
|
||||
def test_requirements_fresh_install_lazy_disabled(monkeypatch):
|
||||
"""Deps missing + lazy installs disabled → unavailable, with the manual
|
||||
pip command as the remediation hint."""
|
||||
monkeypatch.setattr(ww, "_audio_available", lambda: True)
|
||||
monkeypatch.setattr("tools.lazy_deps.is_available", lambda f: False)
|
||||
monkeypatch.setattr("tools.lazy_deps._allow_lazy_installs", lambda: False)
|
||||
monkeypatch.setattr(
|
||||
"tools.lazy_deps.feature_install_command", lambda f: f"uv pip install {f}"
|
||||
)
|
||||
r = ww.check_wake_word_requirements({"provider": "openwakeword"})
|
||||
assert r["available"] is False
|
||||
assert r["deps_available"] is False
|
||||
assert "install" in r["hint"]
|
||||
|
||||
|
||||
def test_requirements_deps_present_but_no_audio_hint(monkeypatch):
|
||||
"""Once deps ARE installed, a failing audio probe blocks with a mic hint
|
||||
(lazy installs can't fix a missing audio device)."""
|
||||
monkeypatch.setattr(ww, "_audio_available", lambda: False)
|
||||
monkeypatch.setattr("tools.lazy_deps.is_available", lambda f: True)
|
||||
monkeypatch.setattr("tools.lazy_deps._allow_lazy_installs", lambda: True)
|
||||
r = ww.check_wake_word_requirements({"provider": "openwakeword"})
|
||||
assert r["available"] is False
|
||||
assert "audio device" in r["hint"]
|
||||
|
||||
|
||||
# ── openWakeWord engine (bundled model + base-model fetch) ───────────────
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -507,20 +507,28 @@ def check_wake_word_requirements(cfg: Optional[Dict[str, Any]] = None) -> Dict[s
|
|||
else:
|
||||
feature = "wake.openwakeword"
|
||||
deps_ok = lazy_deps.is_available(feature)
|
||||
audio_ok = _audio_available()
|
||||
lazy_ok = lazy_deps._allow_lazy_installs()
|
||||
# The audio probe imports sounddevice + numpy — two of the very packages
|
||||
# the lazy installer would fetch — so it can only be trusted once the
|
||||
# feature's deps are installed. On a fresh install (deps missing, lazy
|
||||
# installs allowed) we defer the mic check: the engine constructors call
|
||||
# ``lazy_deps.ensure()`` and the stream-open surfaces any real audio
|
||||
# problem. Gating ``available`` on the probe here made the lazy-install
|
||||
# path unreachable (the probe always failed before ensure() could run).
|
||||
audio_ok = _audio_available() if deps_ok else False
|
||||
key_ok = True
|
||||
hint = ""
|
||||
|
||||
if provider == "porcupine" and not (os.getenv("PORCUPINE_ACCESS_KEY") or "").strip():
|
||||
key_ok = False
|
||||
hint = "Set PORCUPINE_ACCESS_KEY (free key at https://console.picovoice.ai)."
|
||||
elif not deps_ok:
|
||||
elif not deps_ok and not lazy_ok:
|
||||
hint = lazy_deps.feature_install_command(feature) or ""
|
||||
elif not audio_ok:
|
||||
elif deps_ok and not audio_ok:
|
||||
hint = "Microphone capture needs sounddevice + numpy and a working audio device."
|
||||
|
||||
return {
|
||||
"available": audio_ok and (deps_ok or lazy_deps._allow_lazy_installs()) and key_ok,
|
||||
"available": key_ok and ((deps_ok and audio_ok) or (not deps_ok and lazy_ok)),
|
||||
"provider": provider,
|
||||
"deps_available": deps_ok,
|
||||
"audio_available": audio_ok,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue