The problem

Two example travelers (illustrative inputs, not real users). Mika is on her third trip to Japan. She has done Shibuya Crossing and Senso-ji. What she wants now is the Tokyo that fits her: Ryuichi Sakamoto on her headphones, Lost in Translation on her shelf, a weakness for ramen counters and quiet coffee. Every itinerary tool she tries gives her the same top-ten list again, or an LLM's friendly paragraph with a few places she cannot verify and no way to see why they were picked.

Tom is on his second trip to Kyoto. He skipped the temples last time. He loves Cornelius, Spirited Away and Tampopo, and he asks one thing: where in this city would someone with my taste actually go?

Both want a day that is shaped by taste, and an answer they can check. That is the problem Taste Day Japan works on.

What it does

You enter up to three favorite artists, films and foods and pick Tokyo or Kyoto. An agent looks each favorite up in Qloo, asks Qloo for places in that city that fit the combined taste, and builds a one-day plan with five stops: morning, lunch, afternoon, dinner, evening.

Each stop is a card on a map with:

  • the Qloo affinity score as a bar,
  • the taste signals Qloo linked the place to, named in the traveler's own words (for example "Qloo links this place to: Ryuichi Sakamoto (0.38), Lost in Translation (0.31), Radiohead (0.31)"),
  • the distance from the previous stop.

The tool calls the agent made are listed below the plan (collapsed by default), so a reader can see which Qloo calls produced the day.

Who else could use it

The same screen works for people who plan days for others: a travel agency building a taste-matched day for a client, a hotel concierge who has a guest's three favorite artists and ten minutes, or a city tourism office that wants to show visitors parts of a city beyond the landmark list. We have not talked to any of them and make no claim about demand. The point is that the output is a short plan with its evidence attached, which is something a professional can hand over and defend.

What changes without Qloo

The hackathon asks whether the project would work the same without Qloo. We tried to build it so that it would not. Where the evidence is thin we say so.

Step LLM only With Qloo in this project
Understand "Cornelius" or "Tampopo" Matches the name from training text /search returns a Qloo entity ID, the handle everything else uses
Choose places for the combined taste of several artists and films Writes places that sound plausible for the described person /v2/insights returns places in that city ranked by affinity to those entities
Explain a stop Writes a reason that sounds right; nothing checks it The reason is built from Qloo's explainability scores, mapped back to the names the traveler typed
Stay inside real places Can name venues that do not exist (13 of our 15 LLM-only stops were found by name, 2 unconfirmed) Only places Qloo returned can enter the plan; the agent has no other source (all 14 live stops carry Qloo coordinates)
Cross domains (music and film to places) Associates by wording Qloo's taste graph links entities across domains; that link is the signal

This table describes the design. It is not a measured result. We prepared a controlled comparison (same travelers, LLM-only vs Qloo-grounded, scored on whether places exist and whether reasons tie to the input) and ran the LLM-only side on 2026-10-03: a Gemini model (gemini-3.5-flash, temperature 0) gave 15 stops for three travelers; 13 were found in OpenStreetMap by name (the other two are one place found under its Japanese name and one we could not confirm), and all 15 reasons mentioned something the traveler had typed. In other words, a current LLM did not obviously invent places on these inputs, so we do not argue that Qloo is needed to prevent invention. The Qloo side was run on 2026-10-06 against the live hackathon API with the same three travelers, and the result is mixed. The places are different from the LLM-only ones (0 of 14 overlap) and each comes with coordinates and per-input scores from Qloo. But by the same OpenStreetMap name lookup only 9 of 14 Qloo places were found (against 13 of 15 for the LLM), where found the OSM point is within 0.5 km of the coordinates Qloo gave, and the scores within one day are close to each other (0.23 to 0.41 for every stop), so they show which favorites Qloo weighted, not why this place beat another. Food matching is loose: a "ramen" query in Tokyo returned an Italian restaurant first, and we do not claim that the agent picks the right cuisine. Whether the Qloo places suit the traveler better than the LLM's was not measured. We do not claim a win.

How Qloo is used

  • /search turns names into Qloo entity IDs (artists, films).
  • /v2/tags finds tag IDs for food keywords.
  • /v2/insights with filter.type=urn:entity:place, filter.location.query (the city), signal.interests.entities (the IDs from /search), filter.tags (food tags, for lunch and dinner) and feature.explainability=true returns places with an affinity score and the signals that contributed.
  • Meals and non-meal stops come from different queries (food-tag queries for lunch and dinner, an untagged query with food places dropped for the other slots), so a day is not five restaurants.

