Build an AI Personal Trainer with LangChain Agents and SendGrid
Time to read: 9 minutes
Can AI help me run a faster marathon?
Problem: I play a lot of tennis and run, bike, and walk as my forms of transit, so I have never trained for a half marathon or 10k. However, I KNOW I must train to run a marathon. As a developer, I turned to code, wondering how AI could help me run a faster marathon.
Solution: Read on to learn how to build a personal marathon training plan generator Streamlit-hosted app from personal workout data using the Strava API, LangChain agents, chains, few-shot prompt templating, PromptTemplates, OpenAI, and SendGrid. This can also be applied to personal training, nutrition, and more!
In the Streamlit page above, a user inputs their Strava API token so the app can access their workout data, the date they want to begin training, and the marathon (or race) day. When they click Submit, a custom training plan is generated.
You can test the app out here using a Strava API token generated by following the directions under Strava Activity Webhook Authentication in this tutorial on querying data with LangChain agents.
Do you prefer learning via video more? Check out this TikTok relating to this tutorial--massive thank you to Twilio Community Engagement Manager Sandra Mendez for spearheading this video.
Understanding LangChain Agents
This meaty app uses many features of LangChain, such as agents: agents use LLMs to decide what actions should be taken and they have access to tools like web search or calculators which can be packaged into a logical loop of operations.
For more background on LangChain, you can read this blog post here.
Prerequisites
- A free SendGrid account
- Python installed - download Python here
- An email address to test out this project
- OpenAI Account – make an OpenAI Account here
- A Strava account - sign up for a Strava account if you don't have one already
- A Strava app and API key - follow directions here under Setup the Strava API
- A Strava Auth token - follow directions here under Strava Activity Webhook Authentication
You need a Strava app and API key–following the directions above will provide you with the necessary Strava Client Secret in order to generate a Strava Access Token to make a request to access your Strava data. Different Strava API app values include:
- Client ID: Your application ID
- Client Secret: Your client secret (please keep this confidential)
- Authorization/Access token: Your Strava API app's authorization token will need to be regenerated every six hours (should be confidential)
Next, after making an OpenAI account if you don't have one already, you can get an OpenAI API Key here by clicking on + Create new secret key. Then, grab a SendGrid API key here.
The Python application will need to have access to these keys, so in your root directory (called strava-langchain
) make a .env file to store the API keys safely. The application we create will be able to import this key as an environment variable soon.
In the .env file add the following lines of text, making sure to replace <YOUR-OPENAI-API-KEY>
and <YOUR-SENDGRID-KEY>
with your actual keys. STRAVA_CLIENT_SECRET should have been used above to generate a Strava Access Token:
Your Strava Access Token will be placed into a Streamlit form. Streamlit is an open-source Python app framework that helps developers quickly create web apps for data science and machine learning.
In the root directory run the following commands to install the project's required libraries after making a virtual environment:
Make a requirements.txt
file containing the following:
Install them on the command line by running pip install -r requirements.txt
.
Then run mkdir data_files
to make a folder called data_files
to store the activity data.
We will import these in our code soon.
Few-shot prompting Examples
There are a few ways one could go about making an AI personal marathon trainer, workout trainer, or recommendation system. My college classmate-turned Redis AI engineer Sam Partee suggested using few-shot prompting, in which a few explicit examples (or "shots") guide the Large Language Model (LLM) to respond in a certain way.
Create a file named stapp.py and include some examples of marathon training plans. I used OpenAI to create these plans and then formatted them a bit because most marathon plans I found online were not copy-and-pasteable.
The complete code for that file is here on GitHub. Now let's make some custom tools to help LangChain suggest personalized workouts.
Make Custom Tools to assist LangChain Agents
Using agents allows LLMs access to tools with which LLMs can search the web, do math, run code, and more. Though LLMs are powerful, they still have trouble with certain tasks–that's why you may sometimes need to make custom tools.
LangChain offers a large selection of pre-built tools, but only so many problems can be solved using existing tools.
Create a file named request.py and include the following import statements.
Next, we set some variables such as the Strava URL to retrieve a user's Activity data, have the app access its API keys, and then chain LLMs together: the llm
variable passes the desired gpt-4-32k
model to use with ChatOpenAI and sets the temperature to 0.2–play around with this value. A higher temperature value means the LLM output will include more randomness. Lastly, pass LLM
to a math chain to perform complex math problems.
Now make some functions for the custom tools to help the LLM convert meters to miles, calculate the number of days between two dates, and get the number of days in a Pandas dataframe object.
Beneath that, add a helper function to help validate user emails.
It's time to make a Custom Prompt Template in LangChain to make constructing prompts using dynamic inputs easier.
Make Custom Prompt Template
LangChain provides a set of default prompt templates to generate prompts for different tasks. However, for some projects like this one, developers might want to create a prompt template with specific dynamic instructions for the LLM–in instances like that, you can create a custom prompt template similar to the one below right inside request.py beneath validate_email
:
That helps shape the output. Let's work on receiving the input.
Make a Streamlit web app in Python
To make a webpage to receive input about the user for their personal marathon training plan, we will use Streamlit.
In request.py, we set a title, subheader, the dates that are first displayed when the date input widget is rendered, and a Streamlit form containing two text_input
fields for the user's Strava token and email, make the date input widget, and then write the input dates to the page and split up the input dates into two variables. This code goes directly beneath response = response.removeprefix("Could not parse LLM output: ").removesuffix("").
Run the command streamlit run request.py
in your terminal. An application should open up on your localhost:8080:
Beneath the last line marathon_date = dates[1]
, add the following code to validate the user input:
Get Strava data into Pandas and CSV form
The parameters and headers need to be set in order to call a Strava API request. my_dataset
contains data from just the first page of user activities, so to retrieve more activity data, loop through four pages of Strava activities and add them to my_dataset
before calling pd.json_normalize
on it to turn the semi-structured JSON data into a flat table to work with it.
Directly beneath the if VALIDANSWERS:
line, add the following code:
Next, create a new Pandas Dataframe object with specific columns containing the categories you want to consider: this tutorial is only looking at the activity name, type (bike ride, run, swim, tennis, etc), distance, moving time, total elevation gain, and start date.
The following code goes beneath activities = pd.json_normalize(my_dataset)
and creates a CSV file of the activities data after removing items from 2021 so it only includes workouts from 2022 and 2023. You can replace this with another year! It then makes a CSV file named data_files/runs.csv containing the runs and no other activity types.
Now, convert meters to miles and the moving time seconds to minutes and hours with the code below:
Next, this data will be passed to a LangChain agent.
Generate a Personal Marathon Plan with LangChain Agents
In the same request.py file, make an array called tools
containing the tools the agent has access to. Each tool gets a name, function to run, and description. The description is optional, but recommended because it's used by an agent to determine tool use.
Beneath creating the data_activity_df
variable, add the tools array:
Now, create some Pandas dataframe agents to interact with the Strava data. These agents are similar to CSV agents which load data from CSV files instead of dataframes and perform queries. You could use both and see how the output differs.
Agents can be chained together to build more complex applications. Another agent used below is of type ZERO_SHOT_REACT_DESCRIPTION
. Zero-shot means the agent functions only on the present action — it lacks memory. It uses the ReAct framework to decide which tool to use, solely based on the tool’s description.
The code runs the agents, telling them what to do and saving the output to variables for use later. Be specific with the prompts here and play around with different questions or commands.
Beneath the tools array, add:
Next, make a prompt template, a "reproducible way to generate a prompt" containing a text string ("the template"). It accepts a set of parameters from the user and generates a prompt. This mirrors the items in stapp.py's examples
array.
prefix
contains directions and context to the LLM and suffix
is the user input and output indicator. Finally, these, along with the input variables which are unique to the student the plan is being made for, are passed into FewShotPromptTemplate
.
Create a custom output parser object to help structure the response from the LLM and LLM chain, the most common type of chain. The LLM chain consists of the model and prompt. For this case it's an LLM but it could be a ChatModel. The LLM is passed to a LLMSingleActionAgent
to complete a single action and structure the response from the LLM.
This agent is passed to an AgentExecutor. An executor differs from an agent in that it is a running instance of an agent used to execute tasks according to the agent’s decisions and configured tools. In short, agents decide which tools to use and the executors execute them. The output of the Executor (which is given the user input) is the personalized marathon training plan. Beneath the FewShotPromptTemplate()
function, add the following code:
Email the Plan as a PDF using Twilio SendGrid
We access the part of the plan containing the dates, replace new lines with breaks because the ReportLab Python library to generate and format PDFs ignores \n
, and send an outbound email using Twilio SendGrid–the email body says "good luck at your marathon on {marathon_date}" with the plan attached.
The email attachment is formatted and styled using ReportLab. The following code returns a stylesheet with a few basic heading and text styles to format the PDF attachment which is in the form of a SimpleDocTemplate document. The document is built using the Paragraph class that lets us format the text with inline font style (and optional color changes) using an XML style markup.
The encoded file is passed as an attachment to the SendGrid email. If the email is sent successfully, the Streamlit app displays a success message; otherwise, it displays an error message.
Run streamlit run request.py
in your terminal. Fill in your information (like Strava Access Token, the email where you want to receive the generated plan, the training start date, and marathon race date!) on the Streamlit app, click Submit, and wait for your personal marathon training plan in your email.
If the Submit button is clicked, we check the input (dates and email). If the inputs are either:
- invalid: the Streamlit app displays an error message and caches the function so the user can try valid input.
- valid: the Streamlit app displays a success message and it's time to use the user's Strava API token to get their Strava workout data.
Play around with different agents, prompting, and different parameters like temperature (which affects the risks the agent takes and creativity of the output)--this is a lot of code which I feel like could be unnecessary, but it yielded the best results.
The complete code above can be found here on GitHub.
What's Next for Agents and SendGrid
So will I use this to train for my marathon? I haven't coded this much since college (or had this much fun doing so) and this was a great way to learn many of LangChain's features, but time will tell –work travel, tennis practices, and AI hackathons make consistent training difficult. I do want to continue trying different ways of changing the output.
There's so much developers can do with communication APIs and AI–this is just the tip of the iceberg. You can use a different LLM, look at different Strava data, try different agents (or use chains instead!), embeddings, use LLaMA 2 or other similar models on Replicate, and more. You could use SMS or Twilio Conversations to collect user input instead of Streamlit, or make the LLM recommend other workouts for different goals or meals based on nutrition data!
Let me know online what you're building (and send running tips!)
- Twitter: @lizziepika
- GitHub: elizabethsiegle
- Email: lsiegle@twilio.com
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.