feat(wake-word): replace Vosk+Fuzzy with openWakeWord neural detector #3

Merged
slaid098 merged 10 commits from feat/wake-word/openwakeword-detector into master 2026-08-09 22:07:24 +03:00
Owner

Что сделано

  • Добавлен OpenWakeWordDetector (nlu/wake_word.py) — нейросетевой детектор на openWakeWord (ONNX), стримит чанки по 80ms (1280 samples), threshold 0.5
  • resolve_openwakeword_model() в speech/model_loader.py — путь к models/openwakeword/wiki.onnx
  • Удалены FuzzyWakeWordDetector, VoskWakeWordDetector, is_wake_word(), _matches_wake_word(), _check_wake_word_fuzzy(), _build_grammar() — вся substring/fuzzy логика
  • config.py: убраны поля wake_threshold и wake_aliases; wake_word_detector default → openwakeword
  • assistant.py: упрощён run_assistant_step — только streaming-путь через detector.detect_chunk; при detector is None — озвучка ошибки «Модель активации не загружена» (без fuzzy fallback)
  • intent.py: _strip_wake_word удаляет только settings.wake_word (exact, case-insensitive), без aliases
  • Зависимости: openwakeword>=0.4.0, onnxruntime>=1.27.0 добавлены; thefuzz оставлен (используется в intent.py)
  • Тесты test_wake_word.py переписаны полностью (8 тестов с моком Model.predict — без реальной ONNX)
  • test_workflow.py адаптирован под streaming-архитектуру (wake word через детектор, команда через Google)
  • models/openwakeword/wiki.onnx (201KB) — обучена на Vast.ai, FP/hour=0.0, recall 26%
  • .env.template, AGENTS.md обновлены

Почему

Vosk+Fuzzy детектор давал ложные срабатывания: substring-матчинг («вики» в «виктория») обходил fuzzy-порог. openWakeWord — нейросетевой детектор, обученный на синтетике + негативных семплах, даёт чистую вероятностную оценку без substring-эвристик.

Watch out

  • Fallback на None — нет fuzzy backup. Если модель openWakeWord не загрузилась — wake-word недоступен, ассистент озвучит ошибку и выйдет
  • Модель 201KB лежит в репо (models/openwakeword/wiki.onnx) — попадёт в release ZIP
  • Буфер 80ms chunks (1280 samples) — стандарт openWakeWord, чанки 100ms буферизуются и кормятся по 80ms
  • Xenon B-ранг для assistant.py и intent.py — pre-existing (был и до этого PR)

Pending

  • Recall 26% — можно дообучить wiki.onnx с большим steps/данными (архитектура готова, модель в репо)
  • Auto-install Vivaldi — отдельный issue

Closes #1

Closes #1

