""" nlp.py — OpenRouter (qwen/qwen3.6-plus) NLP-анализ текста песни → {mood, scenes} """ import json import os import requests from dotenv import load_dotenv load_dotenv(os.path.expanduser("~/.openclaw/.env")) OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions" OPENROUTER_MODEL = "qwen/qwen3.6-plus" OPENROUTER_HEADERS_BASE = { "Content-Type": "application/json", "HTTP-Referer": "https://openclaw.ai", "X-Title": "Karaoke Generator", } def analyze(text: str) -> dict: """ Отправить текст в OpenRouter (qwen/qwen3.6-plus), получить {mood, scenes}. При ошибке или отсутствии ключа — fallback. """ api_key = os.environ.get("OPENROUTER_API_KEY") if not api_key: print("[nlp] OPENROUTER_API_KEY не задан. Используем fallback.") return _fallback(text) prompt = ( "Определи настроение и 3-5 ключевых визуальных сцен для этой песни. " "Ответь ТОЛЬКО JSON без обёрток:\n" '{"mood": "строка", "scenes": ["сцена1", "сцена2", ...]}' "\n\nТекст песни:\n" + text[:3000] ) headers = { **OPENROUTER_HEADERS_BASE, "Authorization": f"Bearer {api_key}", } body = { "model": OPENROUTER_MODEL, "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, } try: resp = requests.post(OPENROUTER_URL, headers=headers, json=body, timeout=30) resp.raise_for_status() data = resp.json() content = data["choices"][0]["message"]["content"].strip() # Парсим JSON — иногда модель возвращает markdown-обёртку content = content.strip("```json").strip("```").strip() result = json.loads(content) print(f"[nlp] OpenRouter ответ: mood={result.get('mood')}, scenes={result.get('scenes')}") return result except Exception as e: print(f"[nlp] Ошибка OpenRouter API: {e}. Используем fallback.") return _fallback(text) def _fallback(text: str) -> dict: """Простой fallback без API.""" text_lower = text.lower() mood_map = { "love": "romantic", "любов": "romantic", "heart": "romantic", "сердц": "romantic", "kiss": "romantic", "night": "moody", "ноч": "moody", "dark": "moody", "темн": "moody", "rain": "moody", "дожд": "moody", "sun": "happy", "солнц": "happy", "свет": "happy", "light": "happy", "party": "energetic", "танц": "energetic", "dance": "energetic", "drive": "energetic", "драйв": "energetic", "sad": "sad", "груст": "sad", "cry": "sad", "плач": "sad", } mood = "neutral" for key, val in mood_map.items(): if key in text_lower: mood = val break scene_map = { "love": "romantic couple", "любов": "romantic sunset", "night": "city night lights", "ноч": "starry sky", "sun": "golden hour landscape", "солнц": "sunrise nature", "rain": "rain window", "дожд": "rainy city", "party": "party lights", "танц": "dance floor", "sad": "solitary person", "груст": "lonely road", "sea": "ocean waves", "мор": "ocean sunset", "mountain": "mountain peaks", "гор": "mountain landscape", "fire": "campfire", "огон": "firelight", "snow": "snowy landscape", "снег": "winter forest", "лес": "forest path", "forest": "forest path", "road": "highway drive", "дорог": "open road", } scenes = ["abstract gradient"] for key, val in scene_map.items(): if key in text_lower: scenes.append(val) scenes = scenes[:5] if len(scenes) < 1: scenes = ["abstract gradient"] return {"mood": mood, "scenes": scenes}