VEU.WathBody/Архитектура_LLM_планировани...

245 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Дизайн: создание и корректировка программы тренировок с помощью LLM
Дата составления: 17.10.2026
Привязка к проекту: «Феникс 6X» (см. «Техническое задание.md»). Архитектура: телефон + часы Garmin Fenix 6 + личный сервер (FastAPI + PostgreSQL), данные из Garmin Connect в .fit.
---
## 1. Главная идея: LLM — планировщик, код — источник истины
Ключевое архитектурное решение: **LLM никогда не является хранилищем программы и не имеет прямого доступа к данным.**
- Программа живёт в БД сервера как **версионированный JSON-документ** строгой схемы.
- LLM только **предлагает** программу или изменение — в виде структурного JSON (structured output / function calling), не свободного текста.
- Детерминированный **валидатор** проверяет предложение по схеме и правилам спортивной методологии. Прошёл — новая версия программы. Не прошёл — повторный запрос LLM с текстом ошибки (ограниченное число попыток), затем fallback на правила.
Почему так:
| Риск «LLM всем управляет» | Как снимается |
|---|---|
| Галлюцинации (несуществующие упражнения, веса 500 кг) | Жёсткая JSON-схема + справочник ID упражнений + валидатор |
| Небезопасная прогрессия (резкий скачок нагрузки) | Детерминированные правила-ограничители (разд. 6) |
| Непредсказуемость, «откатить нельзя» | Неизменяемые версии программы + журнал причин изменений |
| Дорого и медленно при каждом чихе | Гибрид: микроправки после тренировки делает простой код, LLM вызывается редко (разд. 3) |
| Зависимость от интернета/провайдера | Офлайн-fallback: чисто правила (разд. 7) |
---
## 2. Представление программы: версионированный JSON
Формат хранения — свой JSON (внутренний формат из документа «Форматы_тренировок_и_рекомендации.md»). Иерархия:
```
Программа (Program, версия неизменяема)
└─ Неделя (Week / мезоцикл: целевой объём, тонус)
└─ Тренировка (Session: день, фокус — верх/низ/тяни-толкай)
└─ Упражнение (Exercise: ссылка на ID из библиотеки)
└─ Подход-план (SetPlan: вес, повторения, RPE-цель, отдых)
```
Пример фрагмента:
```json
{
"program_id": "prg_2026_10",
"version": 3,
"based_on_version": 2,
"status": "active", // draft | active | archived
"author": "llm", // llm | rules | user
"created_at": "2026-10-17T18:00:00Z",
"weeks": [
{
"week": 1,
"goal": "гипертрофия, объёмная",
"sessions": [
{
"day": "monday",
"focus": "push",
"exercises": [
{
"exercise_id": "bench_press", // только ID из библиотеки!
"sets": [
{"weight_kg": 80, "reps": 8, "target_rpe": 7, "rest_sec": 150}
]
}
]
}
]
}
],
"rationale": "Увеличил вес в жиме на 2.5 кг: 3 тренировки подряд все подходы закрыты при RPE<=7. Объём недельный не менял."
}
```
Важные свойства:
- **Упражнения — только по ID** из библиотеки упражнений приложения (иначе сломаются голосовой ввод, статистика и сопоставление с .fit).
- **Каждая версия неизменяема.** Корректировка = создание новой версии + запись `program_changes` (что, почему, решение валидатора, принял ли пользователь).
- Выполненные тренировки ссылаются на конкретную версию — история не «поплывёт» после правок.
- Из этой же структуры при необходимости можно собрать экспорт (CSV/JSON/FIT workout).
---
## 3. Два контура и три частоты корректировки
### Контур A — планирование (редко, LLM)
Когда: холодный старт, смена цели, начало нового мезоцикла (каждые 4–6 недель), явная просьба пользователя.
Что делает LLM: строит программу целиком (или следующий мезоцикл) из профиля, цели, инвентаря, ограничений (травмы) и сводки истории.
### Контур B — корректировка (три частоты, гибрид «правила + LLM»)
| Частота | Кто решает | Что корректирует | Пример |
|---|---|---|---|
| После каждой тренировки | **Код (правила), без LLM** | Вес/повторы следующего занятия по прогрессии | «Все подходы закрыты при RPE≤7 → +2.5 кг»; «не дотянул 2 раза → −5%» |
| Раз в неделю | **LLM** | Следующая неделя: объём, порядок, замена упражнений, лёгкая неделя | «Сон плохой, пульс покоя +5 → снизить объём на 15%» |
| Раз в мезоцикл (4–6 нед.) | **LLM** | Перепланирование: делоад, новые упражнения, смена акцентов | «Плато в жиме 3 недели → сменить на жим гантелей, добавить паузы» |
Смысл гибрида: 90% решений — тривиальная прогрессия, которую надёжнее и дешевле считает обычный код. LLM подключается там, где нужна интерпретация нетривиальных сигналов (усталость, плато, пропуск тренировок), — редко, обдуманно и с полным контекстом недели.
---
## 4. Конвейер данных: из .fit — в признаки для LLM
LLM **не видит** сырые .fit и пульсотограммы. Сервер сам считает метрики и отдаёт компактную сводку.
```
Garmin Connect (.fit)
→ парсер (Session / Lap / Record / Set) [уже есть в ТЗ]
→ каноническое JSON-представление тренировки
→ MetricsEngine: детерминированный расчёт признаков
→ хранилище фактов (PostgreSQL)
→ сборка промпта для LLM (сводка недели/мезоцикла)
```
Метрики, которые считает MetricsEngine (детерминированно, без LLM):
- **Выполнение плана:** доля закрытых подходов/тренировок (adherence).
- **Тоннаж** по упражнению и по мышечным группам (план vs факт).
- **Оценочный 1ПМ** (формулы Эпли/Бжицки) и его динамика по неделям.
- **Фактический RPE** — из голосового ввода («Подход 120 на 8» → если план был 120×6 при RPE 8, фактически RPE ниже) и/или короткого опроса после тренировки.
- **Кардионагрузка:** средний/пиковый пульс за подход, время восстановления пульса между подходами (из Lap/Record .fit — это как раз данные часов).
- **Сигналы усталости:** падение рабочего веса при том же RPE, рост времени отдыха, падение числа повторений в последних подходах (кластеры «до отказа»).
- **Субъективный фидбек** из мини-опроса в приложении после тренировки (1 вопрос: «Как прошла? 1–10» + опционально сон/самочувствие) — стоит дёшево и резко повышает качество корректировок.
---
## 5. Контракт с LLM: промпт и структурный вывод
### Состав запроса
1. **Системный промпт** — роль и методология (правила прогрессии, пределы изменений, приоритет безопасности), требование вернуть строго JSON по схеме.
2. **Профиль:** пол, возраст, вес, цель, стаж, инвентарь, травмы/ограничения.
3. **Текущая версия программы** (JSON).
4. **Сводка фактов** от MetricsEngine: последняя неделя (или мезоцикл) в сжатом виде — adherence, тоннаж по группам, динамика e1PM, пульс-метрики, субъективный фидбек. Сырую историю — не давать.
5. **Задача:** «предложи план на следующую неделю/мезоцикл» или «создай программу с нуля».
### Ответ
Только JSON по схеме: новая версия (или дельта-изменения) + обязательное поле `rationale` («почему») — оно попадает в журнал изменений и показывается пользователю («Феникс повысил вес: ты закрыл все подходы»).
Технически: API провайдера с structured output / function calling + JSON Schema; при неудаче валидации — повтор с текстом ошибки, максимум 2–3 попытки.
### Дополнительно (при желании, этап 2)
Двухпроходная схема: первый вызов генерирует план, второй («ревьюер») проверяет его по чек-листу методологии и возвращает замечания. Дёшево и ловит логические ошибки, которые схема не видит (например, два тяжёлых дня на одну группу подряд).
---
## 6. Слой валидации и безопасности (детерминированный)
После LLM предложение проходит обязательные проверки. Это обычный код, не LLM:
**Схема:** все ID упражнений существуют в библиотеке; веса/повторы/отдых в допустимых диапазонах.
**Методологические лимиты (пример, настраиваются):**
- Рост рабочего веса ≤ +5% (или ≤ +2.5 кг на базовых) в неделю на упражнение.
- Изменение недельного объёма ≤ ±20% (кроме запланированного делаода).
- Не поднимать одновременно объём и интенсивность (одна переменная за раз).
- Делоад — не реже чем каждые 6 недель (объём −40–50%).
- Не более 2 «тяжёлых» (RPE≥8) тренировок на одну мышечную группу в неделю.
- Замена упражнения — только в пределах той же двигательной группы (жим → жим, тяга → тяга); список групп — атрибут библиотеки упражнений.
- Лимит общего числа подходов за тренировку (например, ≤ 25).
**Человеко-машинный контур:** предложение показывается в приложении в виде диффа («Вес в приседе: 100 → 102.5, потому что…») с кнопками «Принять / Изменить / Отклонить». По мере доверия настраивается «бюджет автобонуса»: например, микроправки весов ±5% применяются сами, смена упражнений и делоады — только по подтверждению.
---
## 7. Офлайн-fallback и отказоустойчивость
Требование автономности из ТЗ (разд. 5) распространяется и на планирование:
- Если LLM недоступен (нет сети, лимит API, ошибки) — работает **контур правил**: прогрессия по формулам + делоад по счётчику недель. Программа продолжает корректироваться безопасно, просто без «умных» интерпретаций.
- Правила-микроправки (после каждой тренировки) вообще не зависят от LLM — это базовый режим, LLM — надстройка.
- Корректировки — фоновая задача (cron после синхронизации тренировки / вечером воскресенья), не в реальном времени, поэтому очереди и ретраи из ТЗ подходят без изменений.
---
## 8. Компоненты сервера и схема данных
```
┌────────────┐ .fit ┌──────────────┐ факты ┌───────────────┐
│ Garmin │─────────▶│ FitParser │────────▶│ PostgreSQL │
│ Connect API│ │ (из ТЗ) │ │ факты, БД │
└────────────┘ └──────────────┘ └──────┬────────┘
│ сводка
┌───────────────┐ план/дельта │ ▲
│ LLM Planner │◀──────────────┘ │
│ (промпт+JSON) │ │
└──────┬────────┘ ┌──────┴────────┐
▼ │ MetricsEngine │
┌───────────────┐ │ (правила, │
│ Validator │──✗ ошибка→ретрай│ прогрессия) │
└──────┬────────┘ └───────────────┘
▼ версия
┌───────────────┐ дифф «принять?» ┌──────────┐
│ ProgramStore │────────────────────▶│ Телефон │
│ (версии JSON) │◀────────────────────│ (UI) │
└───────────────┘ подтверждение └──────────┘
```
Таблицы (минимум):
- `programs` — id, версия, статус, author, created_at, JSON-тело.
- `program_changes` — from/to версия, rationale, отчёт валидатора, решение пользователя (auto/accepted/rejected/edited).
- `workout_facts` — факт тренировки из .fit + голосового ввода (уже следует из ТЗ).
- `workout_metrics` — посчитанные признаки (тоннаж, e1PM, adherence, пульс-метрики).
- `athlete_state` — профиль, цели, текущие веса, счётчики недель до делоада.
- `llm_calls` — журнал: входная сводка (хэш), ответ, результат валидации, принят ли. Нужен для анализа качества и отладки промптов.
---
## 9. Полный жизненный цикл (пример недели)
1. **Пн–Сб.** Пользователь тренируется: часы пишут .fit, телефон голосом пишет подходы, всё синхронизируется на сервер.
2. **После каждой тренировки:** MetricsEngine обновляет факты → правила двигают веса на следующее занятие (+2.5 кг / удержание / −5%). Без LLM, мгновенно.
3. **После тренировки — опрос 1–10** в приложении (5 секунд).
4. **Воскресенье, cron:** сервер собирает сводку недели → LLM предлагает план следующей недели (или «без изменений») → валидатор проверяет → дифф в приложении → пользователь принимает (или авто в рамках бюджета доверия).
5. **Каждые 4–6 недель:** LLM строит следующий мезоцикл (делоад/новая волна) на сводке всего цикла.
6. Всё попадает в журнал → раз в месяц можно глазами оценить: растут ли e1PM, как часто LLM менял план и помогало ли это (сравнение трендов до/после изменений).
---
## 10. Риски и их обработка
| Риск | Обработка |
|---|---|
| LLM «перетренирует» пользователя | Жёсткие лимиты валидатора + делоад-счётчик в правилах (код, не LLM) |
| LLM занижает нагрузку, прогресс остановится | Метрика «плато» в MetricsEngine (нет роста e1PM N недель) триггерит обязательное предложение изменений |
| Пользователь не принимает правки | Бюджет автобонуса + показывать rationale простым языком; правки сохраняются как «edited» и учитываются в следующих промптах |
| Расходы на API | Вызовы редки (1 раз в неделю + мезоцикл), сводка компактная — копейки; микроправки бесплатны (правила) |
| Некачественный голосовой ввод портит факты | Сопоставление голосовых данных с .fit по времени (уже в ТЗ п. 5.1) — расхождения помечаются и не идут в метрики как истина |
| Субъективность RPE | Опрос после тренировки + расчёт по выполненным повторам; расхождения — сигнал, а не приговор |
---
## 11. Порядок внедрения (в развитие MVP из ТЗ)
1. **Шаг 1 (без LLM):** ProgramStore с версиями JSON + правила прогрессии после тренировки + MetricsEngine (тоннаж, adherence, e1PM). Система уже полезна.
2. **Шаг 2:** еженедельный вызов LLM (структурный вывод) + валидатор + дифф-подтверждение в приложении + журнал `llm_calls`.
3. **Шаг 3:** мезоцикловое планирование, двухпроходная схема «генератор+ревьюер», опрос RPE, автоматизация бюджета доверия.
Такой порядок даёт работающую систему на каждом этапе и позволяет накапливать данные для LLM, а не проектировать его вслепую.