Inspiration
What it does
How we built it
Challenges we ran into
Accomplishments that we're proud of
What we learned
What's next for City Break
What it does
City Break is a small planning agent with one job: turn your tastes into one concrete short-break recommendation you can act on. You type up to five things you love - an artist, a film, a dish, a brand - and the agent queries the Qloo Insights API in three bounded steps: it resolves each taste to a Qloo entity, ranks destinations by audience taste affinity (take-10 shortlist), then pulls real venues inside the winning city, scoped by the winning destination's own geohash. The result is one matched destination, two or three runner-ups, and up to ten matched venues - each number traceable.
The agent is deliberately deterministic. Instead of a free-form LLM loop, the planning policy is fixed and auditable: search, insights, insights. Every tool call is recorded and shown in the UI ("How this was built" panel), so every recommendation ships with visible provenance - which tastes drove it, what the affinity scores are, and what the API was asked.
Scores are presented for what they are: aggregate audience-level cultural-affinity signals between tastes and places - statistical tendencies, not predictions about any individual. The footer and per-plan provenance note say exactly that. Venue lists are described as reflecting Qloo data coverage, not exhaustive directories.
If the Qloo service is unreachable, the app degrades gracefully to a clearly-labelled illustrative sample (every name marked, entity URNs namespaced urn:sample:*) so the product stays explorable - and the labelling is pinned by tests, not vibes.
How we built it
- Python serverless functions on Vercel (stdlib-only HTTP handlers; no framework lock-in).
- A deterministic agent module (planner) wrapping the Qloo Insights API as three tools: /search (entity resolution), /v2/insights on urn:entity:destination (destination shortlist ranked by taste affinity), /v2/insights on urn:entity:place scoped by the winner's geohash prefix (venues inside the winning city).
- Param-style probing at runtime: the client tests comma-joined vs repeated-array insight parameters once and remembers the style that returns data. Live, comma-joined is the style the API accepts; repeated-array trips a confusing "all entities invalid" error.
- Live-verified entity handling: taste signals must be real Qloo entity IDs from /search (freeform strings are rejected), affinity scores are read from query.affinity, and destination entities carry their own geohash - which is what scopes the venue search.
- Vanilla JS frontend: no build step, accessible, fast; affinity and evidence chips per recommendation; full agent tool trace rendered per plan.
- 29 automated tests: planner and client units (mocked Qloo responses) plus a frontend contract suite that parses the shipped index.html and pins the sample-mode shape, labelling and trigger scope. Live-shape regressions are pinned by dedicated tests.
Challenges we ran into
- The docs we built from before key issuance and the live hackathon API disagreed in several places: the supported destination type is urn:entity:destination (our earlier urn:entity:locality calls 400), entities carry entity_id rather than urn/uuid fields, affinity lives under query, and /search omits the success flag and returns results as a bare list. We fixed every shape against the live API with a real key and pinned them with regression tests before submitting.
- Freeform signal strings fail with a generic "at least one valid signal" error - the fix (resolve tastes to entity IDs first) is exactly what makes the agent agent-shaped.
- Keeping the demo honest without the key (the fallback had to be impossible to mistake for live data, which is why every sample name carries an "illustrative" marker), and equally honest with the key: no sample content renders once live data flows.
Accomplishments we're proud of
- Every recommendation is explainable: input tastes, matched entities, per-item evidence and the raw tool trace are all visible in one screen.
- Provenance and responsible-data language ship in the product itself, not in the readme.
- The whole service is two functions and a static page: fast to audit, fast to trust.
What we learned
- Aggregate taste-affinity data is a great fit for travel shortlisting: it converts soft preferences ("I love Radiohead and ramen") into ranked, concrete places.
- Silent-ignore APIs need contract tests more than happy-path demos.
What's next
- Live key onboarding (server-side only; the key never touches the browser).
- Date-awareness (events/gig listings near the break window) and multi-city itineraries.
Reproducibility
- Public repo, MIT license (visible in the GitHub About panel), single clean history.
- 29/29 unit + contract tests green (python3 -m unittest discover -s tests).
- Deploy: npx vercel deploy --prod (set QLOO_API_KEY as a server-side env var in the dashboard).
- The planning policy is deterministic: same inputs + same Qloo data = same plan.
Built With
- python
- qloo-insights-api
- serverless
- vanilla-javascript
- vercel
Log in or sign up for Devpost to join the conversation.