Optimize hot paths and reuse HTTP sessions

This commit is contained in:
2026-02-02 23:45:51 +03:00
parent 38b95e0de6
commit 3caa099232
3 changed files with 134 additions and 85 deletions

View File

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

View File

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

View File

@@ -58,6 +58,120 @@ 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
_TRANSLATION_COMMANDS = [
("переведи на английский с русского", "ru", "en"),
("переведи на русский с английского", "en", "ru"),
("переведи на английский язык с русского", "ru", "en"),
("переведи на русский язык с английского", "en", "ru"),
("переведи с русского на английский", "ru", "en"),
("переведи с русского в английский", "ru", "en"),
("переведи с английского на русский", "en", "ru"),
("переведи с английского в русский", "en", "ru"),
("переведи с русского языка", "ru", "en"),
("переведи с английского языка", "en", "ru"),
("переведи на английский язык", "ru", "en"),
("переведи на русский язык", "en", "ru"),
("переведи на английский", "ru", "en"),
("переведи на русский", "en", "ru"),
("переведи с английского", "en", "ru"),
("переведи с русского", "ru", "en"),
("как по-английски", "ru", "en"),
("как по английски", "ru", "en"),
("как по-русски", "en", "ru"),
("как по русски", "en", "ru"),
("translate to english from russian", "ru", "en"),
("translate to russian from english", "en", "ru"),
("translate from russian to english", "ru", "en"),
("translate from english to russian", "en", "ru"),
("translate into english", "ru", "en"),
("translate into russian", "en", "ru"),
("translate to english", "ru", "en"),
("translate to russian", "en", "ru"),
("translate from english", "en", "ru"),
("translate from russian", "ru", "en"),
]
_TRANSLATION_COMMANDS_SORTED = sorted(
_TRANSLATION_COMMANDS, key=lambda item: len(item[0]), reverse=True
)
_REPEAT_PHRASES = {
"еще раз",
"повтори",
"скажи еще раз",
"что ты сказал",
"повтори пожалуйста",
"александр еще раз",
"еще раз александр",
"александр повтори",
"повтори александр",
}
_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): def signal_handler(sig, frame):
""" """
Обработчик сигнала Ctrl+C. Обработчик сигнала Ctrl+C.
@@ -87,42 +201,7 @@ def parse_translation_request(text: str):
# Список префиксов команд перевода и соответствующих направлений языков. # Список префиксов команд перевода и соответствующих направлений языков.
# Важно: более длинные префиксы должны проверяться первыми (например, # Важно: более длинные префиксы должны проверяться первыми (например,
# "переведи с русского на английский" не должен схватиться как "переведи с русского"). # "переведи с русского на английский" не должен схватиться как "переведи с русского").
commands = [ for prefix, source_lang, target_lang in _TRANSLATION_COMMANDS_SORTED:
("переведи на английский с русского", "ru", "en"),
("переведи на русский с английского", "en", "ru"),
("переведи на английский язык с русского", "ru", "en"),
("переведи на русский язык с английского", "en", "ru"),
("переведи с русского на английский", "ru", "en"),
("переведи с русского в английский", "ru", "en"),
("переведи с английского на русский", "en", "ru"),
("переведи с английского в русский", "en", "ru"),
("переведи с русского языка", "ru", "en"),
("переведи с английского языка", "en", "ru"),
("переведи на английский язык", "ru", "en"),
("переведи на русский язык", "en", "ru"),
("переведи на английский", "ru", "en"),
("переведи на русский", "en", "ru"),
("переведи с английского", "en", "ru"),
("переведи с русского", "ru", "en"),
("как по-английски", "ru", "en"),
("как по английски", "ru", "en"),
("как по-русски", "en", "ru"),
("как по русски", "en", "ru"),
("translate to english from russian", "ru", "en"),
("translate to russian from english", "en", "ru"),
("translate from russian to english", "ru", "en"),
("translate from english to russian", "en", "ru"),
("translate into english", "ru", "en"),
("translate into russian", "en", "ru"),
("translate to english", "ru", "en"),
("translate to russian", "en", "ru"),
("translate from english", "en", "ru"),
("translate from russian", "ru", "en"),
]
for prefix, source_lang, target_lang in sorted(
commands, key=lambda item: len(item[0]), reverse=True
):
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