3,5 месяца, 2 071 коммит, своя пекарня, два ИИ-ассистента и VPS с 2 ГБ RAM
У нас своя небольшая пекарня «D&K Sourdough». Заказы на хлеб долгое время принимались вручную — клиент пишет в Telegram‑группу, мы записываем, следим за тем, чтобы не набрать больше, чем сможем отпечь, потом вручную уведомляем по готовности. Хотелось это автоматизировать. У меня самого есть небольшой опыт в программировании — делал несколько приложений для себя на Delphi в лохматых 2000-х, писал Access приложения, даже напрограммировал систему автополива для теплицы на Arduino, которая работает вот уже 7 лет. И тут, узнав каких способностей уже достигли LLM, 19 января сделал initial commit: один файл bot.py, SQLite, три кнопки. Меню, корзина, клиенты делают заказ, мы получаем оповещение. Сначала все писал с чатом Deepseek и виндовым Copilotom, но потом узнал про агентов и все заверте...
Сейчас это ~91 000 строк Python, 143 React‑компонента, схема БД версии v105 и два работающих ИИ‑ассистента — всё на том же VPS с 2 ГБ RAM.
Неделя первая: система слотов
На следующий день после initial commit выяснилась первая нетривиальная вещь: ёмкость печи ограничена. Большой хлеб занимает 2 слота, маленький — 1, максимум 20 слотов на дату. Если 12 человек возьмут большой — ни печь ни мы не справимся физически.
Нужна oven_limits таблица с max_slots и used_slots, причём бронирование должно быть атомарным — иначе два человека могут одновременно взять последний слот, оба получат подтверждение и будут ждать хлеб, которого не будет.
Это первая конкурентная задача в проекте, и она сразу заставила думать о транзакциях.
За неделю бот обзавёлся корзиной с мульти-заказом, регистрацией пользователей, отдельной admin-панелью со сменой статусов заказов, уведомлениями в Telegram-группу («осталось 2 слота!», «всё разобрали!») и автовотчером для перезапуска при изменении файлов. Всё это был один класс BreadOrderBot на 1500 строк, но работало.
Февраль: из бота в продукт
REST API + React PWA
В начале февраля стало понятно, что Telegram — не единственный нужный интерфейс. Клиенты хотят видеть каталог хлеба как нормальный сайт, оформлять заказ без Telegram. Началась параллельная разработка: FastAPI поверх той же SQLite-базы и React 18.3 + TypeScript 5.6 + Vite 6.0 + Tailwind — фронтенд. JWT сначала в localStorage, потом аудит безопасности сказал «нет» — переехало в httpOnly cookie.
Деплоить vite build на сервер с 2 ГБ RAM было нельзя — OOM при активном боте и API. Выработали правило: всегда билдить локально, деплоить через scp. Правило соблюдается до сих пор.
cd Copilot/frontend && npm run build scp -r dist/* root@server:/root/bread_bot/frontend/dist/ ssh root@server "systemctl restart bread-api"
PWA получила: каталог хлеба с фотографиями, корзину, историю заказов, личный кабинет, Web Push уведомления (VAPID), fullscreen-лайтбокс для фото хлеба, splash screen с реальным progress bar.
Аналитика и учёт затрат
Захотелось наконец понять, зарабатывает ли пекарня деньги или просто занята. Добавили модуль учёта затрат: FIFO-инвентаризация ингредиентов (закупки, расход, остатки), техкарты с рецептами, производственные партии от замеса до выпечки с разбивкой себестоимости на материалы + косвенные + амортизация, оборудование с линейной амортизацией, несколько кассовых счетов с переводами, кредиты с аннуитетными графиками.
Схема БД к концу февраля: v41 → v67. Каждая миграция — Python-функция migrationvNN().
Аудит безопасности
Когда появился полноценный API, провели первый аудит — 170 находок, 14 критических. Самые важные:
Race condition в бронировании слотов — два пользователя одновременно берут последний слот. Решение:
BEGIN IMMEDIATE+db_transaction()с передачейconn=в sub-функции. Паттернsave_order_atomic()стал эталоном для всех похожих операций.27 мест с
date.today()вместоtoday_local()— пекарня в UTC+11, сервер в UTC, разница 11 часов. В полночь по серверному времени ещё утро по сахалинскому, и заказы падали не в ту дату.TOCTOU в инвентаризации — чтение остатка и его списание должны быть в одной транзакции.
SQL injection через f-string в названиях таблиц — исправлено через whitelist.
Март: плагины и телефонная авторизация
Плагиновая архитектура
К марту бот разросся до 10 миксинов в BreadOrderBot и 6 в AdminPanel. Добавление новой фичи требовало редактировать несколько файлов одновременно. Сделали plugin system:
class Plugin(ABC): @property @abstractmethod def name(self) -> str: ... def register_handlers(self, dp, bot_context): ... def register_admin_routes(self) -> dict: ... def register_admin_menu_items(self) -> list: ... def register_admin_text_handlers(self) -> dict: ... def on_startup(self, bot_context): ... def on_shutdown(self): ...
Сейчас активных плагинов 6: аналитика, отзывы, управление затратами, исторические заказы, инструменты разработчика, сообщения в группу.
Авторизация
OTP через Telegram-бот (6 цифр, TTL 5 минут), Telegram Login Widget для PWA, обычный пароль через bcrypt, rate limiting с lockout (5 попыток → блокировка на 5 минут).
Активность и когорты
Добавили полноценную аналитику поведения: когортный анализ удержания, воронка конверсии, сегменты клиентов, выручка по дням/неделям/месяцам, топ хлебов и клиентов.
Апрель: три ИИ-ассистента
Голосовой ассистент для администратора
Голосом текст набирается в три раза быстрее, чем печатанием. Хотелось голосом узнать статус заказов или изменить лимит печи. Пайплайн получился такой:
Голосовое сообщение (OGG) → Groq Whisper Large v3 Turbo (STT, ru) → Claude Haiku (intent parse с tool_use + снимок БД) → Кнопка «Да/Нет» → Атомарное выполнение в БД
Конфигурация минимальна — два API-ключа в .env: GROQ_API_KEY и ANTHROPIC_API_KEY.
Главная инженерная проблема: сервер с 2 ГБ RAM. Claude CLI-бинарник весит ~600 МБ и вызывает OOM при запуске рядом с работающими ботом и API. Решение: вместо CLI — Anthropic Python SDK напрямую, без лишнего процесса, прямо на VPS.
Василиса — клиентский чат-ассистент
Чат-бот «Василиса» в PWA для клиентов. Знает весь каталог хлеба с КБЖУ, ингредиентами и описаниями, видит доступные даты и остатки слотов, знает историю заказов конкретного клиента, может предложить хлеб в корзину через специальный маркер CART_PROPOSAL:{...}.
VASSILISA_PERSONA = ( "Ты Василиса — дружелюбный ассистент пекарни D&K Sourdough. ?\n" "Характер: говоришь как человек, которая сама влюблена в этот хлеб — тепло, " "с искренним интересом, иногда с юмором. Не как справочник, а как друг-пекарь.\n" "Пол: ты девушка. ВСЕГДА говори о себе в женском роде." )
Василиса ведёт долгосрочную память клиентов (customer_ai_memory_repo.py) — сводка по каждому пользователю, которая обновляется автоматически. Если клиент писал три недели назад и упоминал, что любит ржаной с тмином — Василиса это помнит.
Самое тонкое место — не вызов API, а системный промпт: 200 строк ограничений, которые предотвращают выдачу CART_PROPOSAL без явного называния хлеба, добавление в корзину на первом сообщении сессии, раскрытие себестоимости, prompt injection через текст меню.
Пока Василиса как фича ради фичи, ей особо никто не пользуется. При этом Стив, о котором пойдет речь ниже, мне очень помогает.
Стив — проактивный AI-агент
Стив - мой личный помощник в Телеграм. Работает по расписанию cron и сам инициирует действия. Не отвечает на вопросы — наблюдает и докладывает.
0 22 * * * python3 steve_proactive.py morning # Утренний брифинг 0 10 * * * python3 steve_proactive.py evening # Вечерняя сводка 0 23 * * 0 python3 steve_proactive.py weekly # Еженедельный аудит 0 19 1 * * python3 steve_proactive.py monthly # Месячная рефлексия 0 20,2,9 * * python3 steve_proactive.py code_review # Аудит кода 3 раза в день
Утром присылает сколько заказов, выручку и остатки слотов. Еженедельно анализирует тренды и сравнивает с прошлой неделей. Code review ищет bare except, прямые os.getenv(), большие файлы — присылает отчёт в Telegram. Раз в месяц анализирует рост продаж и предлагает изменения в ассортименте.
У Стива есть настроение, которое рассчитывается из сигналов бизнеса (растут продажи — energized, падают — concerned) и инжектируется в каждый промпт:
MOOD_TONES = { "energized": "Ты полон энергии, наблюдения острее, язык живее", "calm": "Ты спокоен и методичен, фокус на деталях", "concerned": "Ты обеспокоен — что-то идёт не так, нужна честность", "curious": "Ты любопытен, хочется разобраться глубже", }
Стив ведёт журнал — записывает мысли после каждого сеанса. У него есть убеждения (beliefs), которые формируются из наблюдений и могут быть подтверждены или опровергнуты реальными данными. Есть DeepSeek fallback: если Anthropic отдаёт rate limit, запросы автоматически переходят на DeepSeek API с совместимым интерфейсом.
В апреле добавили вкладку AdminSteve в React-приложении — можно видеть записи в журнале, текущее настроение, убеждения и предложения по развитию характера.
Май: тюнинг
Работа над UX каталога: новая тема «Cinematic» разработанная в Claude Design — полноэкранный hero с анимированным паром над хлебом (фича еле видная, но пусть будет), cinematic ticker с реальными названиями из каталога. Активно пробуем DeepSeek v4 Pro Max в качестве агента и все выглядит очень многообещающе.
Как это устроено сейчас
Стек
Слой |
Технология |
|---|---|
Telegram-бот |
Python, python-telegram-bot 13.15 (sync), SQLite WAL |
API |
FastAPI, Pydantic v2, Uvicorn |
БД |
SQLite, 67 репозиторий-модулей, v105 схема |
Фронтенд |
React 18.3, TypeScript 5.6, Vite 6.0, Tailwind CSS 3.4 |
PWA |
Service Worker, Web Push (VAPID, pywebpush) |
ИИ |
Claude Sonnet/Haiku (Anthropic), DeepSeek (fallback), Groq Whisper (STT) |
Сервер |
VPS, 2 ГБ RAM, Python 3.8.10, systemd, Nginx |
Авторизация |
JWT (httpOnly cookie), Telegram OTP, Telegram Login Widget |
Цифры
Метрика |
Значение |
|---|---|
Первый коммит |
19 января 2026 |
Всего коммитов |
2 071 |
Python файлов (без venv) |
301 |
Строк Python-кода |
~91 000 |
React-компонентов |
143 |
API-роутеров |
31 |
Модулей БД |
67 |
Версия схемы БД |
v105 |
Тестов |
628+ |
Активных плагинов |
6 |
ИИ-ассистентов |
2 (Стив - мой личный помощник, Василиса работает с клиентами) |
Время разработки |
~3,5 месяца |
Архитектура БД
67 отдельных репозиторий-модулей, каждый отвечает за свою область:
db/ ├── order_repo.py # Заказы ├── bread_repo.py # Каталог хлеба ├── oven_repo.py # Слоты печи ├── inventory_*.py # FIFO-инвентаризация (7 модулей) ├── analytics_*.py # Аналитика (6 модулей) ├── account_repo.py # Кассовые счета ├── loan_repo.py # Кредиты ├── production_repo.py # Производственные партии ├── steve_repo.py # «Душа» Стива ├── customer_ai_memory_repo.py # Память Василисы ├── voice_memory_repo.py # Контекст голосового ИИ └── ... ещё 55 модулей
Атомарность критических операций — через db_transaction() с поддержкой вложенных транзакций через SQLite SAVEPOINT.
Архитектура бота
Бот построен на mixin-архитектуре: 10 миксинов в BreadOrderBot, 6 в AdminPanel. MRO Python делает всё остальное — методы одного миксина спокойно вызывают self.send() из другого. Плюс plugin system с 9 lifecycle hooks.
Что в продакшне
Сервер: VPS 46.17.106.35, 2 ГБ RAM. Два systemd-сервиса: bread-bot.service (Telegram) и bread-api.service (FastAPI + статика React).
Автоматические бэкапы БД каждую ночь: WAL checkpoint + gzip + email. Стив запускается через cron три раза в день для code review, утром и вечером для сводок. Его мысли приходят прямо в Telegram.
Несколько вещей, которые стали очевидны в процессе
SQLite нормально справляется с нагрузкой небольшого бизнеса. При правильном использовании — WAL mode, BEGIN IMMEDIATE для конкурентных записей — 67 модулей, 105 миграций, FIFO, аналитика работают на одном .db файле без проблем.
105 версий схемы за 3,5 месяца — это процесс, а не проблема. Каждая новая фича начинается с migrationvNN(). Откат в крайнем случае — через резервную копию.
ИИ на продакшне с ограниченными ресурсами требует осторожности. 2 ГБ RAM означает: никаких heavy-weight процессов рядом с лёгкими. Claude CLI (600 МБ) + бот + API = OOM. Anthropic Python SDK напрямую + DeepSeek fallback — нормально работает. Groq Whisper без GPU — просто HTTP-запрос.
Race condition — самый неочевидный баг. Не OOM и не падение, а два пользователя, которые одновременно берут последний слот и оба получают подтверждение. BEGIN DEFERRED + два отдельных соединения != транзакция. BEGIN IMMEDIATE + один conn=, пробрасываемый в sub-функции, — транзакция.
Инварианты кодовой базы важнее красоты. «Добавим эту фичу прямо сейчас» — нормально, но только если следовать правилу: один способ открыть соединение с БД, одна конфигурация, один способ получить текущее время в правильном часовом поясе. Когда эти инварианты начинают нарушаться — хаос нарастает быстро.
Комментарии (3)

boronov
08.05.2026 08:16Немного складывается ощущение, что статья это выжимка из чата который генерировал этот сервис. Как минимум IP-адрес я бы не светил.
Имхо в подобных историях интереснее читать о том что это дало бизнесу и какой был реальный профит, а не о технической составляющей. Особенно учитывая что сам автор признаёт что далёк от разработки.
105 версий схемы БД за 3,5 месяца это примерно одна миграция в день, то есть почти ничего не планируется заранее и разработка довольно хаотична. 67 репозиторий-модулей для такой задачи навскидку кажутся оверкиллом, саппортить это в одиночку будет непросто. Чем было обосновано такое решение?
SurMaster
Как пет-проект для изучения промпт-инжиниринга — 10/10. Как решение для бизнеса — 2/10. ТС создал проект, который требует полировки трижды в день. Если автор завтра решит уехать в отпуск или, не дай бог, заболеет — пекарня останется без системы управления, потому что никто в этом мусоре из 91к строк не разберется. На код любопытно было бы взглянуть, но предвижу спагетти в коде, дыры в безопасности и неоптимизированные конфиги, так как данный сервер вполне должен без проблем держать такую нагрузку. ИИ ревью кода написанного ИИ же? Если той же моделью - сомнительное решение.
Мой совет - не работайте напрямую на сервере, зачем??? настройте CI/CD, правьте локально и после тестов - пуш на сервер. Или уж хотя бы IDE ZED используйте по ssh, если не можете без этого.
Если «Василиса» и «Стив» хранят историю диалогов прямо в оперативе (а не в Redis/DB) для каждого пользователя, то при паре активных сессий память просто кончается.
Если WAL-логи или кэши БД настроены криво, или если он делает SELECT * из таблицы на 100к строк и пытается это обработать в памяти — привет, OOM
И вообще - nocode не рассматривали?
P.S.: вы хорошо подумали, когда написали ip адрес сервака? “ssh root@server …” заслуживает отдельного коммента