внедрение яндекса AI

main
pilot 4 days ago
parent d1e70731f2
commit 13825d51d6

@ -4,3 +4,4 @@ mysqlclient
qrcode
django-environ
pillow
yandex-ai-studio-sdk

@ -5,63 +5,38 @@ import requests
from typing import Dict, Any, List
from max_bot.models import Client, ProductCategory, Product, Qr
# Хранилище контекстов (можно расширить для хранения состояния бронирования)
# Хранилище контекстов
_contexts: Dict[str, List[Dict[str, str]]] = {}
# Кешируем базовый промт, чтобы не читать файл при каждом запросе
_BASE_PROMPT_CACHE = None
def _log_dialogue(session_id: str, role: str, content: str, log_file: str = None) -> None:
"""
Записывает сообщение диалога в файл лога.
Аргументы:
session_id: идентификатор сессии (например, chat_id)
role: 'SYSTEM', 'USER', 'ASSISTANT'
content: текст сообщения
log_file: путь к файлу лога (если None, берётся из настроек или по умолчанию)
"""
if log_file is None:
# Путь к файлу лога: можно задать в settings или использовать фиксированный путь
# Здесь используем папку logs рядом с файлом api_deepseek.py
base_dir = os.path.dirname(os.path.abspath(__file__))
log_dir = os.path.join(base_dir, 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'deepseek_dialogs.log')
# Формируем строку лога с временной меткой
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# Для удобства чтения обрезаем content, если он слишком длинный? Нет, оставляем полным.
log_entry = f"[{timestamp}] [{role.upper()}] [{session_id}]: {content}\n"
# Записываем в файл (атомарно, с блокировкой? Для простоты используем обычное добавление)
with open(log_file, 'a', encoding='utf-8') as f:
f.write(log_entry)
# Если это системное сообщение или начало сессии, добавим разделитель для наглядности
if role.upper() == 'SYSTEM':
with open(log_file, 'a', encoding='utf-8') as f:
f.write('-' * 80 + '\n')
# Записываем в файл (атомарно, с блокировкой? Для простоты используем обычное добавление)
with open(log_file, 'a', encoding='utf-8') as f:
f.write(log_entry)
# Если это системное сообщение или начало сессии, добавим разделитель для наглядности
# Если это системное сообщение, добавляем разделитель для наглядности
if role.upper() == 'SYSTEM':
with open(log_file, 'a', encoding='utf-8') as f:
f.write('-' * 80 + '\n')
def _load_base_prompt() -> str:
"""
Загружает базовый текст системного промта из файла.
Если файл не найден, возвращает встроенный fallback.
"""
global _BASE_PROMPT_CACHE
if _BASE_PROMPT_CACHE is not None:
return _BASE_PROMPT_CACHE
# Определяем путь к файлу (он лежит рядом с api_deepseek.py)
base_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(base_dir, 'system_prompt_base.txt')
@ -69,27 +44,18 @@ def _load_base_prompt() -> str:
with open(file_path, 'r', encoding='utf-8') as f:
_BASE_PROMPT_CACHE = f.read()
except FileNotFoundError:
# Fallback: используем встроенный текст (минимальный)
_BASE_PROMPT_CACHE = (
"Ты — ассистент ресторана. Отвечай на вопросы и помогай с бронированием. "
"Возвращай JSON с полями intent, message, entities, status.\n"
"Информация о ресторане и меню:\n"
)
# Также можно залогировать ошибку
print("WARNING: system_prompt_base.txt not found, using fallback.")
return _BASE_PROMPT_CACHE
def get_system_prompt(client: Client) -> str:
"""
Генерирует системный промпт на основе данных клиента.
Базовый текст загружается из файла, затем добавляются ресторан и меню.
"""
# 1. Загружаем базовый промт
prompt = _load_base_prompt()
# 2. Добавляем информацию о ресторане
qr = Qr.objects.filter(client=client).first()
if qr:
restaurant_info = f"""
@ -105,8 +71,7 @@ def get_system_prompt(client: Client) -> str:
else:
prompt += "\nИнформация о ресторане отсутствует.\n"
# 3. Добавляем меню
categories = ProductCategory.objects.filter(client=client, status=True).order_by('ord')
categories = ProductCategory.objects.filter(client=client, status=True, src=2).order_by('ord')
if categories.exists():
prompt += "\nМеню:\n"
for cat in categories:
@ -123,7 +88,6 @@ def get_system_prompt(client: Client) -> str:
else:
prompt += "\nМеню не загружено.\n"
# 4. Добавляем финальную инструкцию (если она не в файле)
prompt += """
При ответах на вопросы о меню используй только те блюда, которые перечислены выше.
Если клиент спрашивает о блюде, которого нет в списке, честно скажи об этом и предложи посмотреть другие позиции.
@ -144,16 +108,16 @@ def deepseek_chat_structured(
"""
Отправляет запрос к DeepSeek и возвращает структурированный ответ (JSON).
"""
# Логируем системный промт только при создании новой сессии
# Инициализация сессии
if session_id not in _contexts:
_contexts[session_id] = [
{"role": "system", "content": system_prompt}
]
# Логируем системный промт при первом обращении
_log_dialogue(session_id, 'SYSTEM', system_prompt)
# Логируем запрос пользователя
_log_dialogue(session_id, 'USER', user_query)
_contexts[session_id].append({"role": "user", "content": user_query})
url = "https://api.deepseek.com/v1/chat/completions"
@ -176,9 +140,7 @@ def deepseek_chat_structured(
data = response.json()
assistant_content = data["choices"][0]["message"]["content"]
# Логируем ответ ассистента
_log_dialogue(session_id, 'ASSISTANT', assistant_content)
_contexts[session_id].append({"role": "assistant", "content": assistant_content})
# Парсим JSON
@ -202,9 +164,29 @@ def deepseek_chat_structured(
"status": "complete"
}
except requests.exceptions.HTTPError as e:
# Обработка ошибки 402 Payment Required
if e.response.status_code == 402:
user_message = "⚠️ К сожалению, я сейчас не могу отвечать на эти вопросы, попробуйте позже."
_log_dialogue(session_id, 'ERROR', f"Payment Required (402): {e}")
return {
"intent": "general",
"message": user_message,
"entities": {},
"status": "complete"
}
else:
error_msg = f"⚠️ Произошла ошибка при обращении к серверу. Попробуйте позже."
_log_dialogue(session_id, 'ERROR', f"HTTP error: {e}")
return {
"intent": "general",
"message": error_msg,
"entities": {},
"status": "complete"
}
except Exception as e:
error_msg = f"⚠️ Извините, произошла ошибка. Попробуйте позже."
# Логируем ошибку
error_msg = "⚠️ Извините, произошла ошибка. Попробуйте позже."
_log_dialogue(session_id, 'ERROR', f"DeepSeek error: {e}")
return {
"intent": "general",

@ -0,0 +1,200 @@
# import openai
#
# client = openai.OpenAI(
# api_key="AQVNxNGDP4KUYdTvjE_s2YBGugv5RFehgZKBlJRu",
# base_url="https://ai.api.cloud.yandex.net/v1",
# project="b1g8dec1sf09i9ohn7bl"
# )
#
# response = client.responses.create(
# prompt={
# "id": "fvtitb2k94c13nthvitn",
# },
# input="some message",
# )
#
# print(response.output_text)
import os
import json
from datetime import datetime
from typing import Dict, Any, List, Optional
from yandex_ai_studio_sdk import AIStudio
from yandex_ai_studio_sdk.auth import APIKeyAuth
from max_bot.models import Client, ProductCategory, Product, Qr
# Хранилище контекстов для диалогов
_contexts: Dict[str, List[Dict[str, str]]] = {}
_BASE_PROMPT_CACHE = None
def _log_dialogue(session_id: str, role: str, content: str, log_file: str = None) -> None:
"""
Записывает сообщение диалога в файл лога.
"""
if log_file is None:
base_dir = os.path.dirname(os.path.abspath(__file__))
log_dir = os.path.join(base_dir, 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'yandex_dialogs.log')
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_entry = f"[{timestamp}] [{role.upper()}] [{session_id}]: {content}\n"
with open(log_file, 'a', encoding='utf-8') as f:
f.write(log_entry)
if role.upper() == 'SYSTEM':
with open(log_file, 'a', encoding='utf-8') as f:
f.write('-' * 80 + '\n')
def _load_base_prompt() -> str:
"""Загружает базовый текст системного промта из файла."""
global _BASE_PROMPT_CACHE
if _BASE_PROMPT_CACHE is not None:
return _BASE_PROMPT_CACHE
base_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(base_dir, 'system_prompt_base.txt')
try:
with open(file_path, 'r', encoding='utf-8') as f:
_BASE_PROMPT_CACHE = f.read()
except FileNotFoundError:
_BASE_PROMPT_CACHE = (
"Ты — ассистент ресторана. Отвечай на вопросы и помогай с бронированием. "
"Возвращай JSON с полями intent, message, entities, status.\n"
"Информация о ресторане и меню:\n"
)
print("WARNING: system_prompt_base.txt not found, using fallback.")
return _BASE_PROMPT_CACHE
def get_system_prompt(client: Client) -> str:
"""Генерирует системный промпт на основе данных клиента."""
prompt = _load_base_prompt()
qr = Qr.objects.filter(client=client).first()
if qr:
restaurant_info = f"""
Название: {qr.title or 'Не указано'}
Бренд: {qr.brand or 'Не указан'}
Описание: {qr.description or 'Нет описания'}
Адрес: {qr.addr or 'Не указан'}
Телефон: {qr.phone or 'Не указан'}
Часы работы: {qr.worktime or 'Не указаны'}
Активность: {qr.activity or 'Не указана'}
"""
prompt += restaurant_info
else:
prompt += "\nИнформация о ресторане отсутствует.\n"
categories = ProductCategory.objects.filter(client=client, status=True, src=2).order_by('ord')
if categories.exists():
prompt += "\nМеню:\n"
for cat in categories:
prompt += f"\nКатегория: {cat.title}\n"
products = Product.objects.filter(client=client, up=cat.id, status=1)
if products.exists():
for prod in products:
prompt += f" - {prod.title}{prod.price} руб."
if prod.descr:
prompt += f" ({prod.descr})"
prompt += "\n"
else:
prompt += " (в этой категории пока нет блюд)\n"
else:
prompt += "\nМеню не загружено.\n"
prompt += """
При ответах на вопросы о меню используй только те блюда, которые перечислены выше.
Если клиент спрашивает о блюде, которого нет в списке, честно скажи об этом и предложи посмотреть другие позиции.
Всегда помни, что ты должен возвращать только JSON, без лишнего текста вне JSON.
"""
return prompt
def yandex_chat_structured(
session_id: str,
api_key: str,
folder_id: str,
system_prompt: str,
user_query: str,
model: str = "yandexgpt",
max_tokens: int = 1000,
temperature: float = 0.3,
) -> Dict[str, Any]:
"""
Отправляет запрос к Yandex GPT через официальный SDK и возвращает структурированный ответ (JSON).
Аргументы:
session_id: идентификатор сессии (chat_id)
api_key: API-ключ Yandex Cloud (создаётся в AI Studio)[reference:2]
folder_id: ID папки в Yandex Cloud[reference:3]
system_prompt: системный промт
user_query: запрос пользователя
model: модель (yandexgpt, yandexgpt-lite и др.)
max_tokens: максимальная длина ответа
temperature: креативность (0.01.0)
"""
# Инициализация сессии
if session_id not in _contexts:
_contexts[session_id] = [
{"role": "system", "content": system_prompt}
]
_log_dialogue(session_id, 'SYSTEM', system_prompt)
_log_dialogue(session_id, 'USER', user_query)
_contexts[session_id].append({"role": "user", "content": user_query})
try:
# Аутентификация через API-ключ[reference:4]
auth = APIKeyAuth(api_key)
ai_studio = AIStudio(folder_id=folder_id, auth=auth)
# Отправка запроса через SDK (используем OpenAI-совместимый chat)[reference:5]
response = ai_studio.chat.completions.create(
model=model,
messages=_contexts[session_id],
max_tokens=max_tokens,
temperature=temperature,
)
assistant_content = response.choices[0].message.content
_log_dialogue(session_id, 'ASSISTANT', assistant_content)
_contexts[session_id].append({"role": "assistant", "content": assistant_content})
# Парсим JSON из ответа
try:
parsed = json.loads(assistant_content)
required = ["intent", "message", "entities", "status"]
if all(k in parsed for k in required):
return parsed
else:
return {
"intent": "general",
"message": assistant_content,
"entities": {},
"status": "complete"
}
except json.JSONDecodeError:
return {
"intent": "general",
"message": assistant_content,
"entities": {},
"status": "complete"
}
except Exception as e:
error_msg = "⚠️ Извините, произошла ошибка. Попробуйте позже."
_log_dialogue(session_id, 'ERROR', f"Yandex GPT error: {e}")
return {
"intent": "general",
"message": error_msg,
"entities": {},
"status": "complete"
}

@ -152,11 +152,14 @@ def bot_message(client: Client, message: dict, settings_max: dict, settings_deep
maxbot_send_menu_button(chat_id=chat_id, max_token=settings_max['token'], message='👇 Выберите раздел',
client=client)
else:
# обработка сообщения через ИИ ===========================
# =========================== обработка сообщения через ИИ ===========================
if settings_deepseek.get('token', False):
text = message['message']['body']['text']
# Получаем системный промт из настроек
system_prompt = settings_deepseek.get('deepseek_system_prompt', get_system_prompt(client))
system_prompt = get_system_prompt(client)
if settings_deepseek.get('deepseek_system_prompt', False):
system_prompt = (system_prompt + "\n Дополнительная информация:\n" +
settings_deepseek.get('deepseek_system_prompt'))
# Отправляем запрос в DeepSeek
response = deepseek_chat_structured(

Loading…
Cancel
Save