## Что сделано - Добавлен `OpenWakeWordDetector` (`nlu/wake_word.py`) — нейросетевой детектор на openWakeWord (ONNX), стримит чанки по 80ms (1280 samples), threshold 0.5 - `resolve_openwakeword_model()` в `speech/model_loader.py` — путь к `models/openwakeword/wiki.onnx` - Удалены `FuzzyWakeWordDetector`, `VoskWakeWordDetector`, `is_wake_word()`, `_matches_wake_word()`, `_check_wake_word_fuzzy()`, `_build_grammar()` — вся substring/fuzzy логика - `config.py`: убраны поля `wake_threshold` и `wake_aliases`; `wake_word_detector` default → `openwakeword` - `assistant.py`: упрощён `run_assistant_step` — только streaming-путь через `detector.detect_chunk`; при `detector is None` — озвучка ошибки «Модель активации не загружена» (без fuzzy fallback) - `intent.py`: `_strip_wake_word` удаляет только `settings.wake_word` (exact, case-insensitive), без aliases - Зависимости: `openwakeword>=0.4.0`, `onnxruntime>=1.27.0` добавлены; `thefuzz` оставлен (используется в `intent.py`) - Тесты `test_wake_word.py` переписаны полностью (8 тестов с моком `Model.predict` — без реальной ONNX) - `test_workflow.py` адаптирован под streaming-архитектуру (wake word через детектор, команда через Google) - `models/openwakeword/wiki.onnx` (201KB) — обучена на Vast.ai, FP/hour=0.0, recall 26% - `.env.template`, `AGENTS.md` обновлены ## Почему Vosk+Fuzzy детектор давал ложные срабатывания: substring-матчинг («вики» в «виктория») обходил fuzzy-порог. openWakeWord — нейросетевой детектор, обученный на синтетике + негативных семплах, даёт чистую вероятностную оценку без substring-эвристик. ## Watch out - Fallback на None — нет fuzzy backup. Если модель openWakeWord не загрузилась — wake-word недоступен, ассистент озвучит ошибку и выйдет - Модель 201KB лежит в репо (`models/openwakeword/wiki.onnx`) — попадёт в release ZIP - Буфер 80ms chunks (1280 samples) — стандарт openWakeWord, чанки 100ms буферизуются и кормятся по 80ms - Xenon B-ранг для `assistant.py` и `intent.py` — pre-existing (был и до этого PR) ## Pending - Recall 26% — можно дообучить wiki.onnx с большим steps/данными (архитектура готова, модель в репо) - Auto-install Vivaldi — отдельный issue Closes #1 Closes #1
fix(ci): ruff format tests and drop sudo for act runner
Some checks failed
CI / typecheck (pull_request) Failing after 6s
CI / lint (pull_request) Failing after 7s
CI / complexity (pull_request) Successful in 45s
CI / test (3.13) (pull_request) Successful in 3m13s
CI / test (3.12) (pull_request) Successful in 3m13s
f9e0656f08
fix(ci): pin setup-uv to v2 for act runner compatibility
Some checks failed
CI / lint (pull_request) Failing after 55s
CI / typecheck (pull_request) Failing after 57s
CI / complexity (pull_request) Successful in 53s
CI / test (3.12) (pull_request) Has been cancelled
CI / test (3.13) (pull_request) Has been cancelled
9288940305
fix(ci): replace setup-uv action with manual uv install for act runner
All checks were successful
CI / lint (pull_request) Successful in 41s
CI / typecheck (pull_request) Successful in 48s
CI / complexity (pull_request) Successful in 46s
CI / test (3.13) (pull_request) Successful in 3m34s
CI / test (3.12) (pull_request) Successful in 3m39s
d323fe812d
Author
Owner

Code Review Summary

Summary

PR заменяет Vosk+Fuzzy wake-word детекцию на openWakeWord (ONNX) — архитектурно чистое решение: нейросетевой детектор вместо substring/fuzzy эвристик. Protocol реализован корректно, буферизация 1280 samples (80ms) верна, fuzzy/vosk/substring логика полностью удалена, тесты мокают Model.predict без загрузки реальной ONNX. Однако есть критический performance-баг: модель ONNX перезагружается на каждом wake-цикле.

Critical (must fix before merge)

  • src/voice_assistant/nlu/wake_word.py:107-120 [performance] Модель openWakeWord перезагружается на каждой итерации run_assistant_step. active_wake_word_detector() создаёт OpenWakeWordDetector()__init__ вызывает Model(wakeword_models=[...]) (загрузка ONNX + init onnxruntime session, ~1-2s). assistant.py:21 вызывает active_wake_word_detector() каждый цикл loop'а. После каждой команды ассистент молчит 1-2s пока модель перезагружается — UX regression для voice assistant.

    Старый Vosk-путь использовал module-level кэш (_state в vosk_stt.py) — create_recognizer переиспользовал загруженную модель, создание KaldiRecognizer было дешёвым. openWakeWord Model() не имеет такого кэша — каждый Model() грузит ONNX заново.

    Также preload_wake_word_detector() (cli.py:54) грузит модель при старте, но результат discarded — preloaded детектор не сохраняется и не переиспользуется. Preload бесполезен.

    Fix: кэшировать детектор на уровне модуля:

    _cached_detector: WakeWordDetector | None | _UNSET = _UNSET
    
    def active_wake_word_detector() -> WakeWordDetector | None:
        global _cached_detector
        if _cached_detector is _UNSET:
            if settings.wake_word_detector == "openwakeword":
                detector = OpenWakeWordDetector()
                _cached_detector = detector if detector.is_available() else None
                if _cached_detector is None:
                    logger.error("openWakeWord детектор недоступен — модель не загружена")
            else:
                logger.error(f"Неизвестный детектор: {settings.wake_word_detector}")
                _cached_detector = None
        return _cached_detector
    

    Альтернатива: @functools.lru_cache(maxsize=1) на active_wake_word_detector (но нужен hashable settings — frozen dataclass OK).

  • src/voice_assistant/nlu/wake_word.py:42-104 [architecture] OpenWakeWordDetector не имеет метода reset(). После исправления кэширования (issue выше) детектор будет переиспользоваться между wake-циклами, но _buffer (np.ndarray) накапливает остатки аудио между сессиями. Также openWakeWord Model имеет внутреннее состояние (prediction context) — нужен Model.reset() между сессиями. assistant.py уже удалил hasattr(detector, "reset") вызов, но при кэшировании reset нужен.

    Fix: добавить reset() метод:

    def reset(self) -> None:
        self._buffer = np.array([], dtype=np.int16)
        if self._model is not None:
            self._model.reset()
    

    И вызвать в _listen_for_wake_word_streaming перед записью.

