From 89371216b2fcf1e9bf38fb318c7b976eb99e8159 Mon Sep 17 00:00:00 2001 From: x7peeps Date: Tue, 16 Jun 2026 23:42:49 +0800 Subject: [PATCH] fix(stt/tts): guard against null config subsections crashing with AttributeError When stt.local, tts.edge, or other config subsections are explicitly set to null in config.yaml (which happens by default on a fresh --voice setup), stt_config.get('local', {}) returns None instead of {} because YAML null preserves the key. The chained .get('model') then crashes with 'NoneType' object has no attribute 'get'. Apply the defensive (x or {}) pattern to every place a config subsection is read via .get('xxx', {}). Covers local, edge, openai, mistral, and elevenlabs subsections in both transcription_tools.py and tts_tool.py. Closes #47318 --- tools/transcription_tools.py | 18 +++++++++--------- tools/tts_tool.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index 49f8cbaca22..02380fbc433 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -1130,7 +1130,7 @@ def _transcribe_local(file_path: str, model_name: str) -> Dict[str, Any]: # Language: config.yaml (stt.local.language) > env var > auto-detect. _forced_lang = ( - _load_stt_config().get("local", {}).get("language") + (_load_stt_config().get("local", {}) or {}).get("language") or os.getenv(LOCAL_STT_LANGUAGE_ENV) or None ) @@ -1213,7 +1213,7 @@ def _transcribe_local_command(file_path: str, model_name: str) -> Dict[str, Any] # Language: config.yaml (stt.local.language) > env var > "en" default. language = ( - _load_stt_config().get("local", {}).get("language") + (_load_stt_config().get("local", {}) or {}).get("language") or os.getenv(LOCAL_STT_LANGUAGE_ENV) or DEFAULT_LOCAL_STT_LANGUAGE ) @@ -1542,7 +1542,7 @@ def _transcribe_elevenlabs(file_path: str, model_name: str) -> Dict[str, Any]: return {"success": False, "transcript": "", "error": "ELEVENLABS_API_KEY not set"} stt_config = _load_stt_config() - elevenlabs_config = stt_config.get("elevenlabs", {}) + elevenlabs_config = stt_config.get("elevenlabs", {}) or {} base_url = str( elevenlabs_config.get("base_url") or get_env_value("ELEVENLABS_STT_BASE_URL") @@ -1657,14 +1657,14 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A provider = _get_provider(stt_config) if provider == "local": - local_cfg = stt_config.get("local", {}) + local_cfg = stt_config.get("local", {}) or {} model_name = _normalize_local_model( model or local_cfg.get("model", DEFAULT_LOCAL_MODEL) ) return _transcribe_local(file_path, model_name) if provider == "local_command": - local_cfg = stt_config.get("local", {}) + local_cfg = stt_config.get("local", {}) or {} model_name = _normalize_local_command_model( model or local_cfg.get("model", DEFAULT_LOCAL_MODEL) ) @@ -1675,12 +1675,12 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A return _transcribe_groq(file_path, model_name) if provider == "openai": - openai_cfg = stt_config.get("openai", {}) + openai_cfg = stt_config.get("openai", {}) or {} model_name = model or openai_cfg.get("model", DEFAULT_STT_MODEL) return _transcribe_openai(file_path, model_name) if provider == "mistral": - mistral_cfg = stt_config.get("mistral", {}) + mistral_cfg = stt_config.get("mistral", {}) or {} model_name = model or mistral_cfg.get("model", DEFAULT_MISTRAL_STT_MODEL) return _transcribe_mistral(file_path, model_name) @@ -1690,7 +1690,7 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A return _transcribe_xai(file_path, model_name) if provider == "elevenlabs": - elevenlabs_cfg = stt_config.get("elevenlabs", {}) + elevenlabs_cfg = stt_config.get("elevenlabs", {}) or {} model_name = model or elevenlabs_cfg.get("model_id", DEFAULT_ELEVENLABS_STT_MODEL) return _transcribe_elevenlabs(file_path, model_name) @@ -1754,7 +1754,7 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A def _resolve_openai_audio_client_config() -> tuple[str, str]: """Return direct OpenAI audio config or a managed gateway fallback.""" stt_config = _load_stt_config() - openai_cfg = stt_config.get("openai", {}) + openai_cfg = stt_config.get("openai", {}) or {} cfg_api_key = openai_cfg.get("api_key", "") cfg_base_url = openai_cfg.get("base_url", "") if cfg_api_key: diff --git a/tools/tts_tool.py b/tools/tts_tool.py index e2a96fb4ad7..758eace2472 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -949,7 +949,7 @@ async def _generate_edge_tts(text: str, output_path: str, tts_config: Dict[str, Path to the saved audio file. """ _edge_tts = _import_edge_tts() - edge_config = tts_config.get("edge", {}) + edge_config = tts_config.get("edge", {}) or {} voice = edge_config.get("voice", DEFAULT_EDGE_VOICE) speed = float(edge_config.get("speed", tts_config.get("speed", 1.0)))