From b5c6d9ac08a841b466bc39b9569ff1ceacd4ff97 Mon Sep 17 00:00:00 2001 From: CipherFrame Date: Thu, 21 May 2026 17:33:08 -0400 Subject: [PATCH] fix: wire STT lazy-install into transcription_tools.py The ensure('stt.faster_whisper') lazy-install mechanism was defined in lazy_deps.py but never called from the STT code path. When _HAS_FASTER_WHISPER (a module-level constant) evaluated to False at import time, _get_provider() returned 'none' immediately without attempting installation. On fresh container builds or venv recreations, this meant voice message transcription broke silently until someone manually installed faster-whisper. Add _try_lazy_install_stt() helper that calls ensure() and re-checks dynamically via importlib.util.find_spec. Wire it into all three gates in transcription_tools.py: - _get_provider() explicit 'local' path (line 221) - _get_provider() auto-detect path (line 287) - _transcribe_local() guard (line 405) This ensures the first voice message after any fresh install triggers auto-installation instead of failing permanently until a process restart. --- tools/transcription_tools.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index d741530d358..8782c1be087 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -197,6 +197,26 @@ def _normalize_local_command_model(model_name: Optional[str]) -> str: return _normalize_local_model(model_name) +def _try_lazy_install_stt() -> bool: + """Attempt to lazy-install faster-whisper and return True on success. + + The module-level ``_HAS_FASTER_WHISPER`` flag is set at import time and + cached. If the package wasn't installed at startup, calling ``ensure()`` + installs it. This function re-checks dynamically after installation so + the provider can use it immediately without a process restart. + """ + try: + from tools.lazy_deps import ensure + ensure("stt.faster_whisper") + # Re-check dynamically after install + import importlib.util as _iu + if _iu.find_spec("faster_whisper"): + return True + except Exception: + pass + return False + + def _get_provider(stt_config: dict) -> str: """Determine which STT provider to use. @@ -218,6 +238,9 @@ def _get_provider(stt_config: dict) -> str: return "local" if _has_local_command(): return "local_command" + # Try lazy-install before giving up + if _try_lazy_install_stt(): + return "local" logger.warning( "STT provider 'local' configured but unavailable " "(install faster-whisper or set HERMES_LOCAL_STT_COMMAND)" @@ -285,6 +308,9 @@ def _get_provider(stt_config: dict) -> str: return "local" if _has_local_command(): return "local_command" + # Try lazy-install before falling through to cloud providers + if _try_lazy_install_stt(): + return "local" if _HAS_OPENAI and get_env_value("GROQ_API_KEY"): logger.info("No local STT available, using Groq Whisper API") return "groq" @@ -403,7 +429,8 @@ def _transcribe_local(file_path: str, model_name: str) -> Dict[str, Any]: global _local_model, _local_model_name if not _HAS_FASTER_WHISPER: - return {"success": False, "transcript": "", "error": "faster-whisper not installed"} + if not _try_lazy_install_stt(): + return {"success": False, "transcript": "", "error": "faster-whisper not installed"} try: # Lazy-load the model (downloads on first use, ~150 MB for 'base')