import json from openai import AsyncOpenAI from app.core.config import settings from app.schemas import ChallengeGenerated class GPTService: """Service for generating challenges using OpenAI GPT""" def __init__(self): self.client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) async def generate_challenges( self, game_title: str, game_genre: str | None = None ) -> list[ChallengeGenerated]: """ Generate challenges for a game using GPT. Args: game_title: Name of the game game_genre: Optional genre of the game Returns: List of generated challenges """ genre_text = f" (жанр: {game_genre})" if game_genre else "" prompt = f"""Для видеоигры "{game_title}"{genre_text} сгенерируй 6 челленджей для игрового марафона. Требования: - 2 лёгких челленджа (15-30 минут игры) - 2 средних челленджа (1-2 часа игры) - 2 сложных челленджа (3+ часов или высокая сложность) Для каждого челленджа укажи: - title: короткое название на русском (до 50 символов) - description: что нужно сделать на русском (1-2 предложения) - type: один из [completion, no_death, speedrun, collection, achievement, challenge_run] - difficulty: easy/medium/hard - points: очки (easy: 20-40, medium: 45-75, hard: 90-150) - estimated_time: примерное время в минутах - proof_type: screenshot/video/steam (что лучше подойдёт для проверки) - proof_hint: что должно быть на скриншоте/видео для подтверждения на русском Ответь ТОЛЬКО валидным JSON объектом с ключом "challenges" содержащим массив челленджей. Пример формата: {{"challenges": [{{"title": "...", "description": "...", "type": "...", "difficulty": "...", "points": 50, "estimated_time": 30, "proof_type": "...", "proof_hint": "..."}}]}}""" response = await self.client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"}, temperature=0.7, max_tokens=2000, ) content = response.choices[0].message.content data = json.loads(content) challenges = [] for ch in data.get("challenges", []): # Validate and normalize type ch_type = ch.get("type", "completion") if ch_type not in ["completion", "no_death", "speedrun", "collection", "achievement", "challenge_run"]: ch_type = "completion" # Validate difficulty difficulty = ch.get("difficulty", "medium") if difficulty not in ["easy", "medium", "hard"]: difficulty = "medium" # Validate proof_type proof_type = ch.get("proof_type", "screenshot") if proof_type not in ["screenshot", "video", "steam"]: proof_type = "screenshot" # Validate points based on difficulty points = ch.get("points", 30) if not isinstance(points, int) or points < 1: points = 30 # Clamp points to expected ranges if difficulty == "easy": points = max(20, min(40, points)) elif difficulty == "medium": points = max(45, min(75, points)) elif difficulty == "hard": points = max(90, min(150, points)) challenges.append(ChallengeGenerated( title=ch.get("title", "Unnamed Challenge")[:100], description=ch.get("description", "Complete the challenge"), type=ch_type, difficulty=difficulty, points=points, estimated_time=ch.get("estimated_time"), proof_type=proof_type, proof_hint=ch.get("proof_hint"), )) return challenges