Project 2: llmPlay: Where in the World?

Course Schedule

We will build an app that let users play guess-the-city game with an LLM. When the user guessed right, the LLM sends the city’s lat/lon to the app, which then displays the city centered on a map.

Due to the limitations of smaller LLMs, we found the gemma3:12b model to be the smallest that can play this game. We have provided access to this model through mada.eecs.umich.edu for the completion of this assignment. See the Testing section below.

Treat your messages sent to chatterd and Ollama as public utterances with no reasonable expectation of privacy and know that these are recorded for the purposes of carrying out a contextual interaction with Ollama and are potentially shared with everyone using chatterd.

Partial credits

To help you with time management, break your approach down to smaller tasks, and to help structure your solution, you can earn partial credits by completing the following two tutorials by their deadlines, as listed in the Course Schedule:

Completing and submitting the tutorials by their respective deadlines are optional, though the features and functionalities embodied in the tutorials are REQUIRED of this project.

This project may be completed individually or in teams of at most 2. You can partner differently for each project. Only ONE member of a team needs to submit the project and its tutorials.

Objectives

In addition to the objectives listed in the llmChat and Maps tutorials, this assignment has the following objectives:

Features and requirements

Your app must provide the following features and satisfy the following requirements, including those in applicable documents listed in “Implementation guidelines”, to receive full credit.

Front-end UI

As can be seen in the demo video in the front-end specs, the app consists of a single screen with the following UI elements:

  1. a title bar with the title Where in the world?,
  2. a map that initially shows the device’s current location as a “blue dot” on the map, and,
  3. at the bottom of the screen:
    • a text box spanning the left and middle part of the input area,
    • a “Send” button to the right of the textbox showing a “paper plane” icon. This button is enabled only when the text box is not empty and no networking session is in progress.

      When the button is “disabled,” it is grayed out and tapping on it has no effect.

      While there is a networking session in progress, that is, while waiting for Ollama’s hints or evaluation of user’s guesses, the “Send” button changes from showing a “paper plane” to showing an animated “loading” circle.

UI Design

One can easily spend a whole weekend (or longer) getting the UI “just right.”

:point_right: Remember: we won’t be grading you on how beautiful your UI looks nor how precisely it matches the one shown on the video demo. You’re free to design your UI differently, so long as all indicated UI elements are fully visible on the screen, non overlapping, and functioning as specified.

Front-end UX

As demonstrated in the demo videos:

API

We are re-using the /llmchat API from the llmChat tutorial, with the following modification: when the user made a correct guess in the game, the /llmchat API returns a LatLon SSE event with the following data line:

event: latlon
data: { "lat": double, "lon": double }

We will not be using the /postmaps and /getmaps APIs from the Maps tutorial.

Back-end infrastructures

Implementation and submission guidelines

Back end

For the back end, you should build off the code base from the llmChat tutorial.

If you have not implemented the /llmprep API, you should do so first.

Then you need to make one change to the llmchat() URL handler: once an NDJSON stream from Ollama concludes, after inserting the full response into the database, check the response for the “WINNER!!!:lat:lon:” string. If it exists, split the full response using “:” as the delimiter and retrieve the lat/lon as the second and third elements of the split string. The function to split a string by a delimiter is invariably called split() in all the languages used in the back end (with capitalization of the first letter in Go). To check wether a substring is in a string, you use Contains() in Go, in in Python, contains() in Rust, and includes() in TypeScript.

Structured output?

You can specify a structed (JSON) format that the model must use in its reponse. For example, you can specify that the output must be structured with a field called “content.” Unfortunately, Ollama returns structured output as a stringified JSON object in Ollama’s response’s messages array. This means you cannot parse the structured output until the whole message string has arrived, that is, no streaming and no horizontal scrolling of content as tokens arrive.

And that’s all you need to do to prepare the backend! As usual, git commit your changes to your chatterd source files with the commit message, "llmplay back end", and push your changes to your git repo.

Front end

Detailed guidelines for the two front-end platforms are available in the following separate specs:

Testing

Front end

To test the front end, we found LLM model smaller than gemma3:12b to not be able to play this game. To test your front end, you will use mada.eecs.umich.edu, specifying gemma3:12b as the model to use. Unfortunately, Ollama can only hold one conversation at a time. If you have a host with sufficient resources, you may want to pull model gemma3:12b (8GB) to Ollama running on your host for your own use.

Back end

Once you have a working front end, to test your implementation of the back end on a *-micro isntance, specify gemma3:270m as the model for your Ollama. Set the following as your initial system prompt, “Repeat after user, verbatim.” Then DO NOT follow up by sending “Yes” to /llmchat as described in the “Front-end UX” section above. Instead, in the text box of your app enter, “WINNER!!!:39.91:-79.47”. The system prompt should cause the model to echo this string verbatim.

A working back end should recognize the winning signal echoed by the model and return it along with a LatLon SSE event. A working front end then immediately moves its map camera to center on the vicinity of Fallingwater, PA.

:point_right:WARNING: You will not get full credit if your front end is not set up to work with your back end!

Everytime you rebuild your Go or Rust server or make changes to either of your JavaScript or Python files, you need to restart chatterd:

server$ sudo systemctl restart chatterd

:warning:Leave your chatterd running until you have received your tutorial grade.

:point_right:TIP:

server$ sudo systemctl status chatterd

is your BEST FRIEND in debugging your server. If you get an HTTP error code 500 Internal Server Error or if you just don’t know whether your HTTP request has made it to the server, first thing you do is run sudo systemctl status chatterd on your server and study its output.

If you’re running a Python server, it also shows error messages from your Python code, including any debug printouts from your code. The command systemctl status chatterd is by far the most useful go-to tool to diagnose your back-end server problem.

References


Prepared by Chenglin Li, Xin Jie ‘Joyce’ Liu, Sugih Jamin Last updated: February 26th, 2026