Сейчас в тренде подсчёт калорий и БЖУ — скажу честно, сам считаю) А ещё вайбкодинг. Для меня ИИ-агенты стали чем-то вроде 3D-принтера: если есть фантазия, сделать можно что угодно.

Моя фантазия была бытовой. Каждую неделю дома один разговор: «Что едим?» — «А что есть?» — «Полпачки риса и курица, которая скоро умрёт». Потом полчаса в приложении, корзина в которой все что угодно, ведь ты пока кликаешь, невольно добавляешь лишние, а курица всё-таки умирает.

Я захотел так: говорю «3 дня, двое, 5000 ₽, примерно 2000 ккал в день» — и получаю меню с рецептами, где сначала используется то, что уже лежит дома, и собранную корзину в Яндекс Лавке. Оплачиваю сам, одной кнопкой в приложении.

Я не профессиональный разработчик. Пишу на Python для себя и честно называю себя вайбкодером: код пишут ИИ-агенты, а я ставлю задачи, проверяю и говорю «да» или «нет».

Сразу честно: дорога протоптана. Про MCP для Лавки уже есть статья автора yandex-lavka-mcp, про меню под КБЖУ — статья про MCP ВкусВилла. Технику API ищите там. У меня другое: семейный бот в Telegram, который знает, что лежит в холодильнике, и никогда не платит сам. Ниже — как навайбкодить такое и не развалить: процесс, проверки в коде и грабли, которых в тех статьях нет.

Что получилось

Telegram-бот с Mini App. На главном экране — кухня и шеф-осьминог по имени Осьма. Картинку нарисовала нейросеть, а зоны нажатия на ней — настоящие кнопки.

Холодильник. /
Пишешь боту обычным текстом: «2 кг картошки, 10 яиц, молоко до 10.11». Модель переводит это в граммы, миллилитры и штуки и ставит типовой срок годности. Сохраняется только после кнопки «✅ Сохранить». Список отсортирован по тому, что раньше испортится. Соль, сахар, масло и специи помечены «всегда есть» и не покупаются никогда.

Заказать продукты.
Бюджет, дни, люди, калории, какие приёмы пищи нужны и что можно повторять. Дальше бот:

  1. составляет меню с рецептами — сначала из того, что есть, скоропортящееся в первые дни;

  2. вычитает холодильник из списка покупок;

  3. ищет товары в Лавке и выбирает подходящие;

  4. показывает черновик: меню, товары, сумма, «🧊 Из холодильника»;

  5. по кнопке «🛒 Собрать корзину» кладёт всё в корзину Лавки и проверяет, что каждая позиция на месте.

Шеф Осьма.
Тапаешь по осьминогу и пишешь «борщ» или «что-нибудь с курицей и рисом». Шеф выбирает блюдо, пишет рецепт и собирает корзину под него.

После заказа — «📦 Привезли» добавляет купленное в холодильник, «🍳 Приготовлено» списывает продукты по дням меню. По желанию бот напоминает, что пора готовить.

Ботом пользуюсь я и семья. У каждого свой профиль, свой холодильник и свой аккаунт Лавки.

Чего бот не умеет — и не будет уметь

Тратить деньги. Инструментов оформления, оплаты и отмены заказа нет даже в коде. Это не недоделка, а правило проекта, записанное в инструкциях для агентов первой строкой раздела «Чего не делать». Модель может ошибиться с товаром, количеством или ценой. Пусть ошибается в корзине, которую я посмотрю перед оплатой, а не в заказе, который уже едет.

Как это устроено

Сначала был только MCP-сервер — 15 инструментов для ИИ-агента: адреса, поиск, карточка товара с КБЖУ, корзина, холодильник, меню. Я писал в чате агента «собери ужины на 3 дня» — и агент ходил в Лавку сам. Работало, но для семьи «открой терминал и поговори с агентом» — так себе интерфейс. Поэтому появился бот, а MCP остался для меня.

Оба ходят в одно ядро. Ядро держится на одном принципе, который я бы теперь ставил в любой проект с LLM:

Модель предлагает, код проверяет.

Модель придумывает блюда и выбирает товар из найденных кандидатов. Всё, что можно посчитать, считает код: ровно N дней, повторы только по флагу, калории в пределах ±15 % от цели, сумма к оплате не больше бюджета + 15 %, сколько упаковок взять, есть ли товар на складе. Нарушение модели исправляется кодом, а не «доверяем, она же умная».

В модель уходят названия, фасовки и цены товаров. Cookies и адреса — нет.

А где API Лавки?

