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": "Лёгкий кросс в разговорном темпе" } ] } ]}
А вот что из этого в итоге видит пользователь — тот же слот тренировки, но уже как человеческое сообщение:
После ответа код валидирует типы и 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 сама построит мне тренировочный план». В итоге получил систему, где самая важная часть работы состоит в том, чтобы не дать модели делать слишком много.
ссылка на оригинал статьи https://habr.com/ru/articles/1067942/