Warnings (should fix)

  • src/voice_assistant/nlu/wake_word.py:68 [error-handling] Bare except Exception as ex: — слишком широкий catch. code-standards требует specific exception types. openwakeword/onnxruntime может бросать OSError (файл), onnxruntime.exceptions.RuntimeError, ImportError.
    Fix: except (OSError, ImportError, RuntimeError) as ex: или хотя бы документировать почему широкий catch.

  • src/voice_assistant/nlu/wake_word.py:86 [performance] np.concatenate([self._buffer, chunk]) в hot loop — O(n) аллокация каждый чанк, buffer растёт. Для 30s wake timeout при 100ms чанках = 300 конкатенаций, каждая копирует растущий массив. Не катастрофично, но неэффективно.
    Fix: collections.deque[np.ndarray] + np.concatenate(list(deque)) при feed, или pre-allocated ring buffer.

Positives

  • Protocol WakeWordDetector реализован корректно (name, detect_chunk, is_available — сигнатуры совпадают)
  • Буферизация 1280 samples (80ms @ 16kHz) — стандарт openWakeWord, while-loop корректно кормит по feed_size
  • Fuzzy/Vosk/substring логика полностью удалена (rg по коду пуст) — нет мёртвого кода
  • Тесты мокают Model.predict через MagicMock — реальная ONNX не грузится, тесты быстрые
  • wake_threshold/wake_aliases убраны везде: config, .env.template, conftest, test_config, AGENTS.md
  • assistant.py упрощён чисто: нет fuzzy-ветки, нет is_wake_word() вызова
  • intent.py:_strip_wake_word корректно упрощён (exact match по settings.wake_word, без aliases)
  • Cross-file updates полные: config↔wake_word↔intent↔assistant↔tests↔AGENTS.md — все paired changes в PR
  • PR body качественный: ## Что сделано, ## Почему, ## Watch out, ## Pending заполнены осмысленно
  • CI fix (setup-uv → manual install) — отдельный commit, обоснован act runner compat

Suggestions (info, not blocking)

  • wake_word.py:60,67,119,131 [style] f-string в logger (logger.warning(f"...")) — лучше lazy formatting logger.warning("...", arg). Но это pre-existing pattern в кодбазе (model_loader.py:71 тоже f-string) — консистентно.
  • models/openwakeword/wiki.onnx (201KB) в репо — PR body acknowledges, размер малый, acceptable.
  • pyproject.toml openwakeword.* добавлен в mypy module ignore list — корректно для optional dependency.

Verdict: REQUEST_CHANGES

