From 704419c0a3bb2a8c699204c161d65ecbc34eaa28 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:16:12 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`tes?= =?UTF-8?q?t`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @chcavignx. * https://github.com/chcavignx/AI-Autonomous-Assistant/pull/2#issuecomment-3699439979 The following files were modified: * `scripts/models/audio/fast_whisper_objects.py` * `scripts/models/audio/load_all.py` * `scripts/models/audio/load_huggingface_objects.py` * `scripts/models/audio/models_check.py` * `scripts/models/audio/whisper_objects.py` * `src/audio/voice_agent_offline.py` --- scripts/models/audio/fast_whisper_objects.py | 16 ++++++++--- scripts/models/audio/load_all.py | 8 ++++-- .../models/audio/load_huggingface_objects.py | 9 ++++--- scripts/models/audio/models_check.py | 11 +++++--- scripts/models/audio/whisper_objects.py | 27 +++++++++++++++---- src/audio/voice_agent_offline.py | 9 +++++-- 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/scripts/models/audio/fast_whisper_objects.py b/scripts/models/audio/fast_whisper_objects.py index c78ddc9..0c76cdf 100644 --- a/scripts/models/audio/fast_whisper_objects.py +++ b/scripts/models/audio/fast_whisper_objects.py @@ -30,7 +30,12 @@ def get_models_to_download() -> tuple: - """Get the list of models to download based on platform.""" + """ + Select which Hugging Face model identifiers should be downloaded for the current platform. + + Returns: + tuple: Tuple of model identifier strings — on Raspberry Pi this is the base models tuple, otherwise the base models concatenated with the extended models tuple. + """ # Add larger models if not on Raspberry Pi if not detect_raspberry_pi_model(): return MODELS_NAMES_BASE + MODELS_NAMES_EXTENDED @@ -38,8 +43,11 @@ def get_models_to_download() -> tuple: def run() -> None: - """Downloads and saves Hugging Face models, tokenizers, processors, - and their associated datasets to a local backup in your user cache directory.""" + """ + Download the selected Hugging Face models and store them in the user's local cache. + + Selects models appropriate for the current platform, skips models that are already present in the cache, downloads any missing models into the configured cache directory, and prints progress messages for each model. + """ models_to_download = get_models_to_download() for model_name in models_to_download: if model_exists(model_name, cache_dir): @@ -53,4 +61,4 @@ def run() -> None: if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/scripts/models/audio/load_all.py b/scripts/models/audio/load_all.py index 1b920c0..a967226 100644 --- a/scripts/models/audio/load_all.py +++ b/scripts/models/audio/load_all.py @@ -9,7 +9,11 @@ def main() -> None: - """Function to load all models""" + """ + Orchestrates loading of all audio-related models in a fixed sequence. + + Prints progress banners for each phase and invokes the model-loading routines for Whisper, Fast Whisper, Hugging Face objects, Vosk, and Piper in order. + """ print("==================================================") print("🚀 Starting Master Model Loading Process") print("==================================================") @@ -35,4 +39,4 @@ def main() -> None: if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/scripts/models/audio/load_huggingface_objects.py b/scripts/models/audio/load_huggingface_objects.py index 07116f4..7a3c00c 100644 --- a/scripts/models/audio/load_huggingface_objects.py +++ b/scripts/models/audio/load_huggingface_objects.py @@ -23,8 +23,11 @@ def run() -> None: - """Downloads and saves Hugging Face models, tokenizers, processors, - and their associated datasets to a local backup in your user cache directory.""" + """ + Download and save configured Hugging Face models, tokenizers, processors, and datasets to the local user cache. + + This function iterates over the module-level `model_names` and `data_set_names`, skipping entries already present in `cache_dir`. For each missing repository it attempts to download a snapshot into `cache_dir` and prints progress and completion messages. If a repository is not found or is gated, it prints a corresponding message and continues with the next item. + """ # repo_type="model" if None is by default "model" - Not mandatory but for clarity for model_name in model_names: if model_exists(model_name, cache_dir): @@ -61,4 +64,4 @@ def run() -> None: if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/scripts/models/audio/models_check.py b/scripts/models/audio/models_check.py index 2e0f423..f5a2eeb 100644 --- a/scripts/models/audio/models_check.py +++ b/scripts/models/audio/models_check.py @@ -5,8 +5,13 @@ def model_exists(model_name: str, target_dir: str) -> bool: - """Check if a model exists in the target directory. - Handles symlinks and checks for both directories and files (e.g., .pt files). + """ + Determine whether a model whose name contains the given substring exists in the target directory. + + Searches the resolved target directory for any entry whose name contains model_name. Symlinks are followed and both directories and regular files (e.g., model files like `.pt`) are considered matches. + + Returns: + True if a matching file or directory exists in target_dir, False otherwise. """ target_path = Path(target_dir).resolve() if not target_path.exists(): @@ -18,4 +23,4 @@ def model_exists(model_name: str, target_dir: str) -> bool: # entry.exists() follows symlinks by default if entry.is_dir() or entry.is_file(): return True - return False + return False \ No newline at end of file diff --git a/scripts/models/audio/whisper_objects.py b/scripts/models/audio/whisper_objects.py index f7349bd..3f1038e 100644 --- a/scripts/models/audio/whisper_objects.py +++ b/scripts/models/audio/whisper_objects.py @@ -45,7 +45,12 @@ def get_models_to_download() -> dict: - """Get the dictionary of models to download based on platform.""" + """ + Selects the set of Whisper model download mappings appropriate for the current platform. + + Returns: + dict: Mapping of model names to their download URLs. On Raspberry Pi systems returns `MODELS_BASE`; on other platforms returns a merged mapping of `MODELS_BASE` and `MODELS_EXTENDED`. + """ # Add larger models if not on Raspberry Pi if not detect_raspberry_pi_model(): return {**MODELS_BASE, **MODELS_EXTENDED} @@ -53,7 +58,16 @@ def get_models_to_download() -> dict: def download_file(url: str, target_dir: str, filename: str = None) -> None: - """Downloads a file from a URL to a target directory.""" + """ + Download a file from a URL into a target directory, skipping or resuming as appropriate. + + Checks for an existing model/file using `model_exists` and skips download if present. Ensures the target directory exists (resolving symlinks), then downloads the URL to the given filename (defaults to the URL's final path segment). If a partial file is present, attempts to resume using HTTP Range requests; if the server does not support resuming, restarts the download. Handles HTTP 416 as an already-complete file and reports network or filesystem errors via printed messages. + + Parameters: + url (str): The source URL of the file to download. + target_dir (str): Directory path where the file will be saved; created if missing. + filename (str, optional): Filename to use for the saved file. Defaults to the last path segment of `url`. + """ if not filename: filename = url.split("/")[-1] @@ -119,8 +133,11 @@ def download_file(url: str, target_dir: str, filename: str = None) -> None: def run() -> None: - """Downloads and saves Whisper models, tokenizers, processors, - and their associated datasets to a local backup in your user cache directory.""" + """ + Download Whisper models and related tokenizer and processor files into the module cache directory. + + Uses the whisper library to fetch models returned by get_models_to_download and saves them under cache_dir; if the library download fails, falls back to manually downloading model weight files. Also downloads configured GPT-2 support files into the same cache location. + """ print(f"Target directory: {cache_dir}") models_to_download = get_models_to_download() @@ -146,4 +163,4 @@ def run() -> None: if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/src/audio/voice_agent_offline.py b/src/audio/voice_agent_offline.py index f416113..51e4a34 100644 --- a/src/audio/voice_agent_offline.py +++ b/src/audio/voice_agent_offline.py @@ -533,7 +533,12 @@ def __init__(self, tts_config: AgentConfig): self._validate_piper_model() def _validate_piper_model(self): - """Validate Piper model exists""" + """ + Ensure the configured Piper TTS model file exists and load it into the engine. + + Raises: + FileNotFoundError: If `self.config.tts_model_path` does not point to an existing file. + """ if not os.path.exists(self.config.tts_model_path): raise FileNotFoundError( f"Piper model not found: {self.config.tts_model_path}\n" @@ -997,4 +1002,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file From fca4ee310e7f55b616d2df4d4d095ab5e3c2484e Mon Sep 17 00:00:00 2001 From: chcavignx Date: Tue, 30 Dec 2025 15:21:00 +0100 Subject: [PATCH 2/2] fix: Add error handling to model loading phases Refactored the main function to iterate through model loading phases with try/except blocks, ensuring that a failure in one phase does not prevent subsequent phases from running. Added a summary at the end to report successful and failed phases, and exit with a non-zero status if any phase fails. --- scripts/models/audio/load_all.py | 50 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/scripts/models/audio/load_all.py b/scripts/models/audio/load_all.py index 1b920c0..909e02d 100644 --- a/scripts/models/audio/load_all.py +++ b/scripts/models/audio/load_all.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 """Script to load all models.""" +import sys +import traceback + import fast_whisper_objects import load_huggingface_objects import piper_models @@ -14,24 +17,39 @@ def main() -> None: print("🚀 Starting Master Model Loading Process") print("==================================================") - print("\n--- Phase 1: Whisper Models ---") - whisper_objects.run() - - print("\n--- Phase 2: Fast Whisper Models ---") - fast_whisper_objects.run() - - print("\n--- Phase 3: Hugging Face Objects ---") - load_huggingface_objects.run() - - print("\n--- Phase 4: Vosk Models ---") - vosk_models.run() - - print("\n--- Phase 5: Piper Models ---") - piper_models.run() + phases = [ + ("Whisper Models", whisper_objects.run), + ("Fast Whisper Models", fast_whisper_objects.run), + ("Hugging Face Objects", load_huggingface_objects.run), + ("Vosk Models", vosk_models.run), + ("Piper Models", piper_models.run), + ] + + failed_phases = [] + success_count = 0 + + for name, run_func in phases: + print(f"\n--- Phase {success_count + len(failed_phases) + 1}: {name} ---") + try: + run_func() + success_count += 1 + except Exception: # pylint: disable=broad-except + # We catch the general Exception here to ensure that a failure in one + # model loading phase doesn't prevent other phases from running. + print(f"❌ Error in phase '{name}':") + print(traceback.format_exc()) + failed_phases.append(name) print("\n==================================================") - print("✅ All model loading tasks completed!") - print("==================================================") + print("📋 Final Summary") + print(f"✅ Successful phases: {success_count}/{len(phases)}") + if failed_phases: + print(f"❌ Failed phases: {', '.join(failed_phases)}") + print("==================================================") + sys.exit(1) + else: + print("✅ All model loading tasks completed successfully!") + print("==================================================") if __name__ == "__main__":