Я создал бессерверное приложение, которое просит ChatGPT каждый день писать для меня тренировки. Я не мог быть счастливее с результатами.

Я немного занимаюсь фитнесом.

В колледже я работал в магазине беговых товаров. Я наладил отношения с людьми из фитнес-индустрии и полностью погрузился в коучинг. Я годами обучал людей развивать скорость и выносливость и помогал им освоиться в беге.

Когда я закончил колледж и начал работать в сфере высоких технологий, я продолжал тренировать еще пару лет. В конце концов, это уменьшилось, когда я сосредоточился на работе. Тем не менее, я руководил группой силовых тренировок в своей компании около 6 лет, пока не разразилась пандемия.

Как только мы все начали работать из дома, я создал мобильное приложение, чтобы продолжать отправлять тренировки удаленно, чтобы мы могли тренироваться виртуально и выполнять одни и те же тренировки вместе.

Но опять же, это сошло на нет через пару лет, и теперь я просто тренируюсь. У меня более десяти лет опыта разработки индивидуальных планов тренировок. Но это много работы, особенно если это только для меня. Сейчас у меня двое детей, небольшая ферма, работаю в стартапе и запускаю Ready, Set, Cloud, где я пишу еженедельные посты в блог и информационные бюллетени, а также выпускаю подкаст раз в две недели.

Я немного занят.

Но не заниматься - не вариант. Это часть моей утренней рутины.

Во втором выпуске моего подкаста я рассказываю Рану Изенбергу о платформенной инженерии. Ран говорит, что часть обязанностей команды разработчиков платформы состоит в том, чтобы снизить интеллектуальную нагрузку на разработчиков. Это звучало именно так, как мне было нужно в моем фитнес-режиме. Мне нужен был кто-то или что-то, что облегчило бы мне составление графика тренировок.

ChatGPT сейчас в тренде. Он объединяет информацию со всего Интернета для создания законной модели обучения. Почему бы не использовать его для снижения когнитивной нагрузки и построения тренировок?

Не мешало бы попробовать, верно?

tl;dr — вы можете сразу перейти к репозиторию GitHub, если хотите попробовать сами!

Разница в тренировках

Каждую зиму я прохожу этап силовых тренировок. Я поднимаю тяжести пять дней в неделю и каждый день работаю с разными группами мышц. Я прокручиваю руки, грудь, спину, плечи и ноги. Я также добавляю в смесь кардио-разминку и заминку и всегда стараюсь немного накачать пресс.

Сначала я хотел посмотреть, насколько хорошо ChatGPT сможет придумать тренировку. Поэтому я пошел на сайт и попросил его написать мне один.

Этот ответ меня очень взволновал. Это была такая же хорошая (если не лучше) тренировка, которую я бы придумал, и мне потребовалось всего несколько секунд, чтобы создать именно то, что я искал!

Одна вещь, которую я узнал об упражнениях, заключается в том, что разнообразие — это приправа к жизни. Мало того, что это помогает сохранить вашу умственную свежесть, но изменение режима тренировок также может иметь впечатляющие физические результаты, предотвращая вас от плато. Имея это в виду, я решил немного изменить запрос, который я отправил в ChatGPT.

Я хотел иметь возможность генерировать тренировку случайным образом, будь то схема, суперсет или стандартная тренировка. Вдобавок ко всему, я хотел тренировку, которая случайным образом выбирала бы оборудование, которое у меня было. В один день я мог использовать гири, а в следующий — штангу.

Поэтому я параметризовал типы тренировок и доступное оборудование и создал небольшой рандомизатор, который каждый день выбирал тип тренировки и подмножество оборудования. В итоге мой запрос к ChatGPT выглядел так:

let muscleGroups = ['chest', 'arm', 'shoulder', 'back', 'leg'];
let workoutTypes = ['as a circuit', 'in supersets', 'as a standard workout'];
let equipment = [{ type: 'barbells', threshold: .9 }, { type: 'dumbbells', threshold: .75 }, { type: 'kettlebells', threshold: .45 }];

// Logic to randomly pick a muscle group, workout type, and set of equipment omitted for brevity

