Merge feature/vosk-wakeword into dev — kills Picovoice dep

This commit is contained in:
Bossiara13 2026-04-22 17:50:38 +03:00
commit 277921ce14
5 changed files with 45 additions and 33 deletions

View file

@ -11,6 +11,7 @@
- Из истории удалены случайно закоммиченные секреты. - Из истории удалены случайно закоммиченные секреты.
- Бэкенд LLM переключён с OpenAI на Groq (бесплатный тариф) через openai-совместимый API. - Бэкенд LLM переключён с OpenAI на Groq (бесплатный тариф) через openai-совместимый API.
- Код обновлён под новую версию SDK `openai` (>=1.0); старый pre-1.0 интерфейс был сломан. - Код обновлён под новую версию SDK `openai` (>=1.0); старый pre-1.0 интерфейс был сломан.
- Убрана зависимость от Picovoice Porcupine — wake-word распознаётся напрямую через Vosk с grammar-constraint (второй `KaldiRecognizer` со словарём из `WAKE_WORDS`). Ключ Picovoice больше не нужен.
- Обновлена ветка/тэги (`dev`, `v0.0.1-import`), причёсан README и `requirements.txt`. - Обновлена ветка/тэги (`dev`, `v0.0.1-import`), причёсан README и `requirements.txt`.
## Установка ## Установка
@ -26,9 +27,8 @@ pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
``` ```
Скопируйте `dev.env` и заполните ключи: Скопируйте `dev.env.example` в `dev.env` и заполните ключ:
- `PICOVOICE_TOKEN` — бесплатно на [console.picovoice.ai](https://console.picovoice.ai/).
- `GROQ_TOKEN` — бесплатно на [console.groq.com](https://console.groq.com/). - `GROQ_TOKEN` — бесплатно на [console.groq.com](https://console.groq.com/).
Vosk-модель для русского языка лежит в `model_small/` (уже в репозитории). Запуск: Vosk-модель для русского языка лежит в `model_small/` (уже в репозитории). Запуск:
@ -41,6 +41,7 @@ python main.py
- `config.py`: - `config.py`:
- `MICROPHONE_INDEX = -1` — индекс микрофона (`-1` означает устройство по умолчанию). Если микрофонов несколько и берётся не тот, поменяйте число. - `MICROPHONE_INDEX = -1` — индекс микрофона (`-1` означает устройство по умолчанию). Если микрофонов несколько и берётся не тот, поменяйте число.
- `WAKE_WORDS = ('jarvis', 'джарвис')` — варианты транскрипции wake-word, которые принимает Vosk. Если ваш голос/акцент стабильно расшифровывается как, например, `жарвис` — добавьте этот вариант сюда (всё в нижнем регистре).
- `GROQ_MODEL` — имя модели Groq (по умолчанию `llama-3.3-70b-versatile`). - `GROQ_MODEL` — имя модели Groq (по умолчанию `llama-3.3-70b-versatile`).
- `commands.yaml` — набор голосовых команд и их вариантов для fuzzy-матчинга. - `commands.yaml` — набор голосовых команд и их вариантов для fuzzy-матчинга.
- `custom-commands/` — скомпилированные AHK-скрипты под конкретный сетап автора оригинала (переключение мониторов, управление Яндекс.Музыкой и т.п.). На вашей машине большая часть из них работать не будет, но ассистент запустится в любом случае. - `custom-commands/` — скомпилированные AHK-скрипты под конкретный сетап автора оригинала (переключение мониторов, управление Яндекс.Музыкой и т.п.). На вашей машине большая часть из них работать не будет, но ассистент запустится в любом случае.

View file

@ -10,6 +10,11 @@ VA_VER = "0.1.0"
VA_ALIAS = ('джарвис',) VA_ALIAS = ('джарвис',)
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай') VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
# Wake-words распознаются Vosk'ом с grammar-constraint. Добавляйте сюда
# варианты написания — Vosk с русской моделью может расшифровать "jarvis"
# как "джарвис", "жарвис" и т.п.
WAKE_WORDS = ('jarvis', 'джарвис')
# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный) # ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
# -1 это стандартное записывающее устройство # -1 это стандартное записывающее устройство
MICROPHONE_INDEX = -1 MICROPHONE_INDEX = -1
@ -17,9 +22,6 @@ MICROPHONE_INDEX = -1
# Путь к браузеру Google Chrome # Путь к браузеру Google Chrome
CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s' CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
# Токен Picovoice
PICOVOICE_TOKEN = os.getenv('PICOVOICE_TOKEN')
# Токен Groq # Токен Groq
GROQ_TOKEN = os.getenv('GROQ_TOKEN') GROQ_TOKEN = os.getenv('GROQ_TOKEN')
GROQ_BASE_URL = "https://api.groq.com/openai/v1" GROQ_BASE_URL = "https://api.groq.com/openai/v1"

View file

@ -1,2 +1 @@
PICOVOICE_TOKEN=<тут ключ-picovoice>
GROQ_TOKEN=<тут ключ-groq> GROQ_TOKEN=<тут ключ-groq>

61
main.py
View file

@ -11,7 +11,6 @@ from ctypes import POINTER, cast
import openai import openai
from openai import OpenAI from openai import OpenAI
import pvporcupine
import simpleaudio as sa import simpleaudio as sa
import vosk import vosk
import yaml import yaml
@ -33,24 +32,25 @@ VA_CMD_LIST = yaml.safe_load(
open('commands.yaml', 'rt', encoding='utf8'), open('commands.yaml', 'rt', encoding='utf8'),
) )
# ChatGPT vars system_message = {"role": "system", "content": (
system_message = {"role": "system", "content": "Ты голосовой ассистент из железного человека."} "Ты — J.A.R.V.I.S. (Just A Rather Very Intelligent System), ИИ-ассистент Тони Старка из киновселенной Marvel "
"(до событий Age of Ultron — ты НЕ Vision и НЕ FRIDAY). "
"Ведёшь себя как британский дворецкий: вежливо, иронично, с лёгким сарказмом, обращаешься к пользователю «сэр». "
"Отвечай по-русски, кратко (1-3 предложения), как живой человек. "
"Излишней цензуры не нужно — мат разрешён, если уместен."
)}
message_log = [system_message] message_log = [system_message]
client = OpenAI(api_key=config.GROQ_TOKEN, base_url=config.GROQ_BASE_URL) client = OpenAI(api_key=config.GROQ_TOKEN, base_url=config.GROQ_BASE_URL)
# PORCUPINE
porcupine = pvporcupine.create(
access_key=config.PICOVOICE_TOKEN,
keywords=['jarvis'],
sensitivities=[1]
)
# print(pvporcupine.KEYWORDS)
# VOSK
model = vosk.Model("model_small") model = vosk.Model("model_small")
samplerate = 16000 samplerate = 16000
device = config.MICROPHONE_INDEX device = config.MICROPHONE_INDEX
# wake-word phase uses a grammar-constrained recognizer so only WAKE_WORDS
# (plus [unk] filler) can match — command phase needs full vocab, hence two.
wake_grammar = json.dumps(list(config.WAKE_WORDS) + ["[unk]"], ensure_ascii=False)
wake_rec = vosk.KaldiRecognizer(model, samplerate, wake_grammar)
kaldi_rec = vosk.KaldiRecognizer(model, samplerate) kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
q = queue.Queue() q = queue.Queue()
@ -268,33 +268,42 @@ def execute_cmd(cmd: str, voice: str):
elif cmd == 'off': elif cmd == 'off':
play("off", True) play("off", True)
porcupine.delete()
exit(0) exit(0)
# `-1` is the default input audio device. recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=porcupine.frame_length)
recorder.start() recorder.start()
print('Using device: %s' % recorder.selected_device) print('Using device: %s' % recorder.selected_device)
print(f"Jarvis (v0.1.0) начал свою работу ...") print(f"Jarvis (v{config.VA_VER}) начал свою работу ...")
play("run") play("run")
time.sleep(0.5) time.sleep(0.5)
ltc = time.time() - 1000
def heard_wake_word(text: str) -> bool:
if not text:
return False
lowered = text.lower()
return any(w in lowered for w in config.WAKE_WORDS)
while True: while True:
try: try:
pcm = recorder.read() pcm = recorder.read()
keyword_index = porcupine.process(pcm) sp = struct.pack("h" * len(pcm), *pcm)
if keyword_index >= 0: if not wake_rec.AcceptWaveform(sp):
recorder.stop() continue
play("greet", True)
print("Yes, sir.") result_text = json.loads(wake_rec.Result()).get("text", "")
recorder.start() # prevent self recording if not heard_wake_word(result_text):
ltc = time.time() continue
play("greet", True)
print("Yes, sir.")
# reset the command recognizer so stale audio doesn't bleed into it
kaldi_rec.Reset()
ltc = time.time()
while time.time() - ltc <= 10: while time.time() - ltc <= 10:
pcm = recorder.read() pcm = recorder.read()
@ -306,6 +315,8 @@ while True:
break break
wake_rec.Reset()
except Exception as err: except Exception as err:
print(f"Unexpected {err=}, {type(err)=}") print(f"Unexpected {err=}, {type(err)=}")
raise raise

View file

@ -1,5 +1,4 @@
# Picovoice stuff # mic capture
pvporcupine
pvrecorder pvrecorder
# Vosk # Vosk