diff --git a/README.md b/README.md index 2819273..a499298 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Recording input can be a device index (preferred) or a substring of the device name. AI cleanup is always enabled and uses the locked local Llama-3.2-3B GGUF model -downloaded to `~/.cache/aman/models/` on first use. +downloaded to `~/.cache/aman/models/` during daemon initialization. Use `-v/--verbose` to enable DEBUG logs, including recognized/processed transcript text and llama.cpp logs (`llama::` prefix). Without `-v`, logs are diff --git a/src/aman.py b/src/aman.py index c560583..54afd2f 100755 --- a/src/aman.py +++ b/src/aman.py @@ -70,7 +70,9 @@ class Daemon: cfg.stt.model, cfg.stt.device, ) - self.ai_processor: LlamaProcessor | None = None + logging.info("initializing ai processor") + self.ai_processor = LlamaProcessor(verbose=self.verbose) + logging.info("ai processor ready") self.log_transcript = verbose self.vocabulary = VocabularyEngine(cfg.vocabulary, cfg.domain_inference) self._stt_hint_kwargs_cache: dict[str, Any] | None = None @@ -282,7 +284,7 @@ class Daemon: def _get_ai_processor(self) -> LlamaProcessor: if self.ai_processor is None: - self.ai_processor = LlamaProcessor(verbose=self.verbose) + raise RuntimeError("ai processor is not initialized") return self.ai_processor def _stt_hint_kwargs(self) -> dict[str, Any]: diff --git a/tests/test_aman.py b/tests/test_aman.py index d377aae..d9a9867 100644 --- a/tests/test_aman.py +++ b/tests/test_aman.py @@ -87,13 +87,25 @@ class DaemonTests(unittest.TestCase): cfg = Config() return cfg + def _build_daemon( + self, + desktop: FakeDesktop, + model: FakeModel | FakeHintModel, + *, + cfg: Config | None = None, + verbose: bool = False, + ) -> aman.Daemon: + active_cfg = cfg if cfg is not None else self._config() + with patch("aman._build_whisper_model", return_value=model), patch( + "aman.LlamaProcessor", return_value=FakeAIProcessor() + ): + return aman.Daemon(active_cfg, desktop, verbose=verbose) + @patch("aman.stop_audio_recording", return_value=FakeAudio(8)) @patch("aman.start_audio_recording", return_value=(object(), object())) def test_toggle_start_stop_injects_text(self, _start_mock, _stop_mock): desktop = FakeDesktop() - with patch("aman._build_whisper_model", return_value=FakeModel()): - daemon = aman.Daemon(self._config(), desktop, verbose=False) - daemon.ai_processor = FakeAIProcessor() + daemon = self._build_daemon(desktop, FakeModel(), verbose=False) daemon._start_stop_worker = ( lambda stream, record, trigger, process_audio: daemon._stop_and_process( stream, record, trigger, process_audio @@ -112,9 +124,7 @@ class DaemonTests(unittest.TestCase): @patch("aman.start_audio_recording", return_value=(object(), object())) def test_shutdown_stops_recording_without_injection(self, _start_mock, _stop_mock): desktop = FakeDesktop() - with patch("aman._build_whisper_model", return_value=FakeModel()): - daemon = aman.Daemon(self._config(), desktop, verbose=False) - daemon.ai_processor = FakeAIProcessor() + daemon = self._build_daemon(desktop, FakeModel(), verbose=False) daemon._start_stop_worker = ( lambda stream, record, trigger, process_audio: daemon._stop_and_process( stream, record, trigger, process_audio @@ -136,9 +146,7 @@ class DaemonTests(unittest.TestCase): cfg = self._config() cfg.vocabulary.replacements = [VocabularyReplacement(source="Martha", target="Marta")] - with patch("aman._build_whisper_model", return_value=model): - daemon = aman.Daemon(cfg, desktop, verbose=False) - daemon.ai_processor = FakeAIProcessor() + daemon = self._build_daemon(desktop, model, cfg=cfg, verbose=False) daemon._start_stop_worker = ( lambda stream, record, trigger, process_audio: daemon._stop_and_process( stream, record, trigger, process_audio @@ -156,8 +164,7 @@ class DaemonTests(unittest.TestCase): cfg = self._config() cfg.vocabulary.terms = ["Docker", "Systemd"] - with patch("aman._build_whisper_model", return_value=model): - daemon = aman.Daemon(cfg, desktop, verbose=False) + daemon = self._build_daemon(desktop, model, cfg=cfg, verbose=False) result = daemon._transcribe(object()) @@ -172,8 +179,7 @@ class DaemonTests(unittest.TestCase): cfg.vocabulary.terms = ["Systemd"] cfg.vocabulary.replacements = [VocabularyReplacement(source="docker", target="Docker")] - with patch("aman._build_whisper_model", return_value=model): - daemon = aman.Daemon(cfg, desktop, verbose=False) + daemon = self._build_daemon(desktop, model, cfg=cfg, verbose=False) result = daemon._transcribe(object()) @@ -186,14 +192,22 @@ class DaemonTests(unittest.TestCase): desktop = FakeDesktop() cfg = self._config() - with patch("aman._build_whisper_model", return_value=FakeModel()): - daemon = aman.Daemon(cfg, desktop, verbose=False) + daemon = self._build_daemon(desktop, FakeModel(), cfg=cfg, verbose=False) self.assertFalse(daemon.log_transcript) - with patch("aman._build_whisper_model", return_value=FakeModel()): - daemon_verbose = aman.Daemon(cfg, desktop, verbose=True) + daemon_verbose = self._build_daemon(desktop, FakeModel(), cfg=cfg, verbose=True) self.assertTrue(daemon_verbose.log_transcript) + def test_ai_processor_is_initialized_during_daemon_init(self): + desktop = FakeDesktop() + with patch("aman._build_whisper_model", return_value=FakeModel()), patch( + "aman.LlamaProcessor", return_value=FakeAIProcessor() + ) as processor_cls: + daemon = aman.Daemon(self._config(), desktop, verbose=True) + + processor_cls.assert_called_once_with(verbose=True) + self.assertIsNotNone(daemon.ai_processor) + @patch("aman.stop_audio_recording", return_value=FakeAudio(8)) @patch("aman.start_audio_recording", return_value=(object(), object())) def test_passes_clipboard_remove_option_to_desktop(self, _start_mock, _stop_mock): @@ -202,9 +216,7 @@ class DaemonTests(unittest.TestCase): cfg = self._config() cfg.injection.remove_transcription_from_clipboard = True - with patch("aman._build_whisper_model", return_value=model): - daemon = aman.Daemon(cfg, desktop, verbose=False) - daemon.ai_processor = FakeAIProcessor() + daemon = self._build_daemon(desktop, model, cfg=cfg, verbose=False) daemon._start_stop_worker = ( lambda stream, record, trigger, process_audio: daemon._stop_and_process( stream, record, trigger, process_audio @@ -218,8 +230,7 @@ class DaemonTests(unittest.TestCase): def test_state_changes_are_debug_level(self): desktop = FakeDesktop() - with patch("aman._build_whisper_model", return_value=FakeModel()): - daemon = aman.Daemon(self._config(), desktop, verbose=False) + daemon = self._build_daemon(desktop, FakeModel(), verbose=False) with self.assertLogs(level="DEBUG") as logs: daemon.set_state(aman.State.RECORDING)