Публичного API у Лавки нет, а официальный путь я не искал: делал только для себя. Сервер ходит в тот же веб-API, что и сайт lavka.yandex.ru, с моими сессионными cookies. Разбирать CSRF, гонки в корзине и её версии здесь не буду — это подробно сделал автор yandex-lavka-mcp в статье «Как я научил Claude заказывать продукты в Яндекс Лавке». Его эндпоинты и заголовки агент взял за основу — спасибо, — а живой трафик их подтвердил.

Это неофициально. API может измениться завтра, и это может не нравиться Яндексу. Я автоматизирую свой аккаунт для своих покупок и осознаю риск (поэтому аккаунт без данных и карт на которых есть баланс)

Как строил: 4 дня, 143 коммита

Итог: ~4,4 тыс. строк Python, ~1,3 тыс. строк HTML/JS в Mini App и 498 тестов — тестового кода почти столько же, сколько рабочего.

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

1. Сначала спека. Агент задаёт вопросы по одному: зачем, для кого, что считаем успехом, что точно не делаем. Из ответов получается спека на пару страниц. Для холодильника, например, я сам решил: холодильник у каждого свой, сроки годности в первой версии, списание — ручной кнопкой, без «умного» автосписания.

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

3. Потом TDD. Сначала падающий тест, потом код. Агент, который пишет тест до кода, ошибается заметно реже, а главное — я вижу, что именно он проверяет.

4. Ревью другим агентом. Код, написанный одним агентом, смотрел другой, с другой моделью. Первое же ревью нашло 8 важных проблем. Например, сохранение меню сначала перезаписывало файл, а потом могло упасть на отрисовке — и портило данные. Или инструмент экспорта писал в любой путь, который передаст модель.

5. STATE.md — эстафетная палочка. Над проектом работали несколько агентов на разных моделях: дешёвая — на рутину, дорогая — на проектирование и ревью. Память между ними — один файл: что сделано, что дальше, какие ловушки. Каждая сессия начинается с его чтения и заканчивается его обновлением. Вот кусок оттуда:

## Ловушки (не повторяй)

- **Адрес определяет даркстор.** Перед сборкой сверь цены контрольного товара
  с приложением: расхождение в пару рублей = другой склад, и наличие там другое.
- **Поиск плохо понимает «свежие»:** на «огурцы свежие» первым идёт малосольное,
  на «помидоры» — черри. Фильтруй по словам в названии и фасовке.
- **Проверяй факт после добавления:** add_to_cart → пауза 2 с → get_cart;
  позиция должна быть на месте и без флага. Иначе Лавка её молча выбросит.

Без этого файла каждый новый агент наступал бы на те же грабли. С ним — наступает на новые.

Грабли

Самое интересное. Все 498 тестов работают на моках. Моки — это то, как агент представляет себе API. Реальность оказалась изобретательнее.

1. Адрес = склад

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

Оказалось, у Лавки нет «склада вообще». Есть даркстор, и он определяется адресом. Агент взял первый адрес из избранного, а в приложении у меня был выбран другой. Разные склады — разные цены и разные остатки. Похожую ловушку описывал автор yandex-lavka-mcp, но у него товары подмешивались из «Большой Лавки», а у меня из-за адреса на чужой склад уехала вся корзина.

Лечение:

  • адрес и идентификатор места всегда меняются вместе: сменил адрес — старый контекст склада стирается;

  • наличие — только по карточке товара: флаг available в поиске каталожный, ему верить нельзя;

  • после добавления — пауза 2 секунды и перечитать корзину: позиция должна быть на месте и без пометки «нет на складе»;

  • проверка «тот ли склад» — цена контрольного товара (рис, гречка) должна совпасть с приложением до рубля.

Моки этого не поймали бы никогда: в моке склад один.

2. Cookies из браузера с сюрпризом

Чтобы не копировать cookies руками, агент написал выгрузку прямо из профиля Chromium: расшифровка через DPAPI Windows и AES-GCM. Не работало. В новых версиях схемы базы Chromium приписывает к значению 32 байта SHA-256 от домена. Без их отрезания httpx падал на не-ASCII байтах в заголовке.

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

Для бота сделал проще и безопаснее. Человек открывает из бота страницу, вставляет строку Cookie из DevTools, и она уходит по HTTPS прямо на сервер, мимо истории чата. Кто её прислал, сервер проверяет подписью Telegram:

