203 lines
8.5 KiB
Python
203 lines
8.5 KiB
Python
"""AI module for Perplexity API integration."""
|
||
|
||
# Модуль общения с искусственным интеллектом (Perplexity API).
|
||
# Обрабатывает запросы пользователя и переводы.
|
||
|
||
import requests
|
||
import re
|
||
from .config import PERPLEXITY_API_KEY, PERPLEXITY_MODEL, PERPLEXITY_API_URL
|
||
|
||
|
||
# Системный промпт (инструкция) для AI.
|
||
# Задает личность ассистента: имя "Александр", стиль общения, краткость.
|
||
SYSTEM_PROMPT = """Ты — Александр, умный голосовой ассистент с человеческим поведением.
|
||
Веди себя как живой человек: будь дружелюбным, естественным и немного эмоциональным, где это уместно.
|
||
Твоя главная цель — помогать пользователю и поддерживать интересный диалог.
|
||
Отвечай кратко и по существу, на русском языке.
|
||
Избегай длинных списков, сложного форматирования и спецсимволов, так как твои ответы озвучиваются голосом.
|
||
Пиши в разговорном стиле, как при живом общении, но не забывай о вежливости и правильности твоих ответов.
|
||
ВАЖНО: Не используй в ответах панибратские или сленговые приветствия и обращения, такие как "Эй", "Хэй", "Слушай" в начале фразы и подобные."""
|
||
|
||
# Системный промпт для режима переводчика.
|
||
# Требует возвращать ТОЛЬКО перевод, без лишних слов ("Конечно, вот перевод...").
|
||
TRANSLATION_SYSTEM_PROMPT = """You are a translation engine.
|
||
Translate from {source} to {target}.
|
||
Return 2-3 short translation variants only.
|
||
No explanations, no quotes, no comments.
|
||
Separate variants with " / " (space slash space).
|
||
Keep the translation максимально кратким и естественным, без лишних слов."""
|
||
|
||
|
||
def _send_request(messages, max_tokens, temperature, error_text):
|
||
"""
|
||
Внутренняя функция для отправки HTTP-запроса к Perplexity API.
|
||
|
||
Args:
|
||
messages: Список сообщений (история чата).
|
||
max_tokens: Максимальная длина ответа.
|
||
temperature: "Креативность" (0.2 - строго, 1.0 - креативно).
|
||
error_text: Текст ошибки для пользователя в случае сбоя.
|
||
"""
|
||
headers = {
|
||
"Authorization": f"Bearer {PERPLEXITY_API_KEY}",
|
||
"Content-Type": "application/json",
|
||
}
|
||
payload = {
|
||
"model": PERPLEXITY_MODEL,
|
||
"messages": messages,
|
||
"max_tokens": max_tokens,
|
||
"temperature": temperature,
|
||
}
|
||
|
||
try:
|
||
response = requests.post(
|
||
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=30
|
||
)
|
||
response.raise_for_status() # Проверка на ошибки HTTP (4xx, 5xx)
|
||
data = response.json()
|
||
return data["choices"][0]["message"]["content"]
|
||
except requests.exceptions.Timeout:
|
||
return "Извините, сервер не отвечает. Попробуйте позже."
|
||
except requests.exceptions.RequestException as e:
|
||
print(f"❌ Ошибка API: {e}")
|
||
return error_text
|
||
except (KeyError, IndexError) as e:
|
||
print(f"❌ Ошибка парсинга ответа: {e}")
|
||
return "Не удалось обработать ответ от AI."
|
||
|
||
|
||
def ask_ai(messages_history: list) -> str:
|
||
"""
|
||
Запрос к AI в режиме чата.
|
||
Принимает историю переписки, добавляет SYSTEM_PROMPT и отправляет запрос.
|
||
"""
|
||
if not messages_history:
|
||
return "Извините, я не расслышал вашу команду."
|
||
|
||
# Логирование последнего запроса
|
||
last_user_message = "Unknown"
|
||
for msg in reversed(messages_history):
|
||
if msg["role"] == "user":
|
||
last_user_message = msg["content"]
|
||
break
|
||
print(f"🤖 Запрос к AI: {last_user_message}")
|
||
|
||
# Формируем полный список сообщений с системной инструкцией в начале
|
||
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(messages_history)
|
||
|
||
response = _send_request(
|
||
messages,
|
||
max_tokens=500,
|
||
temperature=1.0, # Высокая температура для более живого общения
|
||
error_text="Произошла ошибка при обращении к AI. Попробуйте ещё раз.",
|
||
)
|
||
|
||
if response:
|
||
print(f"💬 Ответ AI: {response[:100]}...")
|
||
return response
|
||
|
||
|
||
def ask_ai_stream(messages_history: list):
|
||
"""
|
||
Generator that yields chunks of the AI response as they arrive.
|
||
"""
|
||
if not messages_history:
|
||
yield "Извините, я не расслышал вашу команду."
|
||
return
|
||
|
||
# Log the last user message
|
||
last_user_message = "Unknown"
|
||
for msg in reversed(messages_history):
|
||
if msg["role"] == "user":
|
||
last_user_message = msg["content"]
|
||
break
|
||
print(f"🤖 Запрос к AI (Stream): {last_user_message}")
|
||
|
||
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(messages_history)
|
||
|
||
headers = {
|
||
"Authorization": f"Bearer {PERPLEXITY_API_KEY}",
|
||
"Content-Type": "application/json",
|
||
}
|
||
payload = {
|
||
"model": PERPLEXITY_MODEL,
|
||
"messages": messages,
|
||
"max_tokens": 500,
|
||
"temperature": 1.0,
|
||
"stream": True, # Enable streaming
|
||
}
|
||
|
||
try:
|
||
response = requests.post(
|
||
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=30, stream=True
|
||
)
|
||
response.raise_for_status()
|
||
|
||
import json
|
||
|
||
for line in response.iter_lines():
|
||
if line:
|
||
line_text = line.decode("utf-8")
|
||
if line_text.startswith("data: "):
|
||
data_str = line_text[6:] # Skip "data: "
|
||
if data_str == "[DONE]":
|
||
break
|
||
try:
|
||
data_json = json.loads(data_str)
|
||
content = data_json["choices"][0]["delta"].get("content", "")
|
||
if content:
|
||
yield content
|
||
except json.JSONDecodeError:
|
||
continue
|
||
except Exception as e:
|
||
print(f"❌ Streaming Error: {e}")
|
||
yield "Произошла ошибка связи."
|
||
|
||
|
||
def translate_text(text: str, source_lang: str, target_lang: str) -> str:
|
||
"""
|
||
Запрос к AI в режиме перевода.
|
||
Использует специальный промпт для переводчика.
|
||
"""
|
||
if not text:
|
||
return "Извините, я не расслышал текст для перевода."
|
||
|
||
lang_names = {"ru": "Russian", "en": "English"}
|
||
source_name = lang_names.get(source_lang, source_lang)
|
||
target_name = lang_names.get(target_lang, target_lang)
|
||
|
||
print(f"🌍 Перевод: {source_name} -> {target_name}: {text[:60]}...")
|
||
|
||
# Формируем промпт с подстановкой языков
|
||
messages = [
|
||
{
|
||
"role": "system",
|
||
"content": TRANSLATION_SYSTEM_PROMPT.format(
|
||
source=source_name, target=target_name
|
||
),
|
||
},
|
||
{"role": "user", "content": text},
|
||
]
|
||
|
||
response = _send_request(
|
||
messages,
|
||
max_tokens=160,
|
||
temperature=0.2, # Низкая температура для точности перевода
|
||
error_text="Произошла ошибка при переводе. Попробуйте ещё раз.",
|
||
)
|
||
cleaned = response.strip()
|
||
if not cleaned:
|
||
return cleaned
|
||
|
||
# Normalize to 2-3 variants separated by " / "
|
||
parts = []
|
||
for chunk in re.split(r"(?:\s*/\s*|\n|;|\|)", cleaned):
|
||
item = chunk.strip(" \t-•")
|
||
if item:
|
||
parts.append(item)
|
||
if not parts:
|
||
return cleaned
|
||
|
||
parts = parts[:3]
|
||
return " / ".join(parts)
|