Files
game-marathon/backend/app/services/gpt.py
2025-12-16 22:12:12 +07:00

144 lines
5.5 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.
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}
ВАЖНО: Челленджи должны быть СПЕЦИФИЧНЫМИ для каждой игры!
- Используй РЕАЛЬНЫЕ названия локаций, боссов, персонажей, миссий, уровней из игры
- Основывайся на том, какие челленджи РЕАЛЬНО делают игроки в этой игре
- НЕ генерируй абстрактные челленджи типа "пройди уровень" или "убей 10 врагов"
Требования по сложности ДЛЯ КАЖДОЙ ИГРЫ:
- 2 лёгких (15-30 мин): простые задачи
- 2 средних (1-2 часа): требуют навыка
- 2 сложных (3+ часа): серьёзный челлендж
Формат ответа — 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-mini",
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()