Merge feature/vosk-wakeword into dev — kills Picovoice dep
This commit is contained in:
commit
277921ce14
5 changed files with 45 additions and 33 deletions
|
|
@ -11,6 +11,7 @@
|
|||
- Из истории удалены случайно закоммиченные секреты.
|
||||
- Бэкенд LLM переключён с OpenAI на Groq (бесплатный тариф) через openai-совместимый API.
|
||||
- Код обновлён под новую версию 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`.
|
||||
|
||||
## Установка
|
||||
|
|
@ -26,9 +27,8 @@ pip install --upgrade pip
|
|||
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/).
|
||||
|
||||
Vosk-модель для русского языка лежит в `model_small/` (уже в репозитории). Запуск:
|
||||
|
|
@ -41,6 +41,7 @@ python main.py
|
|||
|
||||
- `config.py`:
|
||||
- `MICROPHONE_INDEX = -1` — индекс микрофона (`-1` означает устройство по умолчанию). Если микрофонов несколько и берётся не тот, поменяйте число.
|
||||
- `WAKE_WORDS = ('jarvis', 'джарвис')` — варианты транскрипции wake-word, которые принимает Vosk. Если ваш голос/акцент стабильно расшифровывается как, например, `жарвис` — добавьте этот вариант сюда (всё в нижнем регистре).
|
||||
- `GROQ_MODEL` — имя модели Groq (по умолчанию `llama-3.3-70b-versatile`).
|
||||
- `commands.yaml` — набор голосовых команд и их вариантов для fuzzy-матчинга.
|
||||
- `custom-commands/` — скомпилированные AHK-скрипты под конкретный сетап автора оригинала (переключение мониторов, управление Яндекс.Музыкой и т.п.). На вашей машине большая часть из них работать не будет, но ассистент запустится в любом случае.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ VA_VER = "0.1.0"
|
|||
VA_ALIAS = ('джарвис',)
|
||||
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
|
||||
|
||||
# Wake-words распознаются Vosk'ом с grammar-constraint. Добавляйте сюда
|
||||
# варианты написания — Vosk с русской моделью может расшифровать "jarvis"
|
||||
# как "джарвис", "жарвис" и т.п.
|
||||
WAKE_WORDS = ('jarvis', 'джарвис')
|
||||
|
||||
# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
|
||||
# -1 это стандартное записывающее устройство
|
||||
MICROPHONE_INDEX = -1
|
||||
|
|
@ -17,9 +22,6 @@ MICROPHONE_INDEX = -1
|
|||
# Путь к браузеру Google Chrome
|
||||
CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
|
||||
|
||||
# Токен Picovoice
|
||||
PICOVOICE_TOKEN = os.getenv('PICOVOICE_TOKEN')
|
||||
|
||||
# Токен Groq
|
||||
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
||||
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
PICOVOICE_TOKEN=<тут ключ-picovoice>
|
||||
GROQ_TOKEN=<тут ключ-groq>
|
||||
|
|
|
|||
61
main.py
61
main.py
|
|
@ -11,7 +11,6 @@ from ctypes import POINTER, cast
|
|||
|
||||
import openai
|
||||
from openai import OpenAI
|
||||
import pvporcupine
|
||||
import simpleaudio as sa
|
||||
import vosk
|
||||
import yaml
|
||||
|
|
@ -33,24 +32,25 @@ VA_CMD_LIST = yaml.safe_load(
|
|||
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]
|
||||
|
||||
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")
|
||||
samplerate = 16000
|
||||
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)
|
||||
q = queue.Queue()
|
||||
|
||||
|
|
@ -268,33 +268,42 @@ def execute_cmd(cmd: str, voice: str):
|
|||
|
||||
elif cmd == 'off':
|
||||
play("off", True)
|
||||
|
||||
porcupine.delete()
|
||||
exit(0)
|
||||
|
||||
|
||||
# `-1` is the default input audio device.
|
||||
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=porcupine.frame_length)
|
||||
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
|
||||
recorder.start()
|
||||
print('Using device: %s' % recorder.selected_device)
|
||||
|
||||
print(f"Jarvis (v0.1.0) начал свою работу ...")
|
||||
print(f"Jarvis (v{config.VA_VER}) начал свою работу ...")
|
||||
play("run")
|
||||
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:
|
||||
try:
|
||||
pcm = recorder.read()
|
||||
keyword_index = porcupine.process(pcm)
|
||||
sp = struct.pack("h" * len(pcm), *pcm)
|
||||
|
||||
if keyword_index >= 0:
|
||||
recorder.stop()
|
||||
play("greet", True)
|
||||
print("Yes, sir.")
|
||||
recorder.start() # prevent self recording
|
||||
ltc = time.time()
|
||||
if not wake_rec.AcceptWaveform(sp):
|
||||
continue
|
||||
|
||||
result_text = json.loads(wake_rec.Result()).get("text", "")
|
||||
if not heard_wake_word(result_text):
|
||||
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:
|
||||
pcm = recorder.read()
|
||||
|
|
@ -306,6 +315,8 @@ while True:
|
|||
|
||||
break
|
||||
|
||||
wake_rec.Reset()
|
||||
|
||||
except Exception as err:
|
||||
print(f"Unexpected {err=}, {type(err)=}")
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Picovoice stuff
|
||||
pvporcupine
|
||||
# mic capture
|
||||
pvrecorder
|
||||
|
||||
# Vosk
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue