feat: switch wake word to waltron

This commit is contained in:
2026-03-15 02:59:13 +03:00
parent 6c2702d5e3
commit e1a94c68db
11 changed files with 58 additions and 29 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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:

View File

@@ -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"}'
)

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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:

View File

@@ -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