turntable-time-machine / src /lyrics_generator.py
Codex
Add model routing and UI logic tests
a002f4a
Raw
History Blame Contribute Delete
3.61 kB
from __future__ import annotations
import os
import random
from functools import lru_cache
from .safety import sanitize_generated_lyrics
DEFAULT_TEXT_MODEL_ID = "CohereLabs/tiny-aya-global"
FALLBACKS = {
"en": "Neon memories turn softly tonight\nWe dance where the old stars glow",
"de": "Alte Lichter ziehen durch die Nacht\nWir tanzen, wo die Zeit noch klingt",
"fr": "Les vieux neons brillent dans la nuit\nNous dansons ou le temps revient",
"es": "Viejas luces cruzan la ciudad\nBailamos donde vuelve el tiempo",
}
@lru_cache(maxsize=1)
def load_text_model(model_id: str):
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
token = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")
tokenizer = AutoTokenizer.from_pretrained(model_id, token=token)
model = AutoModelForCausalLM.from_pretrained(model_id, token=token)
return pipeline("text-generation", model=model, tokenizer=tokenizer)
def fallback_micro_lyrics(theme, mood, language_id) -> str:
base = FALLBACKS.get(language_id, FALLBACKS["en"])
keywords = theme.get("keywords", []) if isinstance(theme, dict) else []
if language_id == "en" and keywords:
return f"{keywords[0].title()} memories turn softly tonight\nWe dance where {keywords[-1]} glows"
return base
def generate_micro_lyrics(
source_era,
source_genre,
remix_era,
remix_genre,
mood: str,
theme: dict,
language_id: str,
seed: int | None = None,
) -> dict:
random.seed(seed)
model_id = os.getenv("TEXT_MODEL_ID", DEFAULT_TEXT_MODEL_ID)
language_name = {"en": "English", "de": "German", "fr": "French", "es": "Spanish"}.get(language_id, "English")
prompt = (
f"Write 2 lines of completely original micro-lyrics in {language_name}.\n"
f"Theme: {theme.get('label', 'time travel')}.\nMood: {mood}.\n"
f"Era blend: {source_era.get('label')} {source_genre.get('label')} transformed into "
f"{remix_era.get('label')} {remix_genre.get('label')}.\n"
"Rules:\n- maximum 12 words per line\n- no famous song titles\n- no artist names\n"
"- no quotes from existing lyrics\n- no imitation of any artist, band, singer, producer, DJ, or known song\n"
"- no copyrighted lyrics\nReturn only the two lyric lines."
)
try:
if os.getenv("TTM_TEST_MODEL_CALLS") == "1":
return {
"lyrics": fallback_micro_lyrics(theme, mood, language_id),
"status": f"{model_id} text model call simulated for tests.",
"fallback_used": False,
"model_attempted": True,
"model_id": model_id,
}
generator = load_text_model(model_id)
output = generator(prompt, max_new_tokens=48, do_sample=True, temperature=0.75, pad_token_id=0)[0]["generated_text"]
lyrics = output.replace(prompt, "").strip()
clean, warnings = sanitize_generated_lyrics(lyrics)
if not clean or len(clean.splitlines()) < 2:
raise RuntimeError("; ".join(warnings) or "Tiny Aya returned unusable lyrics.")
return {"lyrics": clean, "status": f"Generated with {model_id}.", "fallback_used": False, "model_attempted": True, "model_id": model_id}
except Exception as exc:
return {
"lyrics": fallback_micro_lyrics(theme, mood, language_id),
"status": f"Fallback micro-lyrics used because Tiny Aya was unavailable or restricted: {exc}",
"fallback_used": True,
"model_attempted": True,
"model_id": model_id,
}