Track: Best Apps and Agents
What I built
Cozy Night Planner is an app someone would actually use every week: type one prompt — "Plan my Friday night, something cozy, under $60 CAD" — and it plans your whole evening. One movie, one Toronto dinner spot, one playlist, each with a one-line reason tied to what you've liked before. Thumbs-down any pick and Re-plan replaces only that pick; the rest of the evening stays put.
It goes beyond a single plan. You can build a taste profile from your own favourites, snap a photo of a bookshelf or record sleeves and have it matched to real entities, or plan date night for two people with different tastes, where every candidate is scored for both and the planner keeps what both would enjoy. A Toronto heatmap shows where your taste concentrates across the city, and the dinner pick leans toward that hotspot.
Why I built it
Planning assistants can reason, but their recommendations are generic: the same popular movie, restaurant, and playlist for everyone. A good evening isn't three good items — it's one coherent night, and coherence has to come from knowing someone's taste as data, not as a vibe the model guesses. I wanted an app where the model is the brain but never the source of truth: it chooses and explains, while a taste graph supplies every candidate.
How it works — NVIDIA Nemotron on Nebius Token Factory
Every model call in the app goes to Nebius Token Factory (api.tokenfactory.nebius.com, OpenAI-compatible endpoint). The app is an agent pipeline, not a single prompt:
- Mood agent (tool calls). Nemotron reads the prompt and calls a Qloo tag-search tool, so "rainy Sunday date night, romantic" steers each category with real tags like Rainy Day Music and Romantic Dinners.
- Candidates from Qloo. Insights requests fetch movies, Toronto places, and artists seeded by the profile's likes, with dislikes excluded. On date night, each candidate is scored for both people and ranked by the weaker score:
$$\text{score}(c) = \min\left(a_{\text{you}}(c),\, a_{\text{partner}}(c)\right)$$
- Budget filter before the model sees anything. Qloo's price level maps to an estimated CAD meal cost (1 → $15, 2 → $30, 3 → $50, 4 → $90); unpriced places are never picked.
- Selection with validation. Nemotron picks only from the candidate IDs it was given, under a strict JSON schema. The server checks every returned ID against the candidate set, retries once on failure, and rejects anything else — no valid pick means a retry state, never a hallucinated fallback.
- Memory. After a few thumbs, Nemotron writes short notes about what you lean toward, each required to cite at least two of your actual thumbs. Taste, thumbs, and memory live in your browser only — no login, no database.
The plan streams its steps live, so you can watch the agent work: mood tags searched, candidates per category, scoring for two, budget filtering, the model choosing, every pick verified.
Nemotron routing, measured — not guessed
Each step runs on the Nemotron model that measured best for it on the app's real requests (full numbers in devpost/model-benchmark.md, re-runnable with npm run bench):
| Step | Model | Why |
|---|---|---|
| Mood agent (tool calls to Qloo tag search) | Nemotron 3.5 Lightning | Read every test mood (5/5), fastest end to end (~2.0 s) |
| Picking the evening + one-line reasons | Nemotron 3 Ultra 550B | Every reply valid, 1.5–2 s, most specific reasons; reliably speaks to both people on date night |
| Fallback if the pick model fails | Nemotron 3.5 Lightning | Valid 2/2 at ~1.3 s, so a plan still arrives |
| Reading a photo (Snap your taste) | Nemotron 3.5 Super VL | Reasoning off missed titles printed sideways on spines; on read all of them (~6 s) |
| Taste memory from your thumbs | Nemotron 3 Ultra 550B | Runs in the background, so the extra thinking is free; notes must cite ≥ 2 thumbs |
| Choosing taste words / shared themes from Qloo's tags | Nemotron 3.5 Lightning | Qloo proposes the tags; the reply schema only allows Qloo's own tags, so nothing is invented |
What I learned
Two things surprised me. First, grounding is measurable: on the app's grounding page, Nemotron asked on its own repeated the person's own favourites in 24 of 32 movie/artist suggestions and named 7 of 24 restaurants that weren't in Toronto or were closed — with Qloo candidates and ID validation, both dropped to zero. Second, thinking controls are a real engineering surface: reasoning on helped the background memory and vision tasks, but on live picks it burned the whole token budget and returned nothing, and on Ultra it once dropped an instruction. Strict JSON schemas plus thinking off for live steps is what made the app reliable enough to demo.
Challenges I faced
The Qloo key rate-limited parallel tag lookups, so lookups run sequentially with backoff on HTTP 429. Toronto results skewed toward cafés at price level 1, so the prompt now prefers dinner-appropriate places. Plain JSON mode looped on itself, which forced the strict-schema setup. Date night's first version — intersecting two top-15 lists — overlapped too little and leaned toward one person, which forced the dual-affinity scoring above. A "trending this month" feature was cut when the trending endpoints returned empty lists and 404s: no real data, so no feature, rather than a faked one.
How Token Factory accelerated this build
Having Nano through Ultra plus a vision model behind one OpenAI-compatible endpoint is what made measured per-step routing practical: same request shape, different model IDs, benchmarked on real prompts in an afternoon. Tool calling worked across Lightning, Nano, Super, and Ultra with the OpenAI format, and strict JSON schema output was the single most useful feature — it ended the looping that plain JSON mode caused. Ultra with reasoning off was surprisingly fast (1.5–2 s), which is why it could stay on the live path instead of being reserved for background work.
Feedback on Token Factory and Nemotron
Strict JSON schema output (response_format: json_schema, strict: true) was the most valuable feature. Two things would help builders: the chat-completions reference says only json_object/text response formats are supported, but strict json_schema worked in every call I made; and the documented reasoning switch is reasoning_effort, while chat_template_kwargs.enable_thinking is what actually worked for switching thinking off — and with reasoning on, Lightning spent its entire max_tokens thinking and returned no content, with no way to cap reasoning tokens separately from the answer. Tool arguments also sometimes packed several ideas into one ("fun and upbeat"), so I split them server-side. Otherwise, most calls returned in 1–3 s, and the slowest part of a plan was the taste-graph lookups, not the model.
Built with
Nebius Token Factory, NVIDIA Nemotron (3.5 Lightning, 3 Ultra 550B, 3.5 Super VL), Qloo API, TypeScript, React + Vite (Vercel), Express (Render).
Built With
- nebius
- qloo
- typescript
Log in or sign up for Devpost to join the conversation.