Код решает, LLM помогает: как я строил AI-тренера для бегунов. ai-агенты.. ai-агенты. llm.. ai-агенты. llm. python.. ai-агенты. llm. python. rag.. ai-агенты. llm. python. rag. здоровье.. ai-агенты. llm. python. rag. здоровье. искусственный интеллект.. ai-агенты. llm. python. rag. здоровье. искусственный интеллект. Клод де Пейс.. ai-агенты. llm. python. rag. здоровье. искусственный интеллект. Клод де Пейс. промпт-инжиниринг.. ai-агенты. llm. python. rag. здоровье. искусственный интеллект. Клод де Пейс. промпт-инжиниринг. разработка продукта.

1. Ошибка, после которой всё стало ясно

Я уже несколько месяцев разрабатываю AI-тренера для бегунов в Telegram — придумал ему имя и образ, Клод де Пейс, — и это ещё далеко не финал. В какой-то момент я дал ему возможность менять расписание по свободной фразе в чате. Первый же реальный тест: пользователь пишет «поменяй мне лёгкую в субботу» — имея в виду «перенеси лёгкую тренировку на субботу».

План действительно поменялся. Не в субботу — в четверг.

Расследование заняло пару минут: в четверг у пользователя стояла лёгкая тренировка, и модель формально нашла в сообщении два признака — easy и Saturday — и уверенно (confidence: 0.92) выбрала тот объект, который был ближе, а не тот, который имел в виду человек. С точки зрения классификации всё было сделано правильно. С точки зрения пользователя — совсем не то, о чём он просил.

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

2. Откуда это всё началось

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

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

3. Код решает, LLM помогает

Из истории с «субботой» и десятков похожих случаев родился рабочий принцип продукта. Каждый следующий шаг разработки фактически был шагом в одном направлении — сужением того, что разрешено решать модели:

«Пусть модель сама всё решает» → «пусть решает, но вернёт JSON» → «пусть предлагает, а код проверяет» → «пусть предлагает только из разрешённого набора» → «пусть изменение сначала станет черновиком и ждёт подтверждения пользователя».

Чем больше я использовал LLM в реальном продукте, тем меньше свободы ей оставлял — и продукт от этого становился только надёжнее.

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

Я довольно быстро понял, что модель, которая одновременно классифицирует, планирует и объясняет свои действия, начинает путаться. Поэтому у каждого сценария теперь одна роль: быстрая модель отвечает за классификацию и маршрутизацию, более сильная — за построение и пересчёт плана. Никогда обе роли одновременно — это упрощает контракт: у каждой роли своя жёсткая JSON-схема, и модели не приходится на лету решать, что она сейчас делает.

При этом схема не гарантирует всего: валидация отсекает некорректный по форме ответ, но не защищает от таймаута, rate limit, недоступности модели или семантически корректного, но неверного по смыслу результата — как в истории с субботой. Поэтому точнее так: некорректный или подозрительный ответ модели не должен напрямую ломать основной сценарий — после валидации есть повторная попытка, а затем детерминированный fallback, который вообще обходится без LLM.

Возьмём конкретный сценарий — составление плана. Модели передаётся не сырой диалог, а контекст, который заранее собирает код: профиль, якорные даты, посчитанная календарная сетка, сжатая история нагрузки, факты об атлете. Ответ модель обязана вернуть строго по схеме:

{
  "weeks": [
    {
      "week_number": 1,
      "cycle_week": 1,
      "workouts": [
        {
          "date": "2026-07-21",
          "type": "easy",
          "distance": 8.0,
          "planned_duration": 50,
          "description": "Лёгкий кросс в разговорном темпе"
        }
      ]
    }
  ]
}

А вот что из этого в итоге видит пользователь — тот же слот тренировки, но уже как человеческое сообщение:

Команда /today в Telegram: бот показывает тренировку на сегодня — тип, дистанцию, темп, описание
Команда /today в Telegram: бот показывает тренировку на сегодня — тип, дистанцию, темп, описание

После ответа код валидирует типы и enum, отбрасывает тренировки задним числом, принудительно закрепляет день старта как гоночный слот — даже если модель ошиблась, — и только потом сохраняет план. Если ответ не проходит валидацию дважды — сначала сам, потом после repair-попытки — включается запасной план, собранный по жёстким правилам прямо в коде, вообще без обращения к LLM.

4. Почему главная метрика не пульс

Главная метрика продукта — оценка нагрузки от 1 до 10. Пользователь ставит её через inline-кнопки Telegram — детерминированный вход без участия модели. Но та же шкала используется и как контракт с LLM при планировании: модель обязана оценить ожидаемую нагрузку тренировки в тех же единицах. Когда план оценён на 4, а факт — на 9, это прямой сигнал перегруза, не требующий интерпретации: реакция «снизить нагрузку» следует из сравнения двух чисел, а не из понимания моделью ситуации.

Цифры полезны — пульс, темп, дистанция, длительность — но они не отвечают на один из главных вопросов: насколько тяжёлой тренировка была именно для этого человека. Пульс может быть низким, темп известным, а тренировка всё равно субъективно тяжёлой. В большинстве спортивных приложений такой субъективный отчёт существует, но занимает второстепенное место где-то в глубине приложения — до него нужно ещё дойти, в отличие от графиков, которые выносят на главный экран. Мне кажется, это упущение: если мы тренируем человека, а не датчик на его запястье, логично для начала слушать, что он сам говорит о своих ощущениях — а не только мерить его пульс и темп. Вокруг этой мысли построена вся механика бота.

За время работы сервиса накопилось больше 250 тренировок с оценками нагрузки и отчётами — и это основной массив, на котором система вообще проверяется на практике, а не на бумаге.

5. Telegram и тренер с характером

