Skip to content

Commit

Permalink
Using chain of thought as a "coach's recs" feature
Browse files Browse the repository at this point in the history
  • Loading branch information
voynow committed Aug 9, 2024
1 parent 1c0c6c9 commit c670708
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 123 deletions.
76 changes: 1 addition & 75 deletions src/constants.py
Original file line number Diff line number Diff line change
@@ -1,75 +1 @@
training_week_html_base = """
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background-color: #be5e5b;
color: #ffffff;
text-align: center;
padding: 20px;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.content {
padding: 20px;
}
.content h2 {
color: #be5e5b;
font-size: 20px;
margin-bottom: 10px;
}
.content ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.content li {
background-color: #f9f9f9;
margin-bottom: 10px;
padding: 15px;
border-left: 5px solid #be5e5b;
border-radius: 5px;
color: #333;
}
.content li strong {
display: block;
font-size: 16px;
margin-bottom: 5px;
color: #333;
}
.footer {
background-color: #f1f1f1;
text-align: center;
padding: 10px;
font-size: 9px;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Your Training Schedule</h1>
</div>
<div class="content">
<h2>Get pumped for this week's training.</h2>
<ul>
"""
COACH_ROLE = "You are a talented running coach with years of experience."
112 changes: 105 additions & 7 deletions src/email_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import sib_api_v3_sdk
from dotenv import load_dotenv

from src import constants
from src.types.training_week import TrainingWeek
from src.types.training_week_with_coaching import TrainingWeekWithCoaching

load_dotenv()

Expand All @@ -18,25 +17,124 @@
)