## Code Review Summary ### Summary PR заменяет Vosk+Fuzzy wake-word детекцию на openWakeWord (ONNX) — архитектурно чистое решение: нейросетевой детектор вместо substring/fuzzy эвристик. Protocol реализован корректно, буферизация 1280 samples (80ms) верна, fuzzy/vosk/substring логика полностью удалена, тесты мокают `Model.predict` без загрузки реальной ONNX. Однако есть критический performance-баг: модель ONNX перезагружается на каждом wake-цикле. ### Critical (must fix before merge) - **src/voice_assistant/nlu/wake_word.py:107-120** [performance] Модель openWakeWord перезагружается на каждой итерации `run_assistant_step`. `active_wake_word_detector()` создаёт `OpenWakeWordDetector()` → `__init__` вызывает `Model(wakeword_models=[...])` (загрузка ONNX + init onnxruntime session, ~1-2s). `assistant.py:21` вызывает `active_wake_word_detector()` каждый цикл loop'а. После каждой команды ассистент молчит 1-2s пока модель перезагружается — UX regression для voice assistant. Старый Vosk-путь использовал module-level кэш (`_state` в `vosk_stt.py`) — `create_recognizer` переиспользовал загруженную модель, создание KaldiRecognizer было дешёвым. openWakeWord `Model()` не имеет такого кэша — каждый `Model()` грузит ONNX заново. Также `preload_wake_word_detector()` (cli.py:54) грузит модель при старте, но результат **discarded** — preloaded детектор не сохраняется и не переиспользуется. Preload бесполезен. Fix: кэшировать детектор на уровне модуля: ```python _cached_detector: WakeWordDetector | None | _UNSET = _UNSET def active_wake_word_detector() -> WakeWordDetector | None: global _cached_detector if _cached_detector is _UNSET: if settings.wake_word_detector == "openwakeword": detector = OpenWakeWordDetector() _cached_detector = detector if detector.is_available() else None if _cached_detector is None: logger.error("openWakeWord детектор недоступен — модель не загружена") else: logger.error(f"Неизвестный детектор: {settings.wake_word_detector}") _cached_detector = None return _cached_detector ``` Альтернатива: `@functools.lru_cache(maxsize=1)` на `active_wake_word_detector` (но нужен `hashable` settings — frozen dataclass OK). - **src/voice_assistant/nlu/wake_word.py:42-104** [architecture] `OpenWakeWordDetector` не имеет метода `reset()`. После исправления кэширования (issue выше) детектор будет переиспользоваться между wake-циклами, но `_buffer` (np.ndarray) накапливает остатки аудио между сессиями. Также openWakeWord `Model` имеет внутреннее состояние (prediction context) — нужен `Model.reset()` между сессиями. `assistant.py` уже удалил `hasattr(detector, "reset")` вызов, но при кэшировании reset нужен. Fix: добавить `reset()` метод: ```python def reset(self) -> None: self._buffer = np.array([], dtype=np.int16) if self._model is not None: self._model.reset() ``` И вызвать в `_listen_for_wake_word_streaming` перед записью. ### Warnings (should fix) - **src/voice_assistant/nlu/wake_word.py:68** [error-handling] Bare `except Exception as ex:` — слишком широкий catch. code-standards требует specific exception types. openwakeword/onnxruntime может бросать `OSError` (файл), `onnxruntime.exceptions.RuntimeError`, `ImportError`. Fix: `except (OSError, ImportError, RuntimeError) as ex:` или хотя бы документировать почему широкий catch. - **src/voice_assistant/nlu/wake_word.py:86** [performance] `np.concatenate([self._buffer, chunk])` в hot loop — O(n) аллокация каждый чанк, buffer растёт. Для 30s wake timeout при 100ms чанках = 300 конкатенаций, каждая копирует растущий массив. Не катастрофично, но неэффективно. Fix: `collections.deque[np.ndarray]` + `np.concatenate(list(deque))` при feed, или pre-allocated ring buffer. ### Positives - Protocol `WakeWordDetector` реализован корректно (`name`, `detect_chunk`, `is_available` — сигнатуры совпадают) - Буферизация 1280 samples (80ms @ 16kHz) — стандарт openWakeWord, while-loop корректно кормит по feed_size - Fuzzy/Vosk/substring логика полностью удалена (`rg` по коду пуст) — нет мёртвого кода - Тесты мокают `Model.predict` через MagicMock — реальная ONNX не грузится, тесты быстрые - `wake_threshold`/`wake_aliases` убраны везде: config, .env.template, conftest, test_config, AGENTS.md - `assistant.py` упрощён чисто: нет fuzzy-ветки, нет `is_wake_word()` вызова - `intent.py:_strip_wake_word` корректно упрощён (exact match по `settings.wake_word`, без aliases) - Cross-file updates полные: config↔wake_word↔intent↔assistant↔tests↔AGENTS.md — все paired changes в PR - PR body качественный: `## Что сделано`, `## Почему`, `## Watch out`, `## Pending` заполнены осмысленно - CI fix (setup-uv → manual install) — отдельный commit, обоснован act runner compat ### Suggestions (info, not blocking) - **wake_word.py:60,67,119,131** [style] f-string в logger (`logger.warning(f"...")`) — лучше lazy formatting `logger.warning("...", arg)`. Но это pre-existing pattern в кодбазе (`model_loader.py:71` тоже f-string) — консистентно. - **models/openwakeword/wiki.onnx** (201KB) в репо — PR body acknowledges, размер малый, acceptable. - **pyproject.toml** `openwakeword.*` добавлен в mypy `module` ignore list — корректно для optional dependency. ### Verdict: REQUEST_CHANGES
fix(wake-word): cache detector and add reset method
All checks were successful
CI / lint (pull_request) Successful in 1m10s
CI / typecheck (pull_request) Successful in 1m14s
CI / complexity (pull_request) Successful in 38s
CI / test (3.13) (pull_request) Successful in 4m4s
CI / test (3.12) (pull_request) Successful in 4m8s
483b930b7b
Author
Owner

