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, games: list[dict] ) -> dict[int, list[ChallengeGenerated]]: """ Generate challenges for multiple games in one API call. Args: games: List of dicts with keys: id, title, genre Returns: Dict mapping game_id to list of generated challenges """ if not games: return {} games_text = "\n".join([ f"- {g['title']}" + (f" (жанр: {g['genre']})" if g.get('genre') else "") for g in games ]) prompt = f"""Ты — эксперт по видеоиграм. Сгенерируй по 6 КОНКРЕТНЫХ челленджей для каждой из следующих игр: {games_text} ВАЖНО: - ВСЕ ТЕКСТЫ (title, description, proof_hint) ОБЯЗАТЕЛЬНО ПИШИ НА РУССКОМ ЯЗЫКЕ! - Используй интернет для поиска актуальной информации об играх - Челленджи должны быть СПЕЦИФИЧНЫМИ для каждой игры! - Используй РЕАЛЬНЫЕ названия локаций, боссов, персонажей, миссий, уровней из игры - Основывайся на том, какие челленджи РЕАЛЬНО делают игроки в этой игре - НЕ генерируй абстрактные челленджи типа "пройди уровень" или "убей 10 врагов" Требования по сложности ДЛЯ КАЖДОЙ ИГРЫ: - 2 лёгких (15-30 мин): простые задачи - 2 средних (1-2 часа): требуют навыка - 2 сложных (3-12 часов): серьёзный челлендж Формат ответа — JSON с объектом где ключи это ТОЧНЫЕ названия игр, как они указаны в запросе: {{ "Название игры 1": {{ "challenges": [ {{"title": "...", "description": "...", "type": "completion|no_death|speedrun|collection|achievement|challenge_run", "difficulty": "easy|medium|hard", "points": 50, "estimated_time": 30, "proof_type": "screenshot|video|steam", "proof_hint": "..."}} ] }}, "Название игры 2": {{ "challenges": [...] }} }} points: easy=20-40, medium=45-75, hard=90-150 Ответь ТОЛЬКО JSON. ОПИСАНИЕ И НАЗВАНИЕ ЧЕЛЛЕНДЖА ТОЛЬКО НА РУССКОМ ЯЗЫКЕ!""" response = await self.client.chat.completions.create( model="gpt-5", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"}, ) content = response.choices[0].message.content data = json.loads(content) # Map game titles to IDs (case-insensitive, strip whitespace) title_to_id = {g['title'].lower().strip(): g['id'] for g in games} # Also keep original titles for logging id_to_title = {g['id']: g['title'] for g in games} print(f"[GPT] Requested games: {[g['title'] for g in games]}") print(f"[GPT] Response keys: {list(data.keys())}") result = {} for game_title, game_data in data.items(): # Try exact match first, then case-insensitive game_id = title_to_id.get(game_title.lower().strip()) if not game_id: # Try partial match if exact match fails for stored_title, gid in title_to_id.items(): if stored_title in game_title.lower() or game_title.lower() in stored_title: game_id = gid break if not game_id: print(f"[GPT] Could not match game: '{game_title}'") continue challenges = [] for ch in game_data.get("challenges", []): challenges.append(self._parse_challenge(ch)) result[game_id] = challenges print(f"[GPT] Generated {len(challenges)} challenges for '{id_to_title.get(game_id)}'") return result def _parse_challenge(self, ch: dict) -> ChallengeGenerated: """Parse and validate a single challenge from GPT response""" ch_type = ch.get("type", "completion") if ch_type not in ["completion", "no_death", "speedrun", "collection", "achievement", "challenge_run"]: ch_type = "completion" difficulty = ch.get("difficulty", "medium") if difficulty not in ["easy", "medium", "hard"]: difficulty = "medium" proof_type = ch.get("proof_type", "screenshot") if proof_type not in ["screenshot", "video", "steam"]: proof_type = "screenshot" points = ch.get("points", 30) if not isinstance(points, int) or points < 1: points = 30 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)) return 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"), ) gpt_service = GPTService()