Telegram выбрал не только из удобства платформы — там естественно соединяются в одном чате детерминированный слой (кнопки с чётко заданными значениями) и недетерминированный (свободный текст). Пользователю не нужно переключаться между «вводом данных» и «разговором с тренером».

Сразу возникла мысль делать не безликого бота, а персонажа. Так родился Клод де Пейс: «Клод» — отсылка к нейросети, «пейс» — темп бега, «де» встало между ними как влитая. Образ — опытный тренер-француз, который тренирует так давно, что сам забыл, сколько ему лет.

«Ты решил превратить восстановительный бег в полноценную прогулку, ну что ж, ça arrive. Главное, что пульс остался низким, но в следующий раз постарайся не удваивать дистанцию, иначе ноги скажут «merci» совсем не так, как ты ожидаешь.»

«Опыт не пропьешь, mon ami. Главное, чтобы ноги в субботу были такими же лёгкими, как твой язык.»

Это не постановочные примеры для статьи — вот как это выглядит в реальной переписке, включая сами кнопки оценки нагрузки:

Диалог с ботом: кнопки оценки нагрузки и контекстный ответ с рекомендациями по темпу и пульсу

Диалог с ботом: кнопки оценки нагрузки и контекстный ответ с рекомендациями по темпу и пульсу

Обратите внимание: в ответе бот ссылается на порог ПАНО конкретного пользователя (168 уд/мин) — это не выдумано на лету и не переспрошено у человека. Каждое сообщение (и сводка за сутки) проходит через отдельный классификатор, который вытаскивает из диалога устойчивые факты — рекорды, пороги, привычки — и подмешивает их в контекст следующих ответов. Со стороны это выглядит почти как память о человеке, хотя на деле это всё тот же принцип: узкая, контролируемая задача извлечения, а не «модель сама всё запомнила».

Персонаж держится не «на глаз», а системно — на уровне промптов и настроек стиля, версионируется вместе с продуктом. При этом у него есть чёткий продуктовый принцип: сначала тренер, потом француз. Французские словечки — приправа, а не основа сообщения.

6. Живые пользователи ломают даже логичные решения

Первые внешние тесты начались спустя пару месяцев. И произошло то, что происходит с любым продуктом, вышедшим на публику, даже небольшую: пользователи делают всё не так, как предполагалось.

Кейс с кнопкой. Отчёт о тренировке был реализован как FSM-состояние: пользователь жмёт кнопку, бот ждёт отчёт, получает и классифицирует его. Всё, что приходит вне этого состояния, считается внеплановой активностью. Логично на бумаге — но многие люди эту кнопку просто не замечают. Текст отчёта уходит не в ту тренировку, а плановая остаётся незафиксированной.

Кейс с субботой уже описан в начале — и это тот случай, где формально корректная классификация дала пользователю не то, что он хотел. Именно после него любая правка расписания стала сначала черновиком «было → станет» с явным подтверждением, а не молчаливой заменой. Вот как это выглядит сейчас на похожем запросе:

Диалог: пользователь просит поменять темповую на лёгкую, бот показывает черновик «было → станет» с кнопками подтверждения

Диалог: пользователь просит поменять темповую на лёгкую, бот показывает черновик «было → станет» с кнопками подтверждения

Про совсем экзотические запросы вроде «через две недели бегу трейл на 100 км» я вообще молчу — это было ожидаемо с самого начала.

Сейчас активно пользуются больше 20 человек — и оба этих кейса всплыли не в теории, а в первые же недели, когда живые люди начали писать боту то, что писать «не положено».

7. AI, который чинит AI

Для диагностики проблем я сделал отдельного AI-агента. Работает это так: пользователь совершает странное действие → событие логируется вместе с контекстом → агент скачивает метрики и логи за период или по конкретному пользователю → анализирует их вместе с кодом и документацией → находит место, где логика разошлась с ожиданием → предлагает патч, который я проверяю и, если согласен, внедряю. Реализован этот агент как skill в Cursor, где ведётся вся разработка — но суть не в конкретном инструменте, а в самом контуре.

Пользователь
    ↓
Тренер (бот)
    ↓
Ошибка / неожиданный кейс
    ↓
Метрики и логи
    ↓
AI-анализ
    ↓
Патч кода
    ↓
Новая версия тренера

Это уже не просто «я сделал бота» — это контур, где один AI-инструмент помогает разрабатывать и лечить другой AI-продукт. Через него уже прошло около 30 патчей — то есть это не разовый эксперимент, а рабочий канал разработки.

8. Что до сих пор бесит

Не все случаи расписаны, и иногда бот тупит, делая не то, что от него ожидаешь. Я понимаю, что это предстоит исправлять: каждый такой мелкий косяк требует внимания, кода и тестирования — работы здесь ещё много. Это не история «сделал и забыл», это живой процесс, где новый пользовательский кейс почти гарантированно найдёт дыру в текущих правилах.

9. Куда дальше

В планах — синхронизация с часами, но не как отказ от идеи субъективной нагрузки, а как дополнение: для интервальных тренировок и гонок объективные данные о предельных режимах реально полезны. Параллельно уже реализуется система учёта накопленной нагрузки и анализа интервалов — с целью предсказывать оптимальный темп на конкретной гонке.

Вторая задача — социальные функции: календарь забегов вместо ручного ввода дистанции и даты, возможность делиться тренировками с другими пользователями. По сути — превратить это в клуб. Клуб тренера Клод де Пейс. Сейчас ботом регулярно пользуются больше 20 человек — при том, что сервис запущен меньше месяца назад: люди пользуются им первые несколько недель, а не годами.

Я начинал с идеи «LLM сама построит мне тренировочный план». В итоге получил систему, где самая важная часть работы состоит в том, чтобы не дать модели делать слишком много.

Автор: lexa97

Источник