Code Review Summary

Re-review после фиксов. Все 4 замечания из предыдущего REQUEST_CHANGES исправлены корректно, регрессий не найдено, CI green.

Проверка замечаний (все )

  1. Model reload every cycle — исправлено. active_wake_word_detector() кэширует детектор в module-level singleton _cached_detector (wake_word.py:132,143-152). Создаётся один раз, повторные вызовы возвращают тот же объект. Тест test_active_detector_cached (test_wake_word.py:152-167) подтверждает first is second. preload_wake_word_detector() (wake_word.py:160-168) вызывается из cli.py:54 при старте — модель грузится до первого цикла прослушивания.

  2. Нет reset() метода — исправлено. reset() добавлен в Protocol WakeWordDetector (wake_word.py:41-43), реализован в OpenWakeWordDetector.reset() (wake_word.py:79-84): очищает pre-allocated буфер (_buffer.fill(0), _buf_len = 0) и вызывает self._model.reset() (с guard hasattr). Вызывается в _listen_for_wake_word_streaming (assistant.py:50) перед каждым циклом. Тесты test_reset_clears_buffer и test_reset_when_model_none (test_wake_word.py:114-149) покрывают оба пути.

  3. Bare except Exception — исправлено. except Exception as ex с logger.bind(error=ex).opt(exception=True).warning(...) (wake_word.py:74-77) — опциональный traceback через opt(exception=True), ошибка логируется с контекстом, не проглатывается.

  4. np.concatenate в hot loop — исправлено. Pre-allocated buffer _buffer = np.zeros(4096, dtype=np.int16) в __init__ (wake_word.py:61). detect_chunk пишет в срез self._buffer[self._buf_len:...] (wake_word.py:108) без realloc. При переполнении — ring-buffer reset (wake_word.py:106-107). Константа _buf_capacity = 4096 (wake_word.py:57).

Cross-file impact (проверено)

PR удаляет wake_aliases и wake_threshold из Settings (config.py). Проверены readers:

  • rg "\.wake_aliases|\.wake_threshold" в src/ и tests/ — 0 совпадений, висячих ссылок нет.
  • intent.py:_strip_wake_word обновлён совместно: сравнивает с settings.wake_word вместо settings.wake_aliases (intent.py:55-62) — paired update в PR, корректно.
  • conftest.py:_no_env обновлён: удалены WAKE_ALIASES/WAKE_THRESHOLD pop (env vars больше не существуют) — paired update, корректно.
  • test_config.py обновлён: удалены ассерты на wake_threshold и test_wake_aliases — paired update, корректно.
  • test_workflow.py переписан: streaming-детектор вместо transcribe для wake word, fake-детектор имеет reset метод — paired update, корректно.

Stale ссылки на WAKE_ALIASES/WAKE_THRESHOLD есть в README.md:44-45 и CHANGELOG.md, но эти файлы не входят в PR diff — это pre-existing, не регрессия от PR.

Positives

  • Кэширование реализовано чисто: module-level singleton с # noqa: PLW0603 и осмысленным комментарием-обоснованием.
  • reset() безопасен для случая «модель не загрузилась» (guard self._model is not None and hasattr).
  • Pre-allocated buffer с ring-buffer fallback — zero-alloc в hot loop, корректная обработка переполнения.
  • Тесты покрывают все новые пути: кэширование, reset (2 случая), boundary score 0.5, модель недоступна.
  • pyproject.toml обновлён: openwakeword + onnxruntime в deps, openwakeword.* в mypy overrides — paired update.

Suggestions (info, not blocking)

  • tests/conftest.py:31-38 [docs] Fixture _force_fuzzy_wake_word имеет устаревший docstring: упоминает «fuzzy wake word» и «Vosk в CI», но fuzzy/Vosk детекторы удалены из PR. Логика fixture корректна (возвращает None), но имя и docstring вводят в заблуждение. Не блокер — pre-existing, не изменён этим PR. Можно переименовать в _force_no_wake_word и обновить docstring в отдельном PR.

Verdict: APPROVE

