4 Commits

Author SHA1 Message Date
Dmitrium12 5f1e2e67a4 add command and add its in ollama functions 2024-04-29 17:58:53 +07:00
Dmitrium12 c23b1d42bf add linter and test ollama in python 2024-04-29 15:31:01 +07:00
Dmitrium12 b2866d073c .gitignore 2024-01-09 22:10:57 +07:00
Dmitrium12 a889c68e40 starting 2024-01-09 22:10:13 +07:00
29 changed files with 117 additions and 1219 deletions
-10
View File
@@ -1,10 +0,0 @@
VA_ALIAS='("джарвис",)'
VA_TBR='("скажи", "покажи", "ответь", "произнеси", "расскажиv, "сколько", "слушай")'
VOSK_MODEL_NAME='vosk-model-small-ru-0.22' # vosk-model-ru-0.42
MICROPHONE_INDEX=-1
PICOVOICE_TOKEN='token'
# home assistant
HOME_ASSISTANT_URL='http://localhost:8123/api'
HOME_ASSISTANT_TOKEN=''
+1 -2
View File
@@ -6,8 +6,7 @@ __pycache__/
*$py.class
# Custom
data/model_small/
data/model_large/
model_small/
# C extensions
*.so
+8 -35
View File
@@ -3,34 +3,27 @@ import os
import struct
import time
import noisereduce as nr
import pvporcupine
import vosk
import yaml
from fuzzywuzzy import fuzz
from pvrecorder import PvRecorder
from data import config
from modules import HomeAssistant
from utils import download_models, execute_cmd, play
import config
from utils import execute_cmd, play
class Jarvis:
"""
Это основной модуль голосового ассистента
"""
def __init__(self):
download_models.install_vosk_model()
self.recorder = None
self.CDIR = os.getcwd()
self.VA_CMD_LIST = yaml.safe_load(open('data/commands.yaml', encoding='utf8'))
self.home_assistant = HomeAssistant.HomeAssistant()
self.VA_CMD_LIST = yaml.safe_load(open('commands.yaml', encoding='utf8'))
self.porcupine = pvporcupine.create(
access_key=config.PICOVOICE_TOKEN,
keywords=['jarvis'],
sensitivities=[1]
)
self.kaldi_rec = vosk.KaldiRecognizer(vosk.Model("data/model_small"), 16000)
self.kaldi_rec = vosk.KaldiRecognizer(vosk.Model("model_small"), 32000)
def main(self):
self.recorder = PvRecorder(
@@ -44,12 +37,7 @@ class Jarvis:
while True:
try:
pcm = self.recorder.read()
reduced_audio = nr.reduce_noise(
y=pcm,
sr=16000,
prop_decrease=0.6
)
if self.porcupine.process(reduced_audio) >= 0:
if self.porcupine.process(pcm) >= 0:
self.recorder.stop()
self.play("greet", True)
self.recorder.start()
@@ -65,13 +53,7 @@ class Jarvis:
print(f"Unexpected {err=}, {type(err)=}")
raise
def va_respond(self, voice: str) -> bool:
"""
Функция предсказывает команду
:param voice: str - распознанная строка
:return: bool - распознана или нет команда
"""
def va_respond(self, voice: str):
print(f"Распознано: {voice}")
for x in config.VA_ALIAS + config.VA_TBR:
voice = voice.replace(x, "").strip()
@@ -82,7 +64,6 @@ class Jarvis:
if vrt > rc['percent']:
rc['cmd'] = c
rc['percent'] = vrt
rc['recognized_phrase'] = x
if len(rc['cmd'].strip()) <= 0:
return False
elif rc['percent'] < 70 or rc['cmd'] not in self.VA_CMD_LIST.keys():
@@ -90,16 +71,8 @@ class Jarvis:
time.sleep(1)
return False
else:
execute_cmd.execute_cmd(self, rc['cmd'], rc['recognized_phrase'], voice)
execute_cmd.execute_cmd(self, rc['cmd'])
return True
def play(self, phrase: str, wait_done: bool = True):
"""
Функция для запуска голосовой команды
:param self: modules.Jarvis - объект основного модуля
:param phrase: str - фраза для запуска голосовой команды
:param wait_done: bool - нужно-ли ждать окончания фразы
:return:
"""
def play(self, phrase, wait_done=True):
play.play(self, phrase, wait_done)
-6
View File
@@ -72,9 +72,3 @@ weather:
- возможен дождь сегодня?
- прогноз погоды на сегодня
- погода
home_assistant_execute:
- включи телевизор
- выключи телевизор
- начни уборку
home_assistant_get:
- тест
Executable
+5
View File
@@ -0,0 +1,5 @@
VA_ALIAS = ('джарвис',)
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
MICROPHONE_INDEX = -1
PICOVOICE_TOKEN = "4xbwaZwZmSHeTiowFl5Rgqsc8CR4FKGV8YueJUlR4Zt2e1kB64IDcA=="
OPENAI_TOKEN = "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"
-15
View File
@@ -1,15 +0,0 @@
import environs
env = environs.Env()
env.read_env()
VA_ALIAS = env.str("VA_ALIAS")
VA_TBR = env.str("VA_TBR")
VOSK_MODEL_NAME = env.str("VOSK_MODEL_NAME")
MICROPHONE_INDEX = env.int("MICROPHONE_INDEX")
PICOVOICE_TOKEN = env.str("PICOVOICE_TOKEN")
# home assistant
HOME_ASSISTANT_URL = env.str("HOME_ASSISTANT_URL")
HOME_ASSISTANT_TOKEN = env.str("HOME_ASSISTANT_TOKEN")
-4
View File
@@ -1,4 +0,0 @@
пылесос:
- entity_id:vacuum.roborock_vacuum_m1s
- state:находится в
- attributes.battery_level:а его уровень зарядки
+1 -1
View File
@@ -1,4 +1,4 @@
from modules.Jarvis import Jarvis
from Jarvis import Jarvis
def main():
-97
View File
@@ -1,97 +0,0 @@
import requests
import yaml
from fuzzywuzzy import process
from requests import Response
from data import config
class HomeAssistant:
"""
Модуль home assistant для работы с его api
"""
def __init__(self):
self.url = "http://192.168.0.112:9999/api"
self.token = config.HOME_ASSISTANT_TOKEN
self.HA_CMD_LIST = yaml.safe_load(open('data/home_assistant_entities.yaml', encoding='utf8'))
def get_info(self, state: str) -> Response:
"""
Функция для получения информации о заданном entity
:param state: str - объект в home assistant информацию о котором надо узнать
:return: Response - ответ от сервера api
"""
response = requests.get(
url=f"{self.url}/states",
headers={
"Authorization": "Bearer " + self.token
}
)
for entity in response.json():
if entity["entity_id"] == state:
return entity
return response
def send_process(self, command: str = "выключи телевизор") -> bool:
"""
Функция для отправки запроса о выполнении команды к api
:param command: str - команда в виде строки
:return: bool - удачная ли отправка запроса к api
"""
response = requests.post(
url=f"{self.url}/services/conversation/process",
json={"text": command},
headers={
"Authorization": "Bearer " + self.token,
"content-type": "application/json"
},
)
if response.status_code == 200:
return True
return False
def voice_to_name(self, voice: str) -> str:
"""
Функция для неточного сравнивания входной строки голоса
и списка устройств дял которых можно узнать информацию
:param voice: str - распознанная фраза без проверки по списку
:return: str - найденный объект для получения информации
"""
words = voice.lower().split()
best_match = None
highest_score = 0
for word in words:
result, score = process.extractOne(word, self.HA_CMD_LIST.keys())
if score > highest_score:
highest_score = score
best_match = result
return best_match
def validate_info(self, name: str) -> str:
"""
Функция для получения готовой строки информации entity по его имени.
Эта строка готова для произношения
:param name: str - имя entity для нахождения информации о нём
:return: str - готовая строка для найденного по имени объекта для её произношения
"""
answer = name
entity_config = self.HA_CMD_LIST.get(name)
if entity_config:
# Создание словаря, разделяя каждый элемент конфигурации на ключ и значение
entity_details = {item.split(':')[0]: item.split(':')[1] for item in entity_config}
entity_id = entity_details.pop("entity_id", "robot")
if entity_id:
responses = self.get_info(entity_id)
for attribute_path, label in entity_details.items():
response = responses
try:
for attribute in attribute_path.split("."):
response = response[attribute]
answer += f" {label} {response}"
except KeyError:
continue
return answer
Generated
+1 -917
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -26,8 +26,6 @@ torch = "^2.1.1+cpu"
torchaudio = "^2.1.1+cpu"
ollama = "^0.1.6"
ruff = "^0.4.2"
noisereduce = "^3.0.2"
environs = "^11.0.0"
[[tool.poetry.source]]
+67
View File
@@ -0,0 +1,67 @@
import asyncio
import re
import ollama
import yaml
def load_commands(file_path):
with open(file_path) as file:
data = yaml.safe_load(file)
answers = {}
command_mapping = {}
idx = 1
for category, items in data.items():
answers[idx] = items
command_mapping[idx] = category
idx += 1
return answers, command_mapping
async def chat(answers: dict[int, str], request: str = 'музычку пожалуйста') -> str:
answers_str = ''.join([f'{key}. {", ".join(value)}\n' for key, value in answers.items()])
messages = [
{
'role': 'system',
'content': 'Пожалуйста, просмотрите список доступных команд и '
'выберите подходящую команду, указав ее номер. '
'Вы можете выбрать одну команду или несколько команд одновременно. '
'В ответе укажите только номер или номера команд, '
'например: "1" или "1, 3, 5". Вот список доступных команд:\n' + answers_str
},
{
'role': 'user',
'content': request
}
]
async_client = ollama.AsyncClient()
response = await async_client.chat(
model='llama3:8b',
messages=messages,
options={
'temperature': 0.5,
'mirostat_tau': 100.0,
'repeat_last_n': 2,
'num_predict': 20
}
)
return response.get("message").get("content")
async def main():
answers, command_mapping = load_commands('commands.yaml')
number = None
while not number:
response_content = await chat(answers, "останови музыку и скажи погоду")
number = [
int(i)
for i in re.findall(r'\d+', response_content)
if int(i) in answers.keys()
]
command_names = [command_mapping[n] for n in number]
print(f"Выбранный номер: {command_names}")
if __name__ == '__main__':
ollama.pull("llama3:8b")
asyncio.run(main())
-37
View File
@@ -1,37 +0,0 @@
import os
import sys
from data import config
def install_vosk_model() -> None:
"""
Функция устанавливает заданную в конфигурационном файле модели
:return:
"""
try:
open('data/model_small/README')
except Exception as e:
print(e)
if sys.platform == "linux" or sys.platform == "linux2":
os.system(
f"wget https://alphacephei.com/vosk/models/{config.VOSK_MODEL_NAME}.zip"
)
os.system(f"unzip {config.VOSK_MODEL_NAME}.zip")
os.system(f"mv {config.VOSK_MODEL_NAME} data/model_small")
os.system(f"rm -rf {config.VOSK_MODEL_NAME}.zip")
elif sys.platform == "darwin":
os.system(
f"curl https://alphacephei.com/vosk/models/{config.VOSK_MODEL_NAME}.zip"
)
os.system(f"unzip {config.VOSK_MODEL_NAME}.zip")
os.system(f"mv {config.VOSK_MODEL_NAME} data/model_small")
os.system(f"rm -rf {config.VOSK_MODEL_NAME}.zip")
elif sys.platform == "win32":
os.system(
f"curl https://alphacephei.com/vosk/models/{config.VOSK_MODEL_NAME}.zip --output 1.zip"
)
os.system('powershell -command "Expand-Archive 1.zip ./"')
os.system(f"rename {config.VOSK_MODEL_NAME} data/model_small")
os.system("del /s /q 1.zip")
+1 -16
View File
@@ -1,13 +1,4 @@
def execute_cmd(self, cmd: str, recognized_phrase: str, voice: str) -> None:
"""
Функция выполняет полученные команды
:param self: modules.Jarvis - объект основного модуля
:param cmd: str - команда которую функция должна выполнить
:param recognized_phrase: str - распознанная фраза из списка фраз
:param voice: str - распознанная фраза без проверки по списку
:return:
"""
def execute_cmd(self, cmd: str):
if cmd == 'thanks':
self.play("thanks")
elif cmd == 'stupid':
@@ -16,9 +7,3 @@ def execute_cmd(self, cmd: str, recognized_phrase: str, voice: str) -> None:
self.play("off", True)
self.porcupine.delete()
exit(0)
elif cmd == 'home_assistant_execute':
self.home_assistant.send_process(recognized_phrase)
elif cmd == 'home_assistant_get':
entity_name = self.home_assistant.voice_to_name(voice)
entity_info = self.home_assistant.validate_info(entity_name)
print(entity_info)
+2 -10
View File
@@ -3,15 +3,7 @@ import random
import simpleaudio as sa
def play(self, phrase: str, wait_done: bool = True) -> None:
"""
Функция для запуска голосовой команды
:param self: modules.Jarvis - объект основного модуля
:param phrase: str - фраза для запуска голосовой команды
:param wait_done: bool - нужно-ли ждать окончания фразы
:return:
"""
def play(self, phrase, wait_done=True):
filename = None
file_array = ["not_found", "thanks", "run", "stupid", "ready", "off"]
if phrase == "greet":
@@ -23,7 +15,7 @@ def play(self, phrase: str, wait_done: bool = True) -> None:
if wait_done:
self.recorder.stop()
if filename:
wave_obj = sa.WaveObject.from_wave_file(f"{self.CDIR}/data/sound/{filename}")
wave_obj = sa.WaveObject.from_wave_file(f"{self.CDIR}/sound/{filename}")
play_obj = wave_obj.play()
if wait_done:
play_obj.wait_done()
Executable
+31
View File
@@ -0,0 +1,31 @@
import time
import sounddevice as sd
import torch
language = 'ru'
model_id = 'ru_v3'
sample_rate = 48000 # 48000
speaker = 'aidar' # aidar, baya, kseniya, xenia, random
put_accent = True
put_yo = True
device = torch.device('cpu') # cpu или gpu
text = "Хауди Хо, друзья!!!"
model, _ = torch.hub.load(repo_or_dir='snakers4/silero-models',
model='silero_tts',
language=language,
speaker=model_id)
model.to(device)
def va_speak(what: str):
audio = model.apply_tts(text=what + "..",
speaker=speaker,
sample_rate=sample_rate,
put_accent=put_accent,
put_yo=put_yo)
sd.play(audio, sample_rate * 1.05)
time.sleep((len(audio) / sample_rate) + 0.5)
sd.stop()
-67
View File
@@ -1,67 +0,0 @@
import asyncio
import re
import ollama
import yaml
def load_commands(file_path):
with open(file_path) as file:
data = yaml.safe_load(file)
answers = {}
command_mapping = {}
idx = 1
for category, items in data.items():
answers[idx] = items
command_mapping[idx] = category
idx += 1
return answers, command_mapping
async def chat(answers: dict[int, str], request: str = 'музычку пожалуйста') -> str:
answers_str = ''.join([f'{key}. {", ".join(value)}\n' for key, value in answers.items()])
messages = [
{
'role': 'system',
'content': 'Пожалуйста, просмотрите список доступных команд и '
'выберите подходящую команду, указав ее номер. '
'Вы можете выбрать одну команду или несколько команд одновременно. '
'В ответе укажите только номер или номера команд, '
'например: "1" или "1, 3, 5". Вот список доступных команд:\n' + answers_str
},
{
'role': 'user',
'content': request
}
]
async_client = ollama.AsyncClient()
response = await async_client.chat(
model='llama3:8b',
messages=messages,
options={
'temperature': 0.5,
'mirostat_tau': 100.0,
'repeat_last_n': 2,
'num_predict': 20
}
)
return response.get("message").get("content")
async def main():
answers, command_mapping = load_commands('commands.yaml')
number = None
while not number:
response_content = await chat(answers, "останови музыку и скажи погоду")
number = [
int(i)
for i in re.findall(r'\d+', response_content)
if int(i) in answers.keys()
]
return [command_mapping[n] for n in number]
if __name__ == '__main__':
ollama.pull("llama3:8b")
command_names = asyncio.run(main())
print(f"Выбранный номер: {command_names}")