Created my Personal AI Fitness Trainer in 2 Days

Bird person working out at home talking to phone with remote AI personal trainer in the corner.
Image by Annie Ruygt

We’re Fly.io. We run apps for our users on hardware we host around the world. Fly.io happens to be a great place to run Phoenix applications. Check out how to get started!

This post introduces how I created an AI Personal Fitness Trainer named “Max” that acts in a way that works best for me. In 2 days I had a new workout buddy and it has reignited my fitness excitement! I quickly had a weekly fitness program that works towards my goals and a trainer who records my workout information for me. Check out a video DEMO of it working and, most importantly, check out the demo Elixir project to get and customize your own fitness buddy!

First, to know what we’re talking about, get an overview of how it works and to see the AI Fitness Trainer in action, check out the video.

Want to dig in right away? You can find the Elixir LangChain Demo project here.

DEMO NOTE

The initial released DEMO project was from a weekend spike and more refactoring, tuning and cleanup would improve it. But that’s partly the point, in a short time, we can create tools that directly impact our lives and make a positive difference. 💪

What is a conversational agent?

We’re talking about an “Agent” here. An agent is described as:

Agent: a language model is used as a reasoning engine to determine which actions to take and in which order.

There are different kinds of agents, but for our AI Fitness Trainer, we’re using a “conversational agent”. That means the user interacts directly with it and most of the agent’s responses are shown to the user.

Let’s describe how this works in practice. We’re using the Elixir LangChain library to talk to ChatGPT 4 through an API. To run this locally, you’ll need your own OpenAI API key.

With an API key, the Elixir LangChain library helps with the following:

  • Wrapping our custom context around an LLM (Large Language Model) like ChatGPT.
  • Exposing “functions” to the LLM that it can call in our application.
  • Template user data and instructions into a single user message sent to the LLM.
  • Talking to ChatGPT and processing the results.

The following diagram gives an overview of the pieces:

Diagram with ChatGPT and MyApp boundaries. Then an Agent boundary that crosses from MyApp to ChatGPT. Includes context around ChatGPT and functions in MyApp that are available to the Agent.

Make note of the context wrapped around ChatGPT and the functions shared with the agent that have controlled access to the application’s data.

Neither the Context nor the available functions are visible to the user. Let’s focus a bit more on those “hidden” pieces next.

Limit what the user sees

We can think of our application as having two separate but linked conversations going on a the same time.

  1. The user has a conversation with our app.
  2. Our application has a conversation directly with the LLM.

There’s a lot of overlap in these conversations, but there is more to the conversation with the LLM going on behind the scenes than what the user sees.

It starts with the Context we define around ChatGPT for how it should behave. The user doesn’t see any of those instructions. We frequently put those instructions in a system message, instructing the system how to act.

Then, if and when the LLM decides to execute a function from our app, we don’t display the details of the request or our application’s response to the user. Handling those function requests is something the Elixir LangChain library wraps up for us in a Function.

There may be other messages that our app needs for talking to the LLM that we don’t want displayed to the user. For this reason, we’ll create a ChatMessage struct for displaying messages in the UI, and internally, we’ll use a collection of LangChain.Message structs for talking with the LLM. This separation gives us greater control over what the user experiences.

Here’s a diagram to help visualize the different kinds of messages and how what’s displayed to the user differs from what’s happening behind the scenes.

Left side is "actual" and right side is "displayed". Shows message examples and how they may be represented on the other side.

The takeaway here is that there are essentially two versions of the same conversation being represented and the Elixir LangChain library helps manage the one with ChatGPT.

Okay, so we know there’s two representations of the conversation, but how do we make ChatGPT act like a fitness trainer?

Prompt Engineering

The term “prompt engineering” has been thrown around so much it’s almost become a joke, but really, that’s a lot of what we’re doing. We’re describing how the LLM should behave. We put boundaries on it, constrain it to a specific context, assign it goals, and provide examples of how we want it to respond.

Prompt Engineering gives us more consistent behavior. If we don’t do it, then our personal trainer might lean British one time, referencing kilogram weights, then the next time it sounds American and use pounds. Or the AI’s personality feels flat, formal, and not encouraging. Or it doesn’t follow-up with our previous workouts.

Prompt Engineering really is just a different kind of coding. It’s providing context, limits, instructions, conditions, goals and examples of desired outputs.

While building this, my AI trainer kept formatting my weekly workout schedule in a seemingly random format each time. So I specified the format to use and where different pieces of data should go so I always get a consistent result. Now it’s consistently how I want it!

The process of prompt engineering takes time and quite a few experiments. We may run it 10 times and it’s on the 11th that we get an unexpected behavior we like and want more of (which we can specify) or we’ll see it do something we want to avoid.

Honestly, that’s part of the fun!

It’s not perfect… but it’s still worth it.

My Proof of Concept Fitness Agent isn’t perfect. The more I use it over time, the more ways I see it can be improved.

But here’s the point

In a short amount of time, I created something that has a meaningful, positive impact in my daily life. I created a tool that I interact with in a natural, conversational way and it helps me stay focused on my goals, and even tracks my progress. And I have fun doing it too!

It make me wonder what else I might want to create… 🤔

Customize your personal AI fitness trainer!

In the demo project, you’ll find a big blob of text that’s submitted as the “system” message for the LLM. It defines how I wanted my AI trainer to behave. This is where you can have fun! Customize your own trainer to be what you want. Here are some suggestions to get you thinking:

  • Would you prefer a male or female trainer?
  • Do you want them to be encouraging and positive or are you more motivated by a drill sergeant personality?
  • Do you want them to focus on strength training, endurance, flexibility, or something else?
  • Do you want them to use a specific training style? Powerlifting? Bodybuilding for appearance? Crossfit?
  • Do you want them to be American, British or something else?
  • Do you want them to speak really casual and use slang? You need to instruct them to.
  • Do you want them to assume pounds or kilograms? Assigning a nationality brings this kind of context.

Oh the fun you can have! And, in the end, you are the winner. You get an AI Personal Fitness Trainer that works the way you want, and if you use it, you might even reach some personal fitness goals! Now, wouldn’t that be cool?

Fly.io ❤️ Elixir

Fly.io is a great way to run your Phoenix LiveView apps. It’s really easy to get started. You can be running in minutes.

Deploy a Phoenix app today!