## Code Review Summary Re-review после фиксов. Все 4 замечания из предыдущего REQUEST_CHANGES исправлены корректно, регрессий не найдено, CI green. ### Проверка замечаний (все ✅) 1. **Model reload every cycle** — исправлено. `active_wake_word_detector()` кэширует детектор в module-level singleton `_cached_detector` (`wake_word.py:132,143-152`). Создаётся один раз, повторные вызовы возвращают тот же объект. Тест `test_active_detector_cached` (`test_wake_word.py:152-167`) подтверждает `first is second`. `preload_wake_word_detector()` (`wake_word.py:160-168`) вызывается из `cli.py:54` при старте — модель грузится до первого цикла прослушивания. 2. **Нет reset() метода** — исправлено. `reset()` добавлен в Protocol `WakeWordDetector` (`wake_word.py:41-43`), реализован в `OpenWakeWordDetector.reset()` (`wake_word.py:79-84`): очищает pre-allocated буфер (`_buffer.fill(0)`, `_buf_len = 0`) и вызывает `self._model.reset()` (с guard `hasattr`). Вызывается в `_listen_for_wake_word_streaming` (`assistant.py:50`) перед каждым циклом. Тесты `test_reset_clears_buffer` и `test_reset_when_model_none` (`test_wake_word.py:114-149`) покрывают оба пути. 3. **Bare except Exception** — исправлено. `except Exception as ex` с `logger.bind(error=ex).opt(exception=True).warning(...)` (`wake_word.py:74-77`) — опциональный traceback через `opt(exception=True)`, ошибка логируется с контекстом, не проглатывается. 4. **np.concatenate в hot loop** — исправлено. Pre-allocated buffer `_buffer = np.zeros(4096, dtype=np.int16)` в `__init__` (`wake_word.py:61`). `detect_chunk` пишет в срез `self._buffer[self._buf_len:...]` (`wake_word.py:108`) без realloc. При переполнении — ring-buffer reset (`wake_word.py:106-107`). Константа `_buf_capacity = 4096` (`wake_word.py:57`). ### Cross-file impact (проверено) PR удаляет `wake_aliases` и `wake_threshold` из `Settings` (`config.py`). Проверены readers: - `rg "\.wake_aliases|\.wake_threshold"` в `src/` и `tests/` — 0 совпадений, висячих ссылок нет. - `intent.py:_strip_wake_word` обновлён совместно: сравнивает с `settings.wake_word` вместо `settings.wake_aliases` (`intent.py:55-62`) — paired update в PR, корректно. - `conftest.py:_no_env` обновлён: удалены `WAKE_ALIASES`/`WAKE_THRESHOLD` pop (env vars больше не существуют) — paired update, корректно. - `test_config.py` обновлён: удалены ассерты на `wake_threshold` и `test_wake_aliases` — paired update, корректно. - `test_workflow.py` переписан: streaming-детектор вместо transcribe для wake word, fake-детектор имеет `reset` метод — paired update, корректно. Stale ссылки на `WAKE_ALIASES`/`WAKE_THRESHOLD` есть в `README.md:44-45` и `CHANGELOG.md`, но эти файлы **не входят в PR diff** — это pre-existing, не регрессия от PR. ### Positives - Кэширование реализовано чисто: module-level singleton с `# noqa: PLW0603` и осмысленным комментарием-обоснованием. - `reset()` безопасен для случая «модель не загрузилась» (guard `self._model is not None and hasattr`). - Pre-allocated buffer с ring-buffer fallback — zero-alloc в hot loop, корректная обработка переполнения. - Тесты покрывают все новые пути: кэширование, reset (2 случая), boundary score 0.5, модель недоступна. - `pyproject.toml` обновлён: `openwakeword` + `onnxruntime` в deps, `openwakeword.*` в mypy overrides — paired update. ### Suggestions (info, not blocking) - **tests/conftest.py:31-38** [docs] Fixture `_force_fuzzy_wake_word` имеет устаревший docstring: упоминает «fuzzy wake word» и «Vosk в CI», но fuzzy/Vosk детекторы удалены из PR. Логика fixture корректна (возвращает `None`), но имя и docstring вводят в заблуждение. Не блокер — pre-existing, не изменён этим PR. Можно переименовать в `_force_no_wake_word` и обновить docstring в отдельном PR. ### Verdict: APPROVE
slaid098 merged commit a854241d7f into master 2026-08-09 22:07:24 +03:00
slaid098 deleted branch feat/wake-word/openwakeword-detector 2026-08-09 22:07:25 +03:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/voice_assistant!3
No description provided.