"""
Riddle Sprite
=============
Whisper a secret word. The sprite spins a little riddle for it that you can pose
to a friend.
Track 2 toy for the "Small Models Big Adventures" hackathon.
Model: nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16 (4B, well under the 32B ceiling).
Nemotron is a reasoning model, so it likes to think out loud; we turn that off and
strip any stray reasoning, keeping only the riddle.
Modes: model (Nemotron) / keeper (a templated riddle, so it always demos).
"""
import os
import re
import html
import random
import inspect
import gradio as gr
_BLOCKS_HAS_CSS = "css" in inspect.signature(gr.Blocks.__init__).parameters
_LAUNCH_HAS_SSR = "ssr_mode" in inspect.signature(gr.Blocks.launch).parameters
MODEL_ID = os.environ.get("RIDDLE_MODEL", "nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16")
DEBUG = os.environ.get("RIDDLE_DEBUG", "").strip().lower() in {"1", "true", "yes"}
MAX_NEW_TOKENS = 160
SYSTEM_PROMPT = (
"/no_think\n"
"You are a playful riddle sprite. Given a secret word, write ONE short riddle "
"(2 to 4 lines) whose answer is that word. Never use the word itself or any plural "
"or obvious form of it. Be whimsical and fair. End with the line: What am I? "
"Output only the riddle, no preamble, no explanation, no answer."
)
EXAMPLES = ["moon", "umbrella", "shadow", "library"]
def _noop_gpu(*a, **k):
def wrap(fn):
return fn
return wrap(a[0]) if a and callable(a[0]) else wrap
if os.environ.get("SPACES_ZERO_GPU", "").lower() in {"true", "1"}:
try:
import spaces
GPU = spaces.GPU
except Exception: # noqa: BLE001
GPU = _noop_gpu
else:
GPU = _noop_gpu
_tokenizer = None
_model = None
MODE = "keeper"
_warmed = False
def load_model():
global _tokenizer, _model, MODE
try:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
_model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto",
)
_model.eval()
MODE = "model"
print(f"[Riddle] Loaded {MODEL_ID} -- model mode.")
except Exception as exc: # noqa: BLE001
MODE = "keeper"
print(f"[Riddle] Could not load {MODEL_ID} ({exc}). Keeper mode active.")
load_model()
@GPU(duration=40)
def _model_generate(word):
import torch
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Secret word: {word}"},
]
enc = _tokenizer.apply_chat_template(
messages, add_generation_prompt=True, return_tensors="pt", return_dict=True,
).to(_model.device)
input_len = enc["input_ids"].shape[1]
with torch.no_grad():
out = _model.generate(
**enc, max_new_tokens=MAX_NEW_TOKENS, do_sample=True,
temperature=0.8, top_p=0.9, pad_token_id=_tokenizer.eos_token_id,
)
return _tokenizer.decode(out[0][input_len:], skip_special_tokens=True).strip()
_K_TEMPLATES = [
"I have no voice, yet I am known,\nI come to you when light has flown.\nLook for me where things are hidden.\nWhat am I?",
"Small and patient, I wait unseen,\nuntil you need me, then I lean.\nI guard you from what falls above.\nWhat am I?",
"I follow close but make no sound,\nI stretch and shrink along the ground.\nWithout the light I disappear.\nWhat am I?",
]
def _keeper_riddle(word):
rng = random.Random(word.strip().lower())
return rng.choice(_K_TEMPLATES)
def _clean(raw, word):
# strip reasoning traces in any of the forms a reasoning model might emit
raw = re.sub(r".*?", "", raw, flags=re.S | re.I)
raw = re.sub(r".*$", "", raw, flags=re.S | re.I)
raw = re.sub(r"^.*?", "", raw, flags=re.S | re.I)
raw = raw.strip().strip('"').strip()
# reject if empty, leaks the answer, or rambles like reasoning
low = raw.lower()
w = word.strip().lower()
if (not raw or w and w in low or len(raw) > 320
or re.search(r"\b(I should|let me|the answer|the word)\b", raw, re.I)):
return ""
return raw
def make_riddle(word):
raw = ""
if MODE == "model":
try:
raw = _model_generate(word)
except Exception as exc: # noqa: BLE001
print(f"[Riddle] generation error: {exc}")
raw = ""
riddle = _clean(raw, word) if raw else ""
if not riddle:
riddle = _keeper_riddle(word)
return riddle
def esc(s):
return html.escape(str(s))
def render(riddle, word, revealed):
if not riddle:
return ('
Whisper a secret word below, '
'then ask for a riddle.
')
body = esc(riddle).replace("\n", "
")
ans = (f'answer: {esc(word)}
' if revealed
else 'answer hidden
')
return f''
def on_make(word, _state):
global _warmed
word = (word or "").strip()
if not word:
yield ('The sprite needs a secret word first.
',
{"riddle": "", "word": "", "revealed": False}, "")
return
if MODE == "model" and not _warmed:
yield ('🧚 The sprite is thinking up a riddle '
'(the first one takes a moment)...
',
{"riddle": "", "word": word, "revealed": False}, "")
riddle = make_riddle(word)
if MODE == "model":
_warmed = True
state = {"riddle": riddle, "word": word, "revealed": False}
yield render(riddle, word, False), state, ""
def on_reveal(state):
if not state or not state.get("riddle"):
return render("", "", False), state
state = {**state, "revealed": not state.get("revealed")}
return render(state["riddle"], state["word"], state["revealed"]), state
def use_example(text):
return text
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Spectral:ital,wght@0,400;0,500;1,400&display=swap');
:root{--paper:#f3ead7;--paper-2:#efe3c9;--ink:#33301f;--ink-soft:#6b5a47;
--moss:#5a6b3a;--plum:#6d4b86;--line:#c9b48f;}
.gradio-container,.gradio-container.dark,.dark{
--body-background-fill:transparent;--background-fill-primary:#fffaf0;--background-fill-secondary:#f6edd8;
--block-background-fill:#fffaf0;--block-border-color:var(--line);--border-color-primary:var(--line);
--body-text-color:var(--ink);--body-text-color-subdued:var(--ink-soft);
--block-label-text-color:var(--moss);--block-title-text-color:var(--ink);
--block-label-background-fill:#efe3c9;--block-title-background-fill:transparent;
--input-background-fill:#fffaf0;--input-border-color:var(--line);--input-placeholder-color:var(--ink-soft);
--button-primary-background-fill:var(--plum);--button-primary-background-fill-hover:#583a6e;
--button-primary-text-color:#fbf7ec;--button-primary-border-color:#583a6e;
--button-secondary-background-fill:#efe3c9;--button-secondary-background-fill-hover:#e7d6b3;
--button-secondary-text-color:var(--ink);--button-secondary-border-color:var(--line);
--color-accent:var(--plum);--color-accent-soft:#f0e6f3;}
.gradio-container{background:radial-gradient(120% 80% at 80% -10%,#f7efdd,var(--paper) 55%,var(--paper-2));
font-family:'Spectral',Georgia,serif !important;color:var(--ink) !important;max-width:860px !important;}
.gradio-container textarea,.gradio-container input[type="text"],.gradio-container input:not([type]){
background:#fffaf0 !important;color:var(--ink) !important;-webkit-text-fill-color:var(--ink) !important;border-color:var(--line) !important;}
.gradio-container textarea::placeholder,.gradio-container input::placeholder{color:var(--ink-soft) !important;-webkit-text-fill-color:var(--ink-soft) !important;opacity:1;}
.rd-title{font-family:'Fraunces',serif;font-weight:600;font-size:2.5rem;line-height:1;margin:.2rem 0 0;}
.rd-title em{font-style:italic;color:var(--plum);}
.rd-sub{font-style:italic;color:var(--ink-soft);margin:.35rem 0 1rem;font-size:1.05rem;}
.rd-mode{display:inline-block;font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:var(--moss);border:1px solid var(--line);border-radius:999px;padding:.15rem .6rem;}
.scroll{background:#fffaf0;border:1px solid var(--line);border-radius:16px;padding:24px 26px;
box-shadow:0 14px 36px -22px rgba(51,48,31,.7);text-align:center;}
.scroll.empty,.scroll.waking{color:var(--ink-soft);font-style:italic;}
.riddle{font-family:'Fraunces',serif;font-size:1.45rem;line-height:1.5;color:var(--ink);font-style:italic;}
.answer{margin-top:16px;padding-top:12px;border-top:1px dashed var(--line);color:var(--moss);}
.answer.hidden{color:var(--ink-soft);font-style:italic;}
.rd-foot{color:var(--ink-soft);font-size:.82rem;font-style:italic;text-align:center;margin-top:10px;}
footer{display:none !important;}
"""
_bk = {"title": "Riddle Sprite"}
if _BLOCKS_HAS_CSS:
_bk["css"] = CSS
_bk["theme"] = gr.themes.Soft()
with gr.Blocks(**_bk) as demo:
state = gr.State({"riddle": "", "word": "", "revealed": False})
public = "Nemotron-3-Nano-4B · on-device" if MODE == "model" else "Riddle Sprite"
mode_label = f"{public} · [{MODE}]" if DEBUG else public
gr.HTML(f"""
Riddle Sprite
Whisper a secret word. Get a riddle to puzzle a friend.
{mode_label} """)
with gr.Row():
word = gr.Textbox(placeholder="a secret word... (moon, umbrella, library)",
show_label=False, scale=8, autofocus=True)
go = gr.Button("Spin a riddle", variant="primary", scale=1)
with gr.Row():
ex_btns = [gr.Button(e, size="sm") for e in EXAMPLES]
scroll = gr.HTML(render("", "", False))
reveal = gr.Button("Reveal the answer", size="sm")
gr.HTML('')
go.click(on_make, [word, state], [scroll, state, word])
word.submit(on_make, [word, state], [scroll, state, word])
reveal.click(on_reveal, state, [scroll, state])
for b, e in zip(ex_btns, EXAMPLES):
b.click(use_example, gr.State(e), word)
if __name__ == "__main__":
_lk = {}
if not _BLOCKS_HAS_CSS:
_lk["css"] = CSS
_lk["theme"] = gr.themes.Soft()
if _LAUNCH_HAS_SSR:
_lk["ssr_mode"] = False
demo.queue(max_size=24).launch(**_lk)