import os import pymysql from dotenv import load_dotenv import requests import json import re import time import sys import random from datetime import datetime # Загрузка переменных окружения load_dotenv() # Конфигурация из переменных окружения DB_CONFIG = { 'host': os.getenv('MDB_HOST'), 'user': os.getenv('MDB_USER'), 'password': os.getenv('MDB_PW'), 'database': os.getenv('MDBASE'), 'charset': 'utf8mb4' } OPENROUTER_CONFIG = { 'api_key': os.getenv('OR_KEY'), 'model': os.getenv('OR_MODEL_NAME') } # Настройки журналирования LOG_FILE = os.getenv('LOG_FILE', 'ai_namer.log') # Путь к лог-файлу LOG_PREFIX = "AI_namer" # Уникальный префикс для идентификации скрипта def log_message(message, max_retries=5, retry_delay=0.1): """ Записывает сообщение в лог-файл с обработкой блокировок и идентификатором скрипта """ if not LOG_FILE: return timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Добавляем идентификатор скрипта к сообщению log_line = f"[{timestamp}] [{LOG_PREFIX}] {message}\n" for attempt in range(max_retries): try: with open(LOG_FILE, 'a', encoding='utf-8') as log: log.write(log_line) return True except (IOError, OSError) as e: if "locked" in str(e).lower() and attempt < max_retries - 1: # Случайная задержка для уменьшения коллизий sleep_time = retry_delay * (1 + random.random() * 0.5) time.sleep(sleep_time) else: # Если не удалось записать после всех попыток print(f"Ошибка записи в лог: {e}") print(f"Сообщение для лога: {log_line.strip()}") return False def _generate_shortname_by_api(text): """Внутренняя функция для генерации краткого названия через OpenRouter API""" url = "https://openrouter.ai/api/v1/chat/completions" headers = { "Authorization": f"Bearer {OPENROUTER_CONFIG['api_key']}", "Content-Type": "application/json" } prompt = f""" Прочитай нижеприведенный анонс и сформируй краткое (до 80 знаков) название этого события для каталога. Место, дата и время проведения для каталога несущественны. Важно название и, в зависимости от того, что это за событие, выступающие на нем или проводящие его. Для данного каталога допустимо использование никнеймов вместо официальных имен и фамилий. Ответ должен содержать только одно название без каких-либо объяснений, комментариев и вариантов. АНОНС: {text} Название:""" data = { "model": OPENROUTER_CONFIG['model'], "messages": [{"role": "user", "content": prompt}], "max_tokens": 5000, "temperature": 0.3 } try: response = requests.post(url, headers=headers, json=data, timeout=60) # Обработка ошибки 429 (Too Many Requests) if response.status_code == 429: error_info = { "status_code": response.status_code, "headers": dict(response.headers), "body": response.text } # Пытаемся распарсить JSON ответ try: error_info["body"] = response.json() except: pass log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") sys.exit(1) response.raise_for_status() result = response.json() # Извлекаем только текст ответа shortname = result['choices'][0]['message']['content'].strip() # Удаляем возможные кавычки и лишние символы shortname = re.sub(r'^["\']|["\']$', '', shortname) # Обрезаем до 80 символов return shortname[:80] except requests.exceptions.HTTPError as e: if e.response.status_code == 429: error_info = { "status_code": e.response.status_code, "headers": dict(e.response.headers), "body": e.response.text } # Пытаемся распарсить JSON ответ try: error_info["body"] = e.response.json() except: pass log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") sys.exit(1) else: error_msg = f"HTTP ошибка при генерации названия: {e}" log_message(error_msg) print(error_msg) return None except Exception as e: error_msg = f"Ошибка при генерации названия: {e}" log_message(error_msg) print(error_msg) return None def generate_ai_shortname(text): """ Генерирует краткое название для описанного в тексте мероприятия. Args: text (str): Текст описания мероприятия Returns: str: Краткое название (до 80 знаков) или None в случае ошибки """ log_message(f"Начало генерации названия для текста длиной {len(text)} символов") if not text or not text.strip(): log_message("Получен пустой текст для генерации названия") return None shortname = _generate_shortname_by_api(text) if shortname: log_message(f"Успешно сгенерировано название: {shortname}") else: log_message("Не удалось сгенерировать название") return shortname def ai_shortname_all(): """ Обрабатывает все записи в базе данных с пустым полем shortname, генерируя для них краткие названия с помощью нейросети. """ log_message("Запуск обработки всех записей с пустым shortname") # Подключение к БД try: connection = pymysql.connect(**DB_CONFIG) log_message("Успешное подключение к базе данных") except Exception as e: error_msg = f"Ошибка подключения к базе данных: {e}" log_message(error_msg) print(error_msg) return try: with connection.cursor() as cursor: # Выбор записей с пустым shortname cursor.execute("SELECT id, text FROM posts WHERE shortname IS NULL OR shortname = ''") posts = cursor.fetchall() log_message(f"Найдено {len(posts)} записей для обработки") print(f"Найдено {len(posts)} записей для обработки") for i, (post_id, text) in enumerate(posts, 1): if not text: continue log_message(f"Обрабатывается запись {i}/{len(posts)} (ID: {post_id})") print(f"Обрабатывается запись {i}/{len(posts)} (ID: {post_id})") shortname = generate_ai_shortname(text) if shortname: # Обновление записи try: cursor.execute( "UPDATE posts SET shortname = %s WHERE id = %s", (shortname, post_id) ) connection.commit() log_message(f"Запись {post_id} успешно обновлена: {shortname}") print(f"Обновлено: {shortname}") except Exception as e: error_msg = f"Ошибка обновления записи {post_id}: {e}" log_message(error_msg) print(error_msg) else: log_message(f"Не удалось сгенерировать название для записи {post_id}") print("Не удалось сгенерировать название") # Небольшая пауза между запросами time.sleep(1) log_message("Обработка всех записей завершена") print("Обработка завершена") except SystemExit: # Перехватываем системный выход для корректного закрытия соединения log_message("Завершение работы из-за ошибки 429") print("Завершение работы из-за ошибки 429") except Exception as e: error_msg = f"Произошла ошибка при обработке записей: {e}" log_message(error_msg) print(error_msg) finally: connection.close() log_message("Соединение с базой данных закрыто") if __name__ == "__main__": ai_shortname_all()