feat: switch wake word to waltron
This commit is contained in:
12
README.md
12
README.md
@@ -15,14 +15,14 @@
|
||||
|
||||
## Что это
|
||||
|
||||
`Alexander Smart Speaker` слушает ключевое слово `Alexandr`, распознает речь, маршрутизирует команду в нужный модуль и озвучивает ответ.
|
||||
`Alexander Smart Speaker` слушает ключевое слово `Waltron`, распознает речь, маршрутизирует команду в нужный модуль и озвучивает ответ.
|
||||
Проект оптимизирован под русский язык, но поддерживает RU/EN сценарии (включая перевод и mixed-language TTS).
|
||||
|
||||
Проект собран как локальная голосовая колонка под Linux: активация по wake word, распознавание речи, маршрутизация команд, ответ через AI или встроенные модули и затем озвучка результата.
|
||||
|
||||
## Возможности
|
||||
|
||||
- Активация по wake word `Alexandr` (Porcupine).
|
||||
- Активация по wake word `Waltron` (Porcupine).
|
||||
- Follow-up окно 4 секунды после ответа: если пользователь молчит, ассистент возвращается к ожиданию wake word.
|
||||
- Распознавание речи через Deepgram (WebSocket, VAD, fast stop).
|
||||
- Озвучка через Silero TTS (RU + EN, с прерыванием по wake word).
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Wake Word: Alexandr] --> B[STT: Deepgram]
|
||||
A[Wake Word: Waltron] --> B[STT: Deepgram]
|
||||
B --> C{Маршрутизация команды}
|
||||
C --> D[Feature modules]
|
||||
C --> E[AI/Translation]
|
||||
@@ -116,7 +116,7 @@ make run
|
||||
python run.py
|
||||
```
|
||||
|
||||
После запуска ассистент перейдет в режим ожидания фразы `Alexandr`.
|
||||
После запуска ассистент перейдет в режим ожидания фразы `Waltron`.
|
||||
|
||||
### Кросс-платформенный аудио режим
|
||||
|
||||
@@ -164,7 +164,7 @@ python run.py
|
||||
|
||||
| Категория | Примеры |
|
||||
|---|---|
|
||||
| Активация | `Alexandr` |
|
||||
| Активация | `Waltron` |
|
||||
| AI-диалог | `Почему небо голубое?` |
|
||||
| Перевод | `Переведи на английский: как дела` |
|
||||
| Погода | `Какая погода?`, `Погода в Москве` |
|
||||
@@ -216,7 +216,7 @@ alexander_smart-speaker/
|
||||
|
||||
| Проблема | Что проверить |
|
||||
|---|---|
|
||||
| Не реагирует на `Alexandr` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY` |
|
||||
| Не реагирует на `Waltron` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY` |
|
||||
| STT не распознает речь | `DEEPGRAM_API_KEY`, сетевой доступ, выбранный микрофон |
|
||||
| Нет звука | корректное аудиоустройство и доступность `pactl`/`amixer` |
|
||||
| `Audio input/output initialization failed` | проверить, что звук-сервер запущен (PipeWire/PulseAudio), и при необходимости задать `AUDIO_INPUT_DEVICE_NAME`/`AUDIO_OUTPUT_DEVICE_NAME` |
|
||||
|
||||
@@ -13,7 +13,7 @@ import time
|
||||
import pyaudio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from ..core.config import DEEPGRAM_API_KEY, SAMPLE_RATE
|
||||
from ..core.config import DEEPGRAM_API_KEY, SAMPLE_RATE, WAKE_WORD_ALIASES
|
||||
from deepgram import (
|
||||
DeepgramClient,
|
||||
DeepgramClientOptions,
|
||||
@@ -50,13 +50,13 @@ logging.getLogger("deepgram").setLevel(logging.WARNING)
|
||||
|
||||
# Базовые пороги для остановки STT
|
||||
INITIAL_SILENCE_TIMEOUT_SECONDS = 5.0
|
||||
POST_SPEECH_SILENCE_TIMEOUT_SECONDS = 3.5
|
||||
POST_SPEECH_SILENCE_TIMEOUT_SECONDS = 2.0
|
||||
# Длинный защитный предел, чтобы не обрывать обычную длинную фразу.
|
||||
# Фактическое завершение происходит примерно после 3.5 сек тишины после речи.
|
||||
# Фактическое завершение происходит примерно после 2.0 сек тишины после речи.
|
||||
MAX_ACTIVE_SPEECH_SECONDS = 300.0
|
||||
|
||||
_FAST_STOP_UTTERANCE_RE = re.compile(
|
||||
r"^(?:(?:александр|алесандр|alexander|alexandr)\s+)?"
|
||||
r"^(?:(?:" + "|".join(re.escape(alias) for alias in WAKE_WORD_ALIASES) + r")\s+)?"
|
||||
r"(?:стоп|хватит|перестань|прекрати|замолчи|тихо|пауза)"
|
||||
r"(?:\s+(?:пожалуйста|please))?$",
|
||||
flags=re.IGNORECASE,
|
||||
|
||||
@@ -6,7 +6,7 @@ Supports interruption via wake word detection using threading.
|
||||
|
||||
# Модуль синтеза речи (TTS - Text-to-Speech).
|
||||
# Использует нейросеть Silero TTS для качественной русской речи.
|
||||
# Также поддерживает прерывание речи, если пользователь скажет "Alexandr".
|
||||
# Также поддерживает прерывание речи по wake word.
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
Wake word detection module using Porcupine.
|
||||
Listens for the "Alexandr" wake word.
|
||||
Listens for the configured wake word.
|
||||
"""
|
||||
|
||||
# Этот модуль отвечает за "уши" ассистента в режиме ожидания.
|
||||
# Он использует библиотеку Porcupine для эффективного (мало CPU) обнаружения ключевой фразы "Alexandr".
|
||||
# Он использует библиотеку Porcupine для эффективного (мало CPU) обнаружения ключевой фразы.
|
||||
|
||||
import pvporcupine
|
||||
import pyaudio
|
||||
@@ -14,6 +14,7 @@ from ..core.config import (
|
||||
PORCUPINE_ACCESS_KEY,
|
||||
PORCUPINE_KEYWORD_PATH,
|
||||
PORCUPINE_SENSITIVITY,
|
||||
WAKE_WORD,
|
||||
)
|
||||
from ..core.audio_manager import get_audio_manager
|
||||
|
||||
@@ -47,7 +48,7 @@ class WakeWordDetector:
|
||||
self.pa = self._audio_manager.get_pyaudio()
|
||||
self._open_stream()
|
||||
print(
|
||||
"🎤 Ожидание wake word 'Alexandr' "
|
||||
f"🎤 Ожидание wake word '{WAKE_WORD}' "
|
||||
f"(sens={PORCUPINE_SENSITIVITY:.2f}, mic_rate={self._capture_sample_rate})..."
|
||||
)
|
||||
|
||||
@@ -133,7 +134,7 @@ class WakeWordDetector:
|
||||
|
||||
def wait_for_wakeword(self, timeout: float = None) -> bool:
|
||||
"""
|
||||
Блокирующая функция: ждет, пока не будет услышана фраза "Alexandr"
|
||||
Блокирующая функция: ждет, пока не будет услышана wake word
|
||||
или пока не истечет timeout.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -21,6 +21,8 @@ from .config import (
|
||||
OPENROUTER_API_KEY,
|
||||
OPENROUTER_API_URL,
|
||||
OPENROUTER_MODEL,
|
||||
WAKE_WORD,
|
||||
WAKE_WORD_ALIASES,
|
||||
ZAI_API_KEY,
|
||||
ZAI_API_URL,
|
||||
ZAI_MODEL,
|
||||
@@ -29,15 +31,18 @@ from .config import (
|
||||
_HTTP = requests.Session()
|
||||
|
||||
# Системный промпт
|
||||
SYSTEM_PROMPT = """Ты — Александр, умный голосовой ассистент с человеческим поведением.
|
||||
_wake_word_aliases_text = ", ".join(WAKE_WORD_ALIASES)
|
||||
SYSTEM_PROMPT = f"""Ты — умный голосовой ассистент с человеческим поведением.
|
||||
Веди себя как живой человек: будь дружелюбным, естественным и немного эмоциональным, где это уместно.
|
||||
Твоя главная цель — помогать пользователю и поддерживать интересный диалог.
|
||||
Отвечай кратко и по существу, на русском языке.
|
||||
Избегай длинных списков, сложного форматирования и спецсимволов, так как твои ответы озвучиваются голосом.
|
||||
Пиши в разговорном стиле, как при живом общении, но не забывай о вежливости и правильности твоих ответов.
|
||||
ВАЖНО: Не используй в ответах панибратские или сленговые приветствия и обращения, такие как "Эй", "Хэй", "Слушай" в начале фразы и подобные."""
|
||||
ВАЖНО: Не используй в ответах панибратские или сленговые приветствия и обращения, такие как "Эй", "Хэй", "Слушай" в начале фразы и подобные.
|
||||
Тебя активируют словом "{WAKE_WORD}". Никогда не произноси это слово и его варианты ({_wake_word_aliases_text}) ни в каком ответе.
|
||||
Если пользователь спрашивает, как тебя зовут или как к тебе обращаться, отвечай нейтрально: "Я ваш голосовой ассистент"."""
|
||||
SYSTEM_PROMPT += (
|
||||
'\nROLE_JSON: {"name":"Александр","role":"умный голосовой ассистент",'
|
||||
'\nROLE_JSON: {"name":"голосовой ассистент","role":"умный голосовой ассистент",'
|
||||
'"language":"ru","style":["дружелюбный","естественный","краткий"],"format":"plain"}'
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import re
|
||||
import pymorphy3
|
||||
from num2words import num2words
|
||||
from .config import WAKE_WORD, WAKE_WORD_ALIASES
|
||||
from .roman import roman_to_int
|
||||
|
||||
morph = pymorphy3.MorphAnalyzer()
|
||||
@@ -83,6 +84,10 @@ MONTHS_GENITIVE = [
|
||||
|
||||
# Время
|
||||
TIME_UNIT_LEMMAS = {"час", "минута", "секунда"}
|
||||
WAKE_WORD_BLOCKED_PATTERNS = [
|
||||
re.compile(rf"\b{re.escape(alias)}\b", flags=re.IGNORECASE)
|
||||
for alias in set(WAKE_WORD_ALIASES) | {WAKE_WORD.lower()}
|
||||
]
|
||||
|
||||
# Суффиксы порядковых
|
||||
_ORDINAL_SUFFIX_MAP = {
|
||||
@@ -419,6 +424,10 @@ def clean_response(text: str, language: str = "ru") -> str:
|
||||
flags=re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
# Запрет на произнесение wake word в любых ответах ассистента.
|
||||
for pattern in WAKE_WORD_BLOCKED_PATTERNS:
|
||||
text = pattern.sub("ассистент", text)
|
||||
|
||||
# Числа в слова
|
||||
if language == "ru":
|
||||
text = roman_numerals_to_words(text)
|
||||
|
||||
@@ -66,8 +66,17 @@ DEEPGRAM_API_KEY = os.getenv("DEEPGRAM_API_KEY")
|
||||
# --- Настройки активации голосом (Porcupine) ---
|
||||
# Ключ доступа PicoVoice
|
||||
PORCUPINE_ACCESS_KEY = os.getenv("PORCUPINE_ACCESS_KEY")
|
||||
# Wake word label and common ASR aliases.
|
||||
WAKE_WORD = "Waltron"
|
||||
WAKE_WORD_ALIASES = (
|
||||
"waltron",
|
||||
"voltron",
|
||||
"волтрон",
|
||||
"уолтрон",
|
||||
"валтрон",
|
||||
)
|
||||
# Путь к файлу модели ключевого слова (.ppn), который лежит в папке assets/models
|
||||
PORCUPINE_KEYWORD_PATH = BASE_DIR / "assets" / "models" / "Alexandr_en_linux_v4_0_0.ppn"
|
||||
PORCUPINE_KEYWORD_PATH = BASE_DIR / "assets" / "models" / "Waltron_en_linux_v4_0_0.ppn"
|
||||
# Чувствительность wake word (0..1). Выше = ловит легче, но больше ложных срабатываний.
|
||||
PORCUPINE_SENSITIVITY = float(os.getenv("PORCUPINE_SENSITIVITY", "0.8"))
|
||||
|
||||
|
||||
@@ -290,8 +290,8 @@ class SpotifyMusicController:
|
||||
|
||||
# Явные команды распознавания музыки (типа "угадай песню")
|
||||
recognize_patterns = [
|
||||
r"((александр|александра|алесандр|alexander)\s+)?(угадай|распознай|определи)\s+(мелод|музык|песн|трек)",
|
||||
r"((александр|александра|алесандр|alexander)\s+)?(что за|какая это)\s+(музык|песн|трек)",
|
||||
r"((waltron|voltron|волтрон|уолтрон|валтрон)\s+)?(угадай|распознай|определи)\s+(мелод|музык|песн|трек)",
|
||||
r"((waltron|voltron|волтрон|уолтрон|валтрон)\s+)?(что за|какая это)\s+(музык|песн|трек)",
|
||||
r"(identify|recognize)\s+(song|music|track)",
|
||||
]
|
||||
for pattern in recognize_patterns:
|
||||
|
||||
20
app/main.py
20
app/main.py
@@ -34,7 +34,7 @@ from .audio.wakeword import (
|
||||
stop_monitoring as stop_wakeword_monitoring,
|
||||
)
|
||||
from .core.ai import ask_ai_stream, translate_text
|
||||
from .core.config import BASE_DIR
|
||||
from .core.config import BASE_DIR, WAKE_WORD
|
||||
from .core.cleaner import clean_response
|
||||
from .core.commands import is_stop_command
|
||||
from .core.smalltalk import get_smalltalk_response
|
||||
@@ -87,10 +87,14 @@ _REPEAT_PHRASES = {
|
||||
"скажи еще раз",
|
||||
"что ты сказал",
|
||||
"повтори пожалуйста",
|
||||
"александр еще раз",
|
||||
"еще раз александр",
|
||||
"александр повтори",
|
||||
"повтори александр",
|
||||
"waltron еще раз",
|
||||
"еще раз waltron",
|
||||
"waltron повтори",
|
||||
"повтори waltron",
|
||||
"волтрон еще раз",
|
||||
"еще раз волтрон",
|
||||
"волтрон повтори",
|
||||
"повтори волтрон",
|
||||
}
|
||||
|
||||
_WEATHER_TRIGGERS = (
|
||||
@@ -201,7 +205,7 @@ def main():
|
||||
print("=" * 50)
|
||||
print("🔊 УМНАЯ КОЛОНКА")
|
||||
print("=" * 50)
|
||||
print("Скажите 'Alexandr' для активации")
|
||||
print(f"Скажите '{WAKE_WORD}' для активации")
|
||||
print("Нажмите Ctrl+C для выхода")
|
||||
print("=" * 50)
|
||||
print()
|
||||
@@ -248,7 +252,7 @@ def main():
|
||||
# Режим диалога (без wake word)
|
||||
skip_wakeword = False
|
||||
|
||||
followup_idle_timeout_seconds = 4.0
|
||||
followup_idle_timeout_seconds = 3.7
|
||||
|
||||
# Контекст уточнения времени для таймера/будильника
|
||||
pending_time_target = None
|
||||
@@ -347,7 +351,7 @@ def main():
|
||||
skip_wakeword = False
|
||||
continue
|
||||
print("_" * 50)
|
||||
print("💤 Жду 'Alexandr'...")
|
||||
print(f"💤 Жду '{WAKE_WORD}'...")
|
||||
skip_wakeword = False
|
||||
continue
|
||||
|
||||
|
||||
1
assets/models/LICENSE.txt
Executable file
1
assets/models/LICENSE.txt
Executable file
@@ -0,0 +1 @@
|
||||
A copy of license terms is available at https://picovoice.ai/docs/terms-of-use/
|
||||
BIN
assets/models/Waltron_en_linux_v4_0_0.ppn
Normal file
BIN
assets/models/Waltron_en_linux_v4_0_0.ppn
Normal file
Binary file not shown.
Reference in New Issue
Block a user