def verify_init_data(init_data: str, bot_token: str, *, max_age: int = 3600) -> int:
    """Проверить подпись Telegram Web App и вернуть Telegram ID пользователя."""
    pairs = dict(parse_qsl(init_data or "", keep_blank_values=True))
    received = pairs.pop("hash", "")
    check = "\n".join(f"{k}={v}" for k, v in sorted(pairs.items()))
    secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    expected = hmac.new(secret, check.encode(), hashlib.sha256).hexdigest()
    if not received or not hmac.compare_digest(expected, received):
        raise InitDataError("подпись не сошлась")
    ...

3. Агент запускал вчерашний сервер

Агент добавил 15-й инструмент, прогнал тесты — зелёные. Запускаю сервер — инструментов 14. Дважды.

Пакет ставился в .venv не в режиме разработки: сборщик клал копию server.py в site-packages, и uv run пересобирал её не всегда. Лечится только явной переустановкой пакета. Теперь это записано в правилах проекта, которые агент читает в начале каждой сессии.

4. Модель думает, пока меню остывает

Модель для меню по умолчанию «рассуждает» перед ответом. Замер показал: ~95 % токенов ответа уходит на рассуждения, и меню на неделю с рецептами не укладывалось в 120 секунд. Для задач «верни JSON по схеме» рассуждения выключил, и меню стало приходить за разумное время.

5. Один и тот же обед пять дней подряд

Прогон «5 дней, двое, 5000 ₽» вернул меню на ~1000 ккал на человека в день из 6 продуктов. Обед все пять дней одинаковый. Формально бюджет соблюдён, придраться не к чему.

Я дал модели приоритеты: калорийность → бюджет → разнообразие → баланс БЖУ. А главное — код проверяет первые три сам: калории ±15 % от цели, обед не повторяется без флага «повторять», обед ≠ ужин в один день, один белок — не больше половины обедов и ужинов. Не прошло — меню уходит модели на правку с конкретной претензией. Баланс БЖУ остался мягким ориентиром: если душить модель всеми ограничениями сразу, она не укладывается ни в одно.

6. Три пачки лука

Черновик на двоих: три упаковки лука и три — говядины. Арифметика была верной. Модель положила в меню 950 г лука, а код округлял упаковки строго вверх: нужно 900 г говядины, упаковка 400 г — значит, 3.

Два фикса. В промпте — «лук и морковь 30–40 г на человека в блюдо». В коде — допуск: если до целой упаковки не хватает не больше 15 % веса, лишнюю не берём. 900 г из 2 × 400 — это недобор 11 %, переживём.

Соседняя проблема: у Лавки есть лимит на количество одного товара в корзине. Меню хотело 2,2 кг голени, а Лавка давала одну упаковку на 750 г. Теперь такой продукт возвращается в правку меню как «не хватает», а не теряется молча.

7. «Творог» — это не «творог 5 %»

В холодильнике лежал «Творог», в меню был «творог 5%». Бот не узнал творог и купил ещё. Сопоставление по словам вместо точного совпадения решило проблему, а тест закрепил.

Почему я не выкладываю код

Логичный вопрос: «А можно себе?» Кода в открытом доступе нет. Причины:

  1. Неофициальный API. Это автоматизация через приватный веб-API с пользовательскими cookies. Как к этому отнесётся Яндекс, решать не мне — и не вам за меня

  2. Cookies = весь аккаунт Яндекса. Готовый бот, поднятый «по инструкции из интернета», с первого же дня хранит самое ценное, что есть у пользователя. Сделать это действительно безопасно сложно

  3. Ответственность. Если чужая копия утечёт, кто-то лишится аккаунта или получит корзину не на тот склад, отвечать я не хочу

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

Что я вынес про вайбкодинг

  1. Процесс важнее модели. Спека, план, тесты и ревью делают из «агент что-то написал» предсказуемую работу. Без них 143 коммита за 4 дня превратились бы в 143 коммита хаоса.

  2. Модель предлагает, код проверяет. Всё, что можно посчитать, — считает код. Калории, бюджет, упаковки, наличие. LLM — для того, что посчитать нельзя: придумать ужин.

  3. Моки — это фантазия агента об API. 9 дефектов из первого живого прогона не поймал бы ни один тест. Живой прогон нужен как можно раньше.

  4. Память агента — это файл. STATE.md с разделом «Ловушки» сэкономил мне больше времени, чем любая модель.

  5. Ревью другим агентом окупается всегда. Свежий взгляд другой модели находит то, к чему автор уже привык.

  6. Решения остаются за человеком. «Не тратить деньги», «ссылки только на Лавку», «списание руками, без автосписания» — это мои решения. Агент их исполняет и охраняет тестами, но не принимает.

Дальше планирую добавить Ленту, Перекресток и Пятерочку. Ведь эра персональных цен возвращается, а платить больше я не хочу.