Only these three endpoints are reachable. /recommendations and /recs are blocked in code and covered by a test, so the project cannot quietly depend on them.

Engineering choices that matter

This is not a prompt wrapped around one API call. The parts below are the reason the output can be trusted.

  • IDs cannot be invented. The agent is a tool-calling loop, but every entity or tag ID it passes to Qloo must have been returned by an earlier /search or /v2/tags call in the same run. The tool layer rejects anything else, so an LLM cannot fabricate a taste signal.
  • The itinerary rule is deterministic, not generated. The LLM decides which Qloo tools to call; it does not choose the stops. For each slot the rule takes the highest affinity among the top 6 unused places, minus 0.02 per km from the previous stop. Lunch and dinner avoid the same food tag when they can. No place appears twice. The same Qloo data always produces the same day, and the rule is tested.
  • The key stays on the server. It is sent in the X-Api-Key header to the hackathon base URL, never in a URL, never to the browser, never in a response body. A test checks that responses do not contain it. Errors are trimmed so they cannot echo headers.
  • Input limits and validation. Three items per list, 60 characters each, so a public demo cannot be used to burn the Qloo quota with large requests.
  • No hidden dependency. The core is plain JavaScript with no packages. The same code runs in Node (local) and in a Cloudflare Pages Function (the hosted demo).
  • The LLM is swappable. A deterministic mock planner (default), Anthropic, or any OpenAI-compatible API drive the same tools. LLM_MODEL has no default on purpose.
  • Honest modes. The page shows a banner saying whether it is on live Qloo data or on sample replay data, and map pins from sample data are drawn grey and dashed.

Design

The page is one screen: the form, then a map of the five stops joined by a line, then a card per stop with the affinity bar and the reasons, then the collapsed tool-call record. The map uses OpenStreetMap tiles through Leaflet and shows the required attribution. It works at phone width.

How we built it

JavaScript with no dependencies. The core (Qloo client, tools, agent loop, itinerary rule) is shared by the local server and the Pages Function. 21 automated tests cover the client, the agent loop, the itinerary rule, key handling and the coordinates the map needs. The tests run without a network or a key.

Where the project stands (2026-10-06)

The Qloo hackathon API key arrived on 2026-10-03 and the hosted demo has run on live Qloo data since 2026-10-06 (the page banner says "Live Qloo data"). The planner in the public demo is the deterministic mock planner (no LLM key is set), so the language model does not choose the stops anyway; the Qloo calls, the rule and the explanation are what you see. The code to use an LLM planner is in the repository but was not run in the public demo.

Challenges

  • The Qloo client was first written from the documentation; the real responses differed in small ways, so the parsers and tests were checked against 15 recorded real responses. Places carry names and coordinates, and tags come as free text.
  • Food matching is loose. Place queries with a food tag return restaurants of other cuisines (an Italian restaurant first for "ramen" in Tokyo), so the lunch and dinner stops are only roughly what was asked. We left it visible rather than hiding it.
  • Few non-food candidates in a city: Kyoto days can end with four stops because the candidates run out before the evening slot.
  • The per-input scores from the explainability feature are close to each other within a day, so they weakly separate one place from another.

What we learned

  • Qloo's cross-domain signal does move the picks away from the famous list: for Ryuichi Sakamoto and Lost in Translation it returned a cathedral, a converted bathhouse gallery and a Western art museum rather than the obvious venues. Whether that is better taste matching, we could not measure.
  • Building the day with a deterministic rule on top of Qloo's scores makes the output repeatable and testable, and it made the loose spots (food, thin evenings) easy to see.
  • Comparing against an LLM-only baseline removed a claim we had planned to make: the LLM did not invent places in our runs.

What's next

  • Choose the city itself with Qloo destination insights (urn:entity:destination).
  • Keep a day inside one or two districts using area filters.
  • Let a planner enter several travelers and show the overlap of their tastes.

AI use disclosure

Written with Claude Code (Anthropic) under the author's direction. The LLM-only baseline used a Gemini model (gemini-3.5-flash).

Built With

Share this project:

Updates

Submission history