Spaces:
Running
Running
| """ | |
| Test the LLM + parser only. No audio, no Gradio, no Kokoro. | |
| Usage: | |
| python test_llm.py # uses LULLABY_GGUF env var or ./lullaby.gguf | |
| LULLABY_GGUF=/path/to/x.gguf python test_llm.py | |
| """ | |
| import os | |
| import re | |
| import sys | |
| import time | |
| from llama_cpp import Llama | |
| MODEL_PATH = os.environ.get("LULLABY_GGUF", "lolaby.gguf") | |
| SYSTEM_PROMPT = ( | |
| "You write personalized lullabies for small children, with chord markers " | |
| "and a tempo/meter header so a guitar accompaniment can be rendered. " | |
| "Output only the lullaby β no preamble." | |
| ) | |
| # Same regexes as app.py β if these match, the audio pipeline will too. | |
| TEMPO_RE = re.compile(r"Tempo:\s*(\d+)\s*bpm,\s*(\d+)/(\d+)", re.IGNORECASE) | |
| PROG_RE = re.compile(r"Progression:\s*([A-G][^\n]+)", re.IGNORECASE) | |
| CHORD_RE = re.compile(r"\[([A-G][^\]]*)\]\s*([^\[\n]*)") | |
| TEST_PROMPTS = [ | |
| ( | |
| "Mia, sleepy and comforted, 6/8", | |
| "Write a lullaby for: Mia, age 3\n" | |
| "Loves: her stuffed elephant Pip, the moon\n" | |
| "Mood: sleepy and comforted\n" | |
| "Key: C major\n" | |
| "Meter: 6/8", | |
| ), | |
| ( | |
| "Theo with fear, minor key", | |
| "Write a lullaby for: Theo, age 2\n" | |
| "Loves: trains and tunnels\n" | |
| "Fears: the dark\n" | |
| "Mood: tearful, needs soothing\n" | |
| "Key: A minor\n" | |
| "Meter: 6/8", | |
| ), | |
| ( | |
| "Beatriz, content, 4/4", | |
| "Write a lullaby for: Beatriz, age 4\n" | |
| "Loves: the cat next door, songs about the sea\n" | |
| "Mood: cosy and content\n" | |
| "Key: D major\n" | |
| "Meter: 4/4", | |
| ), | |
| ] | |
| def parse_lullaby(text): | |
| """Same parser as the app β returns dict or raises ValueError.""" | |
| tempo_match = TEMPO_RE.search(text) | |
| if not tempo_match: | |
| raise ValueError("missing 'Tempo:' line") | |
| prog_match = PROG_RE.search(text) | |
| if not prog_match: | |
| raise ValueError("missing 'Progression:' line") | |
| progression = [c.strip() for c in re.split(r"\s*-\s*", prog_match.group(1)) if c.strip()] | |
| lines = [] | |
| for raw in text.splitlines(): | |
| raw = raw.strip() | |
| if not raw.startswith("["): | |
| continue | |
| fragments = CHORD_RE.findall(raw) | |
| if fragments: | |
| lines.append(fragments) | |
| if not lines: | |
| raise ValueError("no chord-marked lyric lines") | |
| return { | |
| "bpm": int(tempo_match.group(1)), | |
| "meter": f"{tempo_match.group(2)}/{tempo_match.group(3)}", | |
| "progression": progression, | |
| "lines": lines, | |
| } | |
| def check(label, cond, detail=""): | |
| mark = "β" if cond else "β" | |
| print(f" {mark} {label}" + (f" β {detail}" if detail else "")) | |
| return cond | |
| def main(): | |
| if not os.path.exists(MODEL_PATH): | |
| print(f"ERROR: model not found at {MODEL_PATH}") | |
| print("Set LULLABY_GGUF env var or put lullaby.gguf in cwd.") | |
| sys.exit(1) | |
| print(f"Loading {MODEL_PATH}...") | |
| t0 = time.time() | |
| llm = Llama( | |
| model_path=MODEL_PATH, | |
| n_ctx=1024, | |
| n_threads=4, | |
| n_gpu_layers=0, | |
| chat_format="llama-3", | |
| verbose=False, | |
| ) | |
| print(f"Loaded in {time.time() - t0:.1f}s\n") | |
| total_pass = 0 | |
| total_checks = 0 | |
| for label, prompt in TEST_PROMPTS: | |
| print(f"=== {label} ===") | |
| print(f"Prompt:\n{prompt}\n") | |
| t0 = time.time() | |
| resp = llm.create_chat_completion( | |
| messages=[ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| temperature=0.75, | |
| max_tokens=400, | |
| top_p=0.9, | |
| ) | |
| gen_time = time.time() - t0 | |
| output = resp["choices"][0]["message"]["content"].strip() | |
| print(f"Output ({gen_time:.1f}s):\n{output}\n") | |
| # Parse it | |
| try: | |
| parsed = parse_lullaby(output) | |
| total_pass += check("parses cleanly", True) | |
| total_pass += check("tempo found", "bpm" in parsed, f"{parsed['bpm']} bpm") | |
| total_pass += check("progression has β₯3 chords", | |
| len(parsed["progression"]) >= 3, | |
| str(parsed["progression"])) | |
| total_pass += check("β₯6 chord-marked lines", | |
| len(parsed["lines"]) >= 6, | |
| f"{len(parsed['lines'])} lines") | |
| # Name check | |
| name = prompt.split("for:")[1].split(",")[0].strip() | |
| found = name.lower() in output.lower() | |
| total_pass += check(f"name '{name}' in output", found) | |
| total_checks += 5 | |
| except ValueError as e: | |
| check("parses cleanly", False, str(e)) | |
| total_checks += 5 | |
| print() | |
| print(f"\n=== {total_pass}/{total_checks} checks passed ===") | |
| sys.exit(0 if total_pass == total_checks else 1) | |
| if __name__ == "__main__": | |
| main() | |