Files
wiki/tasks/karaoke/nlp.py
2026-04-30 00:40:01 +03:00

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