Files
smart-speaker/app/core/ai.py

185 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""AI module for Perplexity API integration."""
# Модуль общения с искусственным интеллектом (Perplexity API).
# Обрабатывает запросы пользователя и переводы.
import requests
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 only the translated text, without quotes, comments, or explanations."""
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=400,
temperature=0.2, # Низкая температура для точности перевода
error_text="Произошла ошибка при переводе. Попробуйте ещё раз.",
)
return response.strip()