Optimize hot paths and reuse HTTP sessions
This commit is contained in:
@@ -7,6 +7,7 @@ import requests
|
|||||||
import re
|
import re
|
||||||
from .config import PERPLEXITY_API_KEY, PERPLEXITY_MODEL, PERPLEXITY_API_URL
|
from .config import PERPLEXITY_API_KEY, PERPLEXITY_MODEL, PERPLEXITY_API_URL
|
||||||
|
|
||||||
|
_HTTP = requests.Session()
|
||||||
|
|
||||||
# Системный промпт (инструкция) для AI.
|
# Системный промпт (инструкция) для AI.
|
||||||
# Задает личность ассистента: имя "Александр", стиль общения, краткость.
|
# Задает личность ассистента: имя "Александр", стиль общения, краткость.
|
||||||
@@ -53,7 +54,7 @@ def _send_request(messages, max_tokens, temperature, error_text):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = _HTTP.post(
|
||||||
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15 # Уменьшаем таймаут
|
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15 # Уменьшаем таймаут
|
||||||
)
|
)
|
||||||
response.raise_for_status() # Проверка на ошибки HTTP (4xx, 5xx)
|
response.raise_for_status() # Проверка на ошибки HTTP (4xx, 5xx)
|
||||||
@@ -134,16 +135,16 @@ def ask_ai_stream(messages_history: list):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = _HTTP.post(
|
||||||
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15, stream=True # Уменьшаем таймаут
|
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15, stream=True # Уменьшаем таймаут
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
for line in response.iter_lines():
|
for line in response.iter_lines(decode_unicode=True):
|
||||||
if line:
|
if line:
|
||||||
line_text = line.decode("utf-8")
|
line_text = line
|
||||||
if line_text.startswith("data: "):
|
if line_text.startswith("data: "):
|
||||||
data_str = line_text[6:] # Skip "data: "
|
data_str = line_text[6:] # Skip "data: "
|
||||||
if data_str == "[DONE]":
|
if data_str == "[DONE]":
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import requests
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from ..core.config import WEATHER_LAT, WEATHER_LON, WEATHER_CITY
|
from ..core.config import WEATHER_LAT, WEATHER_LON, WEATHER_CITY
|
||||||
|
|
||||||
|
_HTTP = requests.Session()
|
||||||
def get_wmo_description(code: int) -> str:
|
def get_wmo_description(code: int) -> str:
|
||||||
"""Decodes WMO weather code to Russian description."""
|
"""Decodes WMO weather code to Russian description."""
|
||||||
codes = {
|
codes = {
|
||||||
@@ -348,7 +349,7 @@ def get_coordinates_by_city(city_name: str) -> tuple:
|
|||||||
"format": "json"
|
"format": "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(geocode_url, params=params, timeout=10)
|
response = _HTTP.get(geocode_url, params=params, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
@@ -403,7 +404,7 @@ def get_weather_report(requested_city: str = None) -> str:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, params=params, timeout=10)
|
response = _HTTP.get(url, params=params, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
|||||||
199
app/main.py
199
app/main.py
@@ -58,36 +58,7 @@ from .features.weather import get_weather_report
|
|||||||
from .features.music import get_music_controller
|
from .features.music import get_music_controller
|
||||||
from .features.cities_game import get_cities_game
|
from .features.cities_game import get_cities_game
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
_TRANSLATION_COMMANDS = [
|
||||||
"""
|
|
||||||
Обработчик сигнала Ctrl+C.
|
|
||||||
Позволяет корректно завершить работу программы, освободив ресурсы (микрофон, модели).
|
|
||||||
"""
|
|
||||||
print("\n\n👋 Завершение работы...")
|
|
||||||
try:
|
|
||||||
cleanup_wakeword() # Остановка Porcupine
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка при остановке wakeword: {e}")
|
|
||||||
try:
|
|
||||||
cleanup_stt() # Остановка Deepgram
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка при остановке STT: {e}")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_translation_request(text: str):
|
|
||||||
"""
|
|
||||||
Определяет, является ли фраза запросом на перевод.
|
|
||||||
|
|
||||||
Пример: "Переведи на английский привет мир"
|
|
||||||
Возвращает словарь: {'source_lang': 'ru', 'target_lang': 'en', 'text': 'привет мир'}
|
|
||||||
Или None, если это не запрос перевода.
|
|
||||||
"""
|
|
||||||
text_lower = text.lower().strip()
|
|
||||||
# Список префиксов команд перевода и соответствующих направлений языков.
|
|
||||||
# Важно: более длинные префиксы должны проверяться первыми (например,
|
|
||||||
# "переведи с русского на английский" не должен схватиться как "переведи с русского").
|
|
||||||
commands = [
|
|
||||||
("переведи на английский с русского", "ru", "en"),
|
("переведи на английский с русского", "ru", "en"),
|
||||||
("переведи на русский с английского", "en", "ru"),
|
("переведи на русский с английского", "en", "ru"),
|
||||||
("переведи на английский язык с русского", "ru", "en"),
|
("переведи на английский язык с русского", "ru", "en"),
|
||||||
@@ -119,10 +90,118 @@ def parse_translation_request(text: str):
|
|||||||
("translate from english", "en", "ru"),
|
("translate from english", "en", "ru"),
|
||||||
("translate from russian", "ru", "en"),
|
("translate from russian", "ru", "en"),
|
||||||
]
|
]
|
||||||
|
_TRANSLATION_COMMANDS_SORTED = sorted(
|
||||||
|
_TRANSLATION_COMMANDS, key=lambda item: len(item[0]), reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
for prefix, source_lang, target_lang in sorted(
|
_REPEAT_PHRASES = {
|
||||||
commands, key=lambda item: len(item[0]), reverse=True
|
"еще раз",
|
||||||
):
|
"повтори",
|
||||||
|
"скажи еще раз",
|
||||||
|
"что ты сказал",
|
||||||
|
"повтори пожалуйста",
|
||||||
|
"александр еще раз",
|
||||||
|
"еще раз александр",
|
||||||
|
"александр повтори",
|
||||||
|
"повтори александр",
|
||||||
|
}
|
||||||
|
|
||||||
|
_WEATHER_TRIGGERS = (
|
||||||
|
"погода",
|
||||||
|
"погоду",
|
||||||
|
"что на улице",
|
||||||
|
"какая температура",
|
||||||
|
"сколько градусов",
|
||||||
|
"холодно ли",
|
||||||
|
"жарко ли",
|
||||||
|
"нужен ли зонт",
|
||||||
|
"брать ли зонт",
|
||||||
|
"прогноз погоды",
|
||||||
|
"че там на улице",
|
||||||
|
"что там на улице",
|
||||||
|
"как на улице",
|
||||||
|
"как на улице-то",
|
||||||
|
)
|
||||||
|
|
||||||
|
_CITY_INVALID_WORDS = {
|
||||||
|
"этом",
|
||||||
|
"том",
|
||||||
|
"той",
|
||||||
|
"тут",
|
||||||
|
"здесь",
|
||||||
|
"там",
|
||||||
|
"всё",
|
||||||
|
"все",
|
||||||
|
"всей",
|
||||||
|
"всего",
|
||||||
|
"всем",
|
||||||
|
"всеми",
|
||||||
|
"городе",
|
||||||
|
"город",
|
||||||
|
"село",
|
||||||
|
"деревня",
|
||||||
|
"посёлок",
|
||||||
|
"аул",
|
||||||
|
"станция",
|
||||||
|
"область",
|
||||||
|
"район",
|
||||||
|
"край",
|
||||||
|
"республика",
|
||||||
|
}
|
||||||
|
|
||||||
|
_CITY_PATTERNS = [
|
||||||
|
re.compile(
|
||||||
|
r"в\s+городе\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
r"в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
r"погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
r"погода\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)\s+(?:какая|сейчас|там)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
r"(?:какая|как)\s+погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
"""
|
||||||
|
Обработчик сигнала Ctrl+C.
|
||||||
|
Позволяет корректно завершить работу программы, освободив ресурсы (микрофон, модели).
|
||||||
|
"""
|
||||||
|
print("\n\n👋 Завершение работы...")
|
||||||
|
try:
|
||||||
|
cleanup_wakeword() # Остановка Porcupine
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при остановке wakeword: {e}")
|
||||||
|
try:
|
||||||
|
cleanup_stt() # Остановка Deepgram
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при остановке STT: {e}")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_translation_request(text: str):
|
||||||
|
"""
|
||||||
|
Определяет, является ли фраза запросом на перевод.
|
||||||
|
|
||||||
|
Пример: "Переведи на английский привет мир"
|
||||||
|
Возвращает словарь: {'source_lang': 'ru', 'target_lang': 'en', 'text': 'привет мир'}
|
||||||
|
Или None, если это не запрос перевода.
|
||||||
|
"""
|
||||||
|
text_lower = text.lower().strip()
|
||||||
|
# Список префиксов команд перевода и соответствующих направлений языков.
|
||||||
|
# Важно: более длинные префиксы должны проверяться первыми (например,
|
||||||
|
# "переведи с русского на английский" не должен схватиться как "переведи с русского").
|
||||||
|
for prefix, source_lang, target_lang in _TRANSLATION_COMMANDS_SORTED:
|
||||||
if text_lower.startswith(prefix):
|
if text_lower.startswith(prefix):
|
||||||
# Отрезаем команду (префикс), оставляем только текст для перевода
|
# Отрезаем команду (префикс), оставляем только текст для перевода
|
||||||
rest = text[len(prefix) :].strip()
|
rest = text[len(prefix) :].strip()
|
||||||
@@ -284,19 +363,8 @@ def main():
|
|||||||
|
|
||||||
# Проверка на команду "Повтори" / "Еще раз"
|
# Проверка на команду "Повтори" / "Еще раз"
|
||||||
user_text_lower = user_text.lower().strip()
|
user_text_lower = user_text.lower().strip()
|
||||||
repeat_phrases = [
|
|
||||||
"еще раз",
|
|
||||||
"повтори",
|
|
||||||
"скажи еще раз",
|
|
||||||
"что ты сказал",
|
|
||||||
"повтори пожалуйста",
|
|
||||||
"александр еще раз",
|
|
||||||
"еще раз александр",
|
|
||||||
"александр повтори",
|
|
||||||
"повтори александр",
|
|
||||||
]
|
|
||||||
# Проверяем точное совпадение или если фраза начинается с "повтори" (но не "повтори за мной")
|
# Проверяем точное совпадение или если фраза начинается с "повтори" (но не "повтори за мной")
|
||||||
if user_text_lower in repeat_phrases or (
|
if user_text_lower in _REPEAT_PHRASES or (
|
||||||
user_text_lower.startswith("повтори")
|
user_text_lower.startswith("повтори")
|
||||||
and "за мной" not in user_text_lower
|
and "за мной" not in user_text_lower
|
||||||
):
|
):
|
||||||
@@ -358,49 +426,28 @@ def main():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Проверка команды "Погода"
|
# Проверка команды "Погода"
|
||||||
weather_triggers = [
|
|
||||||
"погода",
|
|
||||||
"погоду",
|
|
||||||
"что на улице",
|
|
||||||
"какая температура",
|
|
||||||
"сколько градусов",
|
|
||||||
"холодно ли",
|
|
||||||
"жарко ли",
|
|
||||||
"нужен ли зонт",
|
|
||||||
"брать ли зонт",
|
|
||||||
"прогноз погоды",
|
|
||||||
"че там на улице",
|
|
||||||
"что там на улице",
|
|
||||||
"как на улице",
|
|
||||||
"как на улице-то",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Проверяем, содержит ли запрос информацию о конкретном городе
|
# Проверяем, содержит ли запрос информацию о конкретном городе
|
||||||
requested_city = None
|
requested_city = None
|
||||||
user_text_lower = user_text.lower()
|
user_text_lower = user_text.lower()
|
||||||
|
|
||||||
# Проверяем наличие упоминания города в запросе (например, "погода в Нью-Йорке", "какая погода в Москве")
|
# Проверяем наличие упоминания города в запросе (например, "погода в Нью-Йорке", "какая погода в Москве")
|
||||||
import re
|
for pattern in _CITY_PATTERNS:
|
||||||
city_patterns = [
|
match = pattern.search(user_text_lower)
|
||||||
r"в\s+городе\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "в городе Волгоград"
|
|
||||||
r"в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "в Нью-Йорке" - улучшенный паттерн для составных названий
|
|
||||||
r"погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "погода в Москве" - улучшенный паттерн
|
|
||||||
r"погода\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)\s+(?:какая|сейчас|там)", # "погода Москва какая"
|
|
||||||
r"(?:какая|как)\s+погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "какая погода в Москве"
|
|
||||||
]
|
|
||||||
|
|
||||||
for pattern in city_patterns:
|
|
||||||
match = re.search(pattern, user_text_lower, re.IGNORECASE)
|
|
||||||
if match:
|
if match:
|
||||||
potential_city = match.group(1).strip()
|
potential_city = match.group(1).strip()
|
||||||
# Проверяем, что это не местоимение или другое слово, а реально название города
|
# Проверяем, что это не местоимение или другое слово, а реально название города
|
||||||
invalid_words = ["этом", "том", "той", "тут", "здесь", "там", "всё", "все", "всей", "всего", "всем", "всеми", "городе", "город", "село", "деревня", "посёлок", "аул", "станция", "область", "район", "край", "республика"]
|
if (
|
||||||
if potential_city and len(potential_city) > 1 and not any(word in potential_city for word in invalid_words):
|
potential_city
|
||||||
|
and len(potential_city) > 1
|
||||||
|
and not any(word in potential_city for word in _CITY_INVALID_WORDS)
|
||||||
|
):
|
||||||
requested_city = potential_city.title() # Приводим к формату "Нью-Йорк", "Москва"
|
requested_city = potential_city.title() # Приводим к формату "Нью-Йорк", "Москва"
|
||||||
break
|
break
|
||||||
|
|
||||||
# Проверяем, содержит ли запрос одну из погодных команд
|
# Проверяем, содержит ли запрос одну из погодных команд
|
||||||
has_weather_trigger = any(trigger in user_text_lower for trigger in weather_triggers)
|
has_weather_trigger = any(
|
||||||
|
trigger in user_text_lower for trigger in _WEATHER_TRIGGERS
|
||||||
|
)
|
||||||
|
|
||||||
if has_weather_trigger:
|
if has_weather_trigger:
|
||||||
from .features.weather import get_weather_report
|
from .features.weather import get_weather_report
|
||||||
|
|||||||
Reference in New Issue
Block a user