TasteBridge is a taste-graph agent: name three things you love and it builds you a cross-domain evening — a film, a table, a soundtrack, a book — where every pick cites its Qloo affinity score and the tags it shares with your seeds. Push back in plain words ("cheaper", "no horror", "more date night") and it re-bridges with a receipt naming every change.

Inspiration

Every recommender answers inside one silo: a movie app suggests movies, a food app suggests food. But taste is one thing — the person who loves Blade Runner 2049 has a dinner, a playlist, and a bookshelf that rhyme with it, and nobody assembles the whole evening. Qloo's Taste AI is the rare API that models taste across domains, which makes the missing product buildable: an agent that treats your taste as the input and a coherent night as the output — and shows its evidence, so "why this?" is answered with the graph, not a shrug.

What it does

  1. Seed — the user names 2–5 favorites (films by default; shows, artists, books, places, games, podcasts, and brands also resolve).
  2. Bridge — TasteBridge resolves each name to a Qloo entity via GET /search, then for each of four evening slots (Watch / Eat & drink / Listen / Read) calls GET /v2/insights with filter.type set to the slot domain and the seeds as signal.interests.entities. The top-scoring non-seed entity wins the slot.
  3. Evidence — every card shows the pick's Qloo affinity (its correlation to your seeds, 0–1), its tag chips — amber when a tag also appears in your seeds' fingerprint — and a plain-language "why this fits" sentence built from the actual shared tags.
  4. Refine — plain-words feedback re-bridges the evening: bans ("no horror" excludes any pick carrying the tag), budget and vibe steers ("cheaper", "date night", "guys night", "cozier", "darker" — resolved to real Qloo tag IDs via GET /v2/tags and added as insight signals), and rotations ("a different movie"). The response includes a change receipt that names each swap ("Eat & drink: Izakaya (Japanese pub) → Ramen bar.") and honestly reports when a constraint changed nothing.

If the Qloo API key is absent or the API cannot be reached from the host, the app runs the identical loop over a small built-in sample taste graph and labels everything SAMPLE — banner in the UI, mode in every API response, reason in the session notice. Sample data is never dressed up as live Qloo output.

How we built it

  • Zero-dependency Python (standard library only): a threaded http.server app with four routes — GET / (single-page UI), GET /api/status, POST /api/bridge, POST /api/refine. No build step, no pip installs; it runs anywhere Python 3 runs.
  • A documented-shape Qloo client (qloo.py) implementing the LLM Hackathon Developer Guide exactly: GET-with-query-string requests, X-Api-Key header, /search for entity resolution, /v2/tags for tag resolution, /v2/insights with filter.type + signal.interests.*. Transport is curl-first with a urllib fallback — this sandbox's egress proxy stalls long urllib reads — with generous timeouts and bounded retries, because the hackathon cluster answers slowly.
  • The agent loop (agent.py): exact-name-preferred seed resolution, seed/tag fingerprinting, per-slot candidate ranking with ban/prefer steering, evidence-sentence generation from shared tags, a small natural-language feedback parser, and receipt diffing between evening versions. Session state round-trips through the client so constraints accumulate across refine rounds.
  • Honesty as a feature: the API key lives server-side only; no Qloo response data is persisted to disk or committed to the repo (per the developer guide); SAMPLE mode is labeled in the badge, the banner, the API payload, and the narration of the demo film.

Challenges we ran into

  • The hackathon cluster would not answer from our build sandbox — for hours. Unauthenticated calls returned 401 in ~3 seconds (host up, auth gate live), but authenticated /search and /v2/insights calls repeatedly timed out with zero bytes received — across 30–280 s timeouts, both transports, and two generations of background probers (the first died when the sandbox wiped /tmp mid-run; its replacement lives in durable storage). We built against the developer guide's documented shapes, made the client patient and retrying, and built the labeled SAMPLE fallback so the loop stayed truthful about its mode. Then, at 23:13 EDT on build night, the durable prober's first attempt landed an HTTP 200 — and every call since has answered in seconds. The demo film is a genuinely live session.
  • Documented-shape traps: insights is GET with a query string (a POST body fails); every insights call needs a filter.type; "music" and "food_and_drink" are not valid entity types (artists and places are); invalid parameters are silently ignored, so empty results never prove an error. The client encodes all of this so the agent layer cannot make those mistakes.
  • Making refine honest: a steer that matches nothing must say so, and a ban that empties a pool must relax taste bans before it ever relaxes the "never suggest the seeds themselves" rule. The receipt only ever names changes that actually happened.

Accomplishments that we're proud of

  • A complete agent loop — seed → bridge → evidence → refine → receipt — in ~1,500 lines of dependency-free Python, every line commented in plain English.
  • Evidence-first recommendations: affinity scores and shared-tag chips on every pick, with the "why" sentence generated from the graph's own tags.
  • A refine loop that understands bans, budgets, and vibes, resolves vibes to real Qloo tags, and proves its work with a named change receipt.
  • An honesty architecture that survived a hostile network: the app never once presented sample data as live, and the failure mode is a labeled feature instead of a broken demo.

What we learned

Taste is a graph problem, not a list problem: the moment picks carry shared-tag evidence, users stop asking "why this?" — the interface answers it inline. We also learned that building against a documented API means encoding the documentation's traps (GET-not-POST, valid type URNs, silent parameter ignoring) into the client, not rediscovering them at runtime.

What's next for TasteBridge

  • Group mode: merge several people's seed sets into one evening nobody hates (intersection affinities + per-person vetoes).
  • Local grounding: pass the winning place types to a maps lookup so "izakaya" becomes the actual izakaya two miles away.
  • A "surprise me" dial that walks one hop further out the graph each turn.

Built With

Share this project:

Updates

Submission history