const request = `Create a ${muscleGroup} workout for strength training that uses ${joinWithAnd(equipmentToUse)} structured ${workoutType}.`;

Для случайного выбора оборудования у меня было сгенерировано случайное число от 0 до 1. Если это случайное число было ниже заданного порога, оно включалось в тренировку.

Со всем этим у меня был отличный способ построить высококачественные тренировки, которые циклически использовали все мое доступное оборудование и давали мне столь необходимую вариативность.

Давайте немного поговорим о том, как это работает.

Бессерверное решение

Поскольку я склонен делать все без сервера, имело смысл создать решение на базе Lambda, DynamoDB, EventBridge и Step Functions. Эти сервисы дают мне управляемую событиями архитектуру, которую можно масштабировать, если я решу превратить ее в предложение SaaS. На данный момент я соглашусь с бесплатным характером приложения, где использование настолько мало, что я никогда не буду платить ни за что.

Это решение основано на двух пошаговых функциях:

  1. Создать расписание на неделю — рандомизирует, какие группы мышц, типы тренировок и оборудование будут использоваться каждый день, и получает данные о тренировках из ChatGPT.
  2. Daily Workout Notifier: вечером отправляет мне электронное письмо с информацией о тренировках на следующий день.

Каждое воскресенье запускается недельный планировщик, случайным образом определяющий, какими будут тренировки, запрашивающий ChatGPT для их создания и сохраняющий в DynamoDB. Затем я получаю электронное письмо, чтобы знать, чего ожидать на следующей неделе.

Каждый будний день запускается уведомитель о тренировках, «выталкивает» тренировку из DynamoDB (подробнее об этом чуть позже) и отправляет подробности по электронной почте, чтобы они были у меня на следующий день. Все, что мне нужно сделать, это открыть электронную почту утром, и я готов к работе!

Модель данных

Недавно меня просветил Пит Нейлор в своем блоге об оптимизации вторичных индексов DynamoDB. Это было настоящим откровением, которое заставило меня задуматься, не делал ли я что-то неправильно в течение долгого времени.

Практика, от которой я стараюсь уклоняться, — это «перегрузка GSI», когда вы запихиваете все в GSI и надеетесь на лучшее. Это кажется (и является) запутанным для поддержки с течением времени и не дает никаких реальных преимуществ. Поэтому я придумал эту модель данных для графика тренировок.

Ключ раздела — это дата, а ключ сортировки — либо workout, либо schedule, чтобы отразить, какую сущность они представляют. У меня есть GSI с именем FutureSchedule с хеш-ключом IsScheduledInFuture и ключом диапазона ScheduledDate.

Этот GSI должен быть разреженным, то есть не все будет проецироваться в него. Будут отображаться только те записи, которые запланированы на будущее, что позволит легко определить, какая будет следующая тренировка.

Чтобы получить завтрашнюю тренировку, у меня есть запрос в машине состояний, который выглядит так:

"Get Next Workout": {
  "Type": "Task",
  "Parameters": {
    "TableName": "${TableName}",
    "IndexName": "FutureSchedule",
    "KeyConditionExpression": "#future = :future",
    "ExpressionAttributeNames": {
      "#future": "IsScheduledInFuture"
    },
    "ExpressionAttributeValues": {
      ":future": {
        "S": "true"
      }
    },
    "Limit": 1
  },
  "Resource": "${DynamodbQuery}",
  "Next": "Has Next Workout?"
}

Это извлечет первый элемент (см. Limit: 1) из GSI. Все элементы в этом индексе отсортированы по запланированной дате, поэтому первым должен быть возвращен элемент на завтра!

Теперь, прежде чем вы скажете мне, что этот шаблон создает горячий раздел, я знаю, что он может. Но этого не произойдет. Доступ к данным осуществляется один раз в день и ограничивается одной записью. У вас начинаются проблемы с горячими разделами когда вы пересекаете 3000 RCU или 1000 WCU в секунду.

