Inspiration

A small venue programmes by gut feeling: the owner's playlist, the film the bartender likes. The owner does know one thing precisely, though: what the regulars talk about. "They love Wes Anderson, Phoebe Bridgers and Sally Rooney" is a taste signal, and turning signals like that into what the same people love in other domains is exactly what Qloo's taste graph does. A language model alone answers from what has been written about those names. We wanted the answer that comes from what their audiences actually share, and we wanted the host to be able to see the difference.

What it does

The host writes one paragraph: the room, the city, what the regulars love. The agent then

  1. resolves those favourites to Qloo entities,
  2. asks the taste graph, with all the favourites together as signals, for artists, films, books, podcasts and brands, and for places in the host's own city by category (record stores, bookshops, galleries),
  3. writes a short programme in which every pick shows its affinity score, the favourite that drove it and by how much, a picture, and for places the neighbourhood, rating and a link,
  4. removes any pick the taste graph did not return and lists it as removed,
  5. then asks the same model the same question with no tools and looks each of its suggestions up in the graph: "Second opinion" shows which the graph chose too, which it scores lower than the programme's picks, which it does not tie to this crowd, and which it cannot find at all.

For the Lisbon wine bar example, one live run returned 20 picks, among them Amor Records and Livraria da Travessa. In that run the model's own 12 suggestions split like this: 1 was in the programme, 9 were in the graph but scored below every pick of the same kind in the programme, 1 scored within the programme's range, and 1 was not in the graph.

The same six tools are also published as an MCP server, so any MCP client can use them without our page.

How we built it

  • Qloo: /search, /v2/tags, /v2/audiences and /v2/insights, through a small standard-library client that refuses undocumented parameter names, because the API ignores them silently.
  • Tools: find_entities, find_tags, list_audiences, recommend, bridge_tastes, score_candidates, defined once with JSON schemas and used by both the MCP server and the built-in agent.
  • Agent: a tool-calling loop on Gemini (any OpenAI-style model also works) that issues independent lookups in one turn, so a programme takes three or four model requests.
  • Guard rails in code, not in the prompt: picks are checked against tool results; ids no tool returned are refused before they reach the API; an id placed under the wrong argument is moved to the right one; a search hit whose name does not match what was asked counts as a miss.
  • Second opinion: filter.results.entities scores the unaided suggestions against the same signals the programme used.
  • Page: one HTML file with no framework. While the agent works, every question it puts to the taste graph appears as a readable line with the number of results, and a status line counts the seconds. FastAPI behind it; keys stay on the server.
  • 67 offline tests run on every push; the demo runs on a free Render instance.

Challenges we ran into

Each of these came from a real run against the live API, and each is now handled in code and covered by a test.

  • A city name is read as its wider region. "Lisbon" returned a surf hotel an hour up the coast, and a radius changed nothing. Places are now checked against their own address fields.
  • A city alone returns every kind of venue, so place lookups carry a category tag.
  • The model once put entity ids under tag_ids. The API did not object; it returned unrelated results at one flat affinity. Nothing looked broken except the picks.
  • The model once invented ids outright, and once altered a favourite's name so that search matched something unrelated.
  • Image URLs point at originals, one of them 8101 by 12000 pixels.
  • The first request on the hosted demo failed because the model was overloaded. The adapter now keeps several models and moves on when one is busy or slow.

Accomplishments that we're proud of

The page does not claim the taste graph is better than a language model; it measures it, per request, with Qloo's own scores. Sometimes the model's guesses score well. For local places they mostly do not, and the host can see which.

What we learned

The taste graph answers a different question from the one a language model answers, and the difference is largest where it matters most to a venue: the real places around it. We also learned to treat an agent's tool arguments as untrusted input.

What's next

Events as a target (what to book, not only what to play), audience segments as signals for hosts who know who comes in but not what they love, and a weekly re-run that tells the host what changed.

Built With

  • fastapi
  • gemini
  • github-actions
  • model-context-protocol
  • python
  • qloo
  • render
Share this project:

Updates

Submission history