def training_week_to_html(training_week: TrainingWeek) -> str:
def training_week_to_html(training_week_with_coaching: TrainingWeekWithCoaching) -> str:
"""
Convert a TrainingWeek object to HTML content for email
:param training_week: TrainingWeek object
:return: HTML content for email
"""
uid = str(uuid.uuid4())
html_content = constants.training_week_html_base
for day, session in training_week.dict().items():
html_content += f"""
total_miles = sum(
[
session["distance"]
for session in training_week_with_coaching.training_week.dict().values()
if isinstance(session, dict)
]
)

html_content = """
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background-color: #6495ED;
color: #ffffff;
text-align: center;
padding: 20px;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.content {
padding: 20px;
}
.content h2 {
color: #6495ED;
font-size: 20px;
margin-bottom: 10px;
}
.content ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.content li {
background-color: #f9f9f9;
margin-bottom: 10px;
padding: 15px;
border-left: 5px solid #6495ED;
border-radius: 5px;
color: #333;
}
.content li strong {
display: block;
font-size: 16px;
margin-bottom: 5px;
color: #333;
}
.review-section, .mileage-target-section {
margin-top: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.footer {
background-color: #f1f1f1;
text-align: center;
padding: 10px;
font-size: 9px;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Your Training Schedule</h1>
</div>
<div class="content">
<h2>Get pumped for this week's training.</h2>
<ul>
"""
for day, session in training_week_with_coaching.training_week.dict().items():
if isinstance(session, dict): # Skip non-session fields
html_content += f"""
<li>
<strong>{day.capitalize()}</strong>
<span>{session['session_type'].value} {session['distance']} miles</span><br>
<span>Notes: {session['notes']}</span>
</li>
"""
"""
html_content += f"""
</ul>
<h2>Total Miles Planned: {total_miles}</h2>
<div class="review-section">
<h2>Coach's Review</h2>
<p>{training_week_with_coaching.typical_week_training_review}</p>
</div>
<div class="mileage-target-section">
<h2>Weekly Mileage Target</h2>
<p>{training_week_with_coaching.weekly_mileage_target}</p>
</div>
</div>
<div class="footer">
<p style="font-size: 15px; color: #777;">Powered by the Strava API and OpenAI</p>
Expand Down
72 changes: 38 additions & 34 deletions src/training_week.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,47 @@
from typing import List

from src.constants import COACH_ROLE
from src.llm import get_completion, get_completion_json
from src.types.day_of_week_summary import DayOfWeekSummary
from src.types.training_week import TrainingWeek
from src.types.training_week_skeleton import TrainingWeekSkeleton
from src.types.training_week_with_coaching import TrainingWeekWithCoaching
from src.types.week_summary import WeekSummary
from src.types.weekly_mileage_target_response import WeeklyMileageTargetResponse


def get_typical_training_week_verbose(
def get_typical_week_training_review(
sysmsg_base: str,
day_of_week_summaries: List[DayOfWeekSummary],
) -> str:
sysmsg = "You are a talented running coach with years of experience. Your client is training for a marathon. You will be provided summary statistics (aggregated by day of the week) of your client's training over the past several weeks."
sysmsg = f"{sysmsg_base} You will be provided summary statistics (aggregated by day of the week) of your client's training over the past several weeks."
usermsg = (
str(day_of_week_summaries)
+ "As the coach, provide an analysis estimating what a typical week of training looks like for your client. e.g. Which days are best for their schedule? When do they rest? When (if ever) is their long run? When do they incorporate speed work? Be concice yet thorough."
+ "As the coach, provide a very concise training review for your client on their typical week. e.g. Which days are best for their schedule? When do they rest? When (if ever) is their long run? When do they incorporate speed work? Write in paragraph form."
)
return get_completion(
[{"role": "assistant", "content": sysmsg}, {"role": "user", "content": usermsg}]
)


def get_training_week_skeleton(training_week_verbose: str) -> TrainingWeekSkeleton:
message = f"""You are a talented running coach with years of experience. Your client is training for a marathon. Here is an analysis this client's training over the past several weeks:
{training_week_verbose}
Create a training week skeleton for your client. Optimize the schedule for their success, making sure to prioritize rest and recovery around their hardest workouts. As a general rule, lets aim for 1 long run, 1 speed workout, and at least 1 rest day per week."""

return get_completion_json(
message=message,
response_model=TrainingWeekSkeleton,
)


def get_weekly_mileage_target(
sysmsg_base: str,
weekly_summaries: List[WeekSummary],
) -> WeeklyMileageTargetResponse:
message = f"""You are a talented running coach with years of experience. Your client is training for a marathon. Here are summary statistics (aggregated by week) of your client's training over the past several weeks:
{weekly_summaries}
As the coach, provide some information to your client about weekly mileage targets."""

return get_completion_json(
message=message,
response_model=WeeklyMileageTargetResponse,
sysmsg = f"""{sysmsg_base} You will be provided summary statistics (aggregated by week) of your client's training over the past several weeks."""
usermsg = f"""{weekly_summaries}
As the coach, provide very concise feedback and prescribe your client a target weekly mileage for next week. Be specific and refer to the data provided. Write in paragraph form."""
return get_completion(
[{"role": "assistant", "content": sysmsg}, {"role": "user", "content": usermsg}]
)


def get_training_week(
training_week_skeleton: TrainingWeekSkeleton,
weekly_mileage_target: WeeklyMileageTargetResponse,
sysmsg_base: str,
typical_week_training_review: str,
weekly_mileage_target: str,
) -> TrainingWeek:
message = f"""You are a talented running coach with years of experience. Your client is training for a marathon. Here is the training week skeleton you created:
{training_week_skeleton}
message = f"""{sysmsg_base} Here is your review of your client's typical week:
{typical_week_training_review}
Additionally, here is the weekly mileage target you provided:
{weekly_mileage_target}
Expand All @@ -66,13 +55,28 @@ def get_training_week(


def generate_training_week(
client_preferences: str,
day_of_week_summaries: List[DayOfWeekSummary],
weekly_summaries: List[WeekSummary],
) -> TrainingWeek:
) -> TrainingWeekWithCoaching:

training_week_verbose = get_typical_training_week_verbose(day_of_week_summaries)
training_week_skeleton = get_training_week_skeleton(training_week_verbose)
weekly_mileage_target = get_weekly_mileage_target(weekly_summaries)
training_week = get_training_week(training_week_skeleton, weekly_mileage_target)
sysmsg_base = f"""{COACH_ROLE}
Your client has included the following preferenced: {client_preferences}"""

return training_week
typical_week_training_review = get_typical_week_training_review(
sysmsg_base=sysmsg_base, day_of_week_summaries=day_of_week_summaries
)
weekly_mileage_target = get_weekly_mileage_target(
sysmsg_base=sysmsg_base, weekly_summaries=weekly_summaries
)
training_week = get_training_week(
sysmsg_base=sysmsg_base,
typical_week_training_review=typical_week_training_review,
weekly_mileage_target=weekly_mileage_target,
)

return TrainingWeekWithCoaching(
training_week=training_week,
typical_week_training_review=typical_week_training_review,
weekly_mileage_target=weekly_mileage_target,
)
14 changes: 14 additions & 0 deletions src/types/training_week_with_coaching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic import BaseModel

from src.types.training_week import TrainingWeek


class TrainingWeekWithCoaching(BaseModel):
training_week: TrainingWeek
"""Client's recommended training week"""

typical_week_training_review: str
"""Coach's review of the client's typical week of training"""

weekly_mileage_target: str
"""Coach's prescribed weekly mileage target for the client"""
19 changes: 12 additions & 7 deletions test.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"athlete_id='98390356' token still valid until 2024-08-06 03:51:08+00:00\n"
"athlete_id='98390356' token still valid until 2024-08-09 03:49:42+00:00\n"
]
}
],
Expand All @@ -54,26 +54,31 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"training_week = generate_training_week(weekly_summaries, day_of_week_summaries)"
"client_preferences = \"A) Training for a marathon B) This will be my second marathon C) Prefer workouts on Wednesdays and long runs on Saturdays\"\n",
"training_week_with_coaching = generate_training_week(\n",
" client_preferences=client_preferences, \n",
" weekly_summaries=weekly_summaries, \n",
" day_of_week_summaries=day_of_week_summaries\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 32,
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'message_id': '<202408052326.98048304873@smtp-relay.mailin.fr>',\n",
"{'message_id': '<202408090006.72808075952@smtp-relay.mailin.fr>',\n",
" 'message_ids': None}"
]
},
"execution_count": 32,
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
Expand All @@ -83,7 +88,7 @@
"\n",
"send_email(\n",
" subject=\"Training Schedule Just Dropped 🏃\",\n",
" html_content=training_week_to_html(training_week),\n",
" html_content=training_week_to_html(training_week_with_coaching),\n",
")"
]
},
Expand Down

0 comments on commit c670708

Please sign in to comment.