После загрузки данных в конечный автомат и отправки электронного письма я удаляю значения IsScheduledInFuture и ScheduledDate из записи о тренировке, чтобы они удалялись из GSI и не возвращались в будущем запросе. Легкий!

Сторонние API

Все это решение работает на некоторых выдающихся сторонних API. Во-первых, это, конечно же, OpenAI API, который позволяет нам общаться с ChatGPT. Этот API позволяет приличное количество настроек для точной настройки результатов, которые вы получаете от ИИ следующего поколения. Вы можете настроить уровень творчества (я думаю, что это сложность тренировки) с помощью настройки temperature. Чем выше температура, тем более креативный ответ вы получите.

Вы также можете ограничить количество токенов в своем ответе. API OpenAI не является бесплатным. В зависимости от модели ИИ, которую вы используете, с вас взимается от 0,0004 до 0,02 доллара США за 1000 токенов. Токены — это фрагменты слов, и в зависимости от того, насколько многословен ваш ответ, вы можете использовать от 30 до 1500 токенов в одном вызове. Поэтому мы хотим ограничить размер платы за запрос, указав значение в параметре max_tokens.

Генератор тренировок использует самую мощную модель искусственного интеллекта, доступную через API, — Davinci. Эта модель близка (но не совсем) к той, которую вы используете при взаимодействии с ChatGPT на сайте. Это оказывается самой дорогой частью всего решения.

Поскольку OpenAI API — самая дорогая часть, я не хочу запускать ненужные запросы. Мы уже знаем, что все время все терпит неудачу, поэтому, когда я построил конечные автоматы для повторных попыток, я решил использовать Momento для кэширования ответов ChatGPT. Это позволяет мне отказаться от дорогостоящего запроса, внедрив кеш для чтения (больше похожий на запрос).

Сначала я проверяю свой кеш в Momento, чтобы узнать, запрашивал ли я OpenAI для конкретной тренировки. Если у меня есть, отлично! Я замыкаю вызов и возвращаю кешированный ответ. Если нет, то я обращаюсь к OpenAI API. У Momento есть бесплатный уровень 50 ГБ в месяц, поэтому я ничего не плачу за использование этого сервиса бессерверного кэширования.

Наконец, я использую SendGrid API, чтобы отправить электронное письмо с тренировкой самому себе. Я мог бы использовать Amazon SES, но у меня уже был полнофункциональный SendGrid, настроенный на отправку электронных писем в ответ на событие EventBridge как часть моей платформы для рассылки новостей. Я просто использую putEvents с DetailType Send Email, передаю поля html, subject и to в Detail, и бум — письмо отправлено. У SendGrid также есть отличный бесплатный уровень, который позволяет мне бесплатно отправлять до 100 электронных писем в день.

Краткое содержание

Мир, в котором мы живем сегодня, удивителен. У меня возникла идея передать создание тренировок искусственному интеллекту, и через два дня я получил автоматические электронные письма с высококачественными тренировками с высокой вариативностью с использованием оборудования, которое есть в моем домашнем спортзале.

Я смог быстро двигаться, перейдя на бессерверную версию, но не так быстро, где я не смогу поддерживать ее в будущем. Я также оставил дверь открытой для того, чтобы это было полностью жизнеспособное предложение SaaS. Было бы несложно добавить в этот микс Cognito, добавить настраиваемые пользовательские настройки в DynamoDB и добавить расписания EventBridge для динамической отправки тренировок в настраиваемое время всем, кто хочет зарегистрироваться.

Программное обеспечение сегодня легко компонуется, независимо от того, полностью ли вы придерживаетесь своих облачных сервисов, используете сторонние предложения, такие как OpenAI, Momento или Sendgrid, или даже пишете свои собственные модули. С помощью API вы можете связать вместе множество сервисов для создания любой сумасшедшей интеграции, о которой вы только мечтаете.

Я очень рад отказаться от тренировочного здания. Это то, чем я наслаждался в течение долгого времени, но я готов, чтобы кто-то другой сделал это за меня. Это был забавный проект, и я надеюсь, что вы возьмете его и будете работать с ним. Он полностью с открытым исходным кодом и доступен для пиара!

Удачного кодирования!