112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
"""
|
||
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}
|