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:
- Practice and apply the objectives learned in the two tutorials
- On the back end, generate a new SSE event to pass lat/lon data as a distinct event
- On the front end, handle a new SSE lat/lon event and reposition displayed map to the new lat/lon
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:
- a title bar with the title
Where in the world?, - a map that initially shows the device’s current location as a “blue dot” on the map, and,
- 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.”
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:
- Upon loading, the app front end does three things automatically:
- displays a map showing the user’s current location, and
-
initiates the game by sending the following
systemprompt to Ollama:"Think of a city. Do not tell the user the name of the city. Ask the user if they are ready to start. If so, give the user 2 to 3 hints at a time and let the user guess. If the user guessed wrong, give them more hints about the city they guessed wrong. When they guessed right, say the following **in the provided format**, along with the lat/lon of the city, and then ask the user if they want to play again. If yes, give them the 2 to 3 hints for the next city:WINNER!!!:lat:lon:"
For the app to work correctly, it is imperative that you specify the exact format for the winning notification, including the colons.
TIP: Be careful with the wording of your prompt. For example,
opening your session with a “Test” or “This is a test” prompt could lead
to confusing subsequent interaction with the model. - following up on sending the
systemprompt, makes a call tochatterd’s/llmchatAPI, with a single “Yes” word as its prompt to the model. This will get the model to start the game and return the first set of hints for the mystery city. This is the same/llmchatAPI used in thellmChattutorial, however you will be making modifications to both its front-end and back-end code.
-
Hints from the LLM will be displayed above the text box. User enters their guess in the textbox and clicks “Send” to post it to the LLM.
-
If user guess is wrong, LLM replies with another set of hints and user can enter another guess. (LLM will reveal the solution if user enters “I give up,” or some other similar statement.)
- If user guess is right, the app will receive the lat/lon of the city
in a SSE
LatLonevent. The app then moves the map camera to center on the lat/lon of the city.
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
-
Upon receiving a message, the back end checks for existence of an
appIDon the message. Notably, the back end does not check for uniqueness ofappIDs; rather, anappIDcan be re-used to resume a conversation with Ollama. -
The back end stores each message from a front end, sent to either the
/llmprepor/llmchatAPI, in its PostgreSQL database, tagged with the message’sappID. When Ollama responds to a prompt, the back end also stores the completion tagged with the sameappID. When forwarding a prompt to Ollama, the back end retrieves and sends all messages with the sameappID, in chronological order, to Ollama, forming the prompt’s context/history. -
Ollama streams replies as newline-delimited JSON (NDJSON) streams. The back end transforms these into SSE streams. Prior-and-post an NDJSON stream, the back end communicates errors to the front end as HTTP errors. Once an NDJSON stream has started, however, any error, e.g., database error, will be communicated to the front end as an SSE
errorevent. -
In the initial system prompt sent to Ollama, when the user made a correct guess, the model is instructed to send a message of the following format: “WINNER!!!:lat:lon:”, where the
latandlonare of the city in question. In addition to forwarding this message, the back end also sends an SSELatLonevent with a JSON Object containing the lat/lon in the event’s data line.
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.
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
Leave your chatterd running until you have received your tutorial grade.
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 |