Я технический лидер. Обычно моя работа не в том, чтобы писать код руками, а в том, чтобы запускать сложные hardware/software-проекты с нуля, собирать архитектуру, принимать ключевые технические решения, выстраивать команду и доводить систему до MVP в условиях неопределённости.
Я работаю на стыке механики, электроники, разработки и AI, связывая это в единый контур.
При этом я не владею языками программирования как разработчик.
Именно поэтому этот проект оказался для меня особенно интересным. Это был не просто внутренний бот, а практический эксперимент: можно ли, оставаясь в роли техлида и архитектора, собрать рабочий software-продукт через агентную разработку, не теряя в качестве решений, управляемости и инженерном здравом смысле.
Короткий ответ: да, можно.
Но очень быстро становится понятно, что агентные системы не избавляют от архитектуры. Они просто дают тебе другой интерфейс к реализации.
Всё началось с очень скучной боли
Как и у многих маленьких команд, закупки у нас долго жили в полумагическом режиме: “ну мы же договоримся”.
Сначала это бумажки. Потом заметки. Потом отдельный топик в Telegram. Потом рождается регламент: кидайте ссылку, цену, количество и обоснование. И какое-то время кажется, что всё, порядок почти наступил.
Спойлер: не наступил.
Часть заявок терялась. Часть проходила мимо. Где-то не было цены. Где-то вместо обоснования было что-то уровня “надо”. Где-то человек просто забывал, что вообще это просил. И в какой-то момент я поймал себя на том, что трачу время не на управление закупками, а на борьбу с формой хаоса.
И вот здесь появилась первая полезная мысль: если процесс влияет на деньги, он не должен жить как свободный формат переписки.
Очень быстро выяснилось, что это не просто “ботик в телеге”
У этой задачи почти сразу вырос инфраструктурный хвост.
Telegram в наших условиях без VPN работает нестабильно. Бот, который должен быть доступен всегда, не хочется держать на ноутбуке. Значит:
-
нужен иностранный VPS;
-
нужен свой VPN-контур;
-
нужен нормальный автозапуск;
-
нужен понятный деплой;
-
нужен способ не умереть после первого неудачного релиза.
То есть это был уже не сценарий “накидать скрипт на вечер”, а вполне нормальный инженерный контур.
Это, кстати, одна из первых вещей, которые такие проекты быстро показывают: “простая автоматизация” почти всегда врёт о своей простоте.
Код руками я не писал. Но система сама себя тоже не собрала
Здесь важно сказать честно, потому что тема агентной разработки сейчас легко скатывается либо в культ, либо в снобизм.
Этот проект я делал в связке с ChatGPT и агентной системой разработки. Но это не было сценарием “написал запрос, нейросеть всё собрала, пошёл пить чай”.
На практике это выглядело так:
-
я формулировал задачу;
-
раскладывал роли и ограничения;
-
уточнял бизнес-логику;
-
руками прогонял сценарии;
-
ловил противоречия и баги;
-
возвращал точечные правки;
-
снова тестировал.
То есть агент здесь был не заменой инженерного мышления, а ускорителем цикла. Он резко уменьшал время между “я вижу проблему” и “у меня есть новая реализация, которую можно проверить”.
И это был, пожалуй, главный практический вывод всего проекта: синтаксис кода можно делегировать сильнее, чем способность удерживать систему целиком.
Если логика мутная, агент просто быстрее реализует мутную логику. Если архитектура слабая, он быстрее размажет слабость по проекту. А вот если у тебя есть ясная модель процесса, ограничения и приоритеты, тогда agent-driven подход даёт очень серьёзное ускорение.
Первая идея была красивой. И плохой
Первая гипотеза выглядела очень соблазнительно: пользователь просто кидает ссылку на товар, а бот сам вытаскивает всё остальное. Название, цену, характеристики. Пользователю остаётся только написать обоснование и количество.
Звучало почти как магия.
Проблема в том, что в живой системе магия очень быстро становится источником хрупкости.
Почему идея сломалась:
-
сайты разные;
-
структура страниц нестабильна;
-
цены бывают неоднозначными;
-
часть данных рендерится неудобно;
-
marketplace-логика местами просто враждебна к надёжному извлечению.
И вот здесь у меня включилась привычка, которая много раз спасала и в hardware/software‑проектах: если процесс повторяемый и операционный, управляемость почти всегда важнее «умности».
Поэтому я отказался от магии и перешёл к wizard-сценарию:
-
пришли скрин;
-
пришли ссылку;
-
напиши обоснование;
-
укажи количество;
-
если OCR не распознал цену, введи её вручную;
-
проверь итоговую карточку и только потом подтверждай.
На вид это менее «вау». На деле это взрослее.
Почему я не стал строить всё вокруг LLM
Соблазн, конечно, был. Хотелось сделать модно: пусть модель понимает заявки, чистит мусорный ввод, нормализует смысл, может даже помогает с приоритетами.
Но довольно быстро стало ясно, что для этой задачи это плохой trade-off.
Мне нужен был контур, который:
-
живёт на слабом VPS;
-
не зависит от внешнего API в runtime;
-
не превращает каждую заявку в платный inference;
-
легко отлаживается;
-
предсказуем в поведении.
В итоге стек получился очень приземлённым, и именно поэтому правильным:
-
Python
-
Telegram bot framework
-
SQLite
-
локальный OCR
-
HTML-отчёты на шаблонах
-
systemd для сервисов и таймеров
Вот это решение я до сих пор считаю правильным. Не потому что LLM плохи. А потому что не каждая задача выигрывает от их присутствия. Иногда нормальная FSM, строгая валидация и понятные статусы дают больше пользы, чем самый умный AI-слой.
В какой-то момент я понял, что делаю не бота, а систему состояний
На старте кажется, что заявка — это просто карточка. Потом выясняется, что нет, это сущность с жизненным циклом.
Она может быть:
-
черновиком;
-
недозаполненной;
-
ожидающей подтверждения;
-
подтверждённой;
-
на модерации;
-
возвращённой на доработку;
-
перенесённой на следующий период;
-
срочной;
-
отправленной.
И если этот жизненный цикл не формализовать, бот очень быстро снова превращается в хаотичный чат с красивыми кнопками.
Поэтому основа логики ушла в FSM и явные доменные статусы.
Очень коротко это выглядело так:
class PurchaseWizard(StatesGroup): waiting_for_image = State() waiting_for_link = State() waiting_for_reason = State() waiting_for_qty_lots = State() waiting_for_lot_size = State() waiting_for_manual_price = State() waiting_for_confirmation = State()
А на уровне бизнес-состояний уже так:
class PurchaseStatus(str, Enum): DRAFT = "draft" AWAITING_IMAGE = "awaiting_image" AWAITING_LINK = "awaiting_link" AWAITING_REASON = "awaiting_reason" AWAITING_QTY = "awaiting_qty_lots" AWAITING_CONFIRMATION = "awaiting_confirmation" COMPLETE = "complete" PENDING_MODERATION = "pending_moderation" RETURNED_FOR_REVISION = "returned_for_revision" APPROVED = "approved" CANCELLED = "cancelled" SENT = "sent"
И вот здесь лежит очень важный практический вывод. Как только в процессе появляются роли, деньги и дедлайны, ты обязан перестать мыслить “хендлерами на сообщения” и начать мыслить “системой состояний”.
Архитектура получилась простой. И это было специально
Я очень сознательно не хотел собирать из этого маленькую распределённую трагедию.
Хотелось:
-
минимум внешних зависимостей;
-
работа на слабом VPS;
-
понятная отладка;
-
простой деплой;
-
отсутствие архитектурного ожирения.
В итоге схема получилась такой:
Telegram user ↓Bot handlers / FSM ↓Purchase service ├── SQLite ├── OCR service ├── Moderation logic ├── HTML report builder └── Reminder / maintenance jobs ↓ systemd service + timers
А структура проекта — примерно такой:
purchase_bot/├── app/│ ├── bot/│ ├── db/│ ├── services/│ ├── templates/│ └── main.py├── data/│ ├── uploads/│ ├── reports/│ └── bot.db├── scripts/├── deploy/└── README.md
И да, без Docker на первой версии. Без тяжёлой БД. Без попытки сразу делать “платформу”.
Мне кажется, для внутренних инструментов это важная мысль: простая архитектура — не признак слабости, а часто признак зрелости.
Самая неожиданная боль была не в OCR и не в отчётах
Самой нервной частью проекта оказался UX внутри самого Telegram.
На старте кажется: ну что там, сообщения и кнопки. На деле Telegram очень быстро начинает работать против тебя, если не сделать интерфейс дисциплинированным.
У меня вылезали такие радости:
-
старые кнопки продолжали жить;
-
карточки наслаивались;
-
после редактирования рядом висели старая и новая версии заявки;
-
уведомления не исчезали вовремя;
-
«Назад» в одном месте работал правильно, а в другом ломал сценарий;
-
часть экранов зацикливалась;
-
в чате оставался мусор, который только мешал.
И это был очень неприятный, но полезный момент. Потому что здесь до меня окончательно дошло: рабочий Telegram-бот нужно проектировать не как поток сообщений, а как экранную систему состояний, случайно запертую внутри мессенджера.
В итоге пришлось идти в single-screen-модель:
-
актуальный экран должен быть один;
-
устаревшие сообщения нужно убирать;
-
старые callback-кнопки должны безопасно переживать релизы;
-
меню, уведомления и рабочие карточки должны жить по разным правилам.
Это не тот кусок, которым хочется хвастаться первым. Но именно он делает систему пригодной к ежедневному использованию.
Реальная логика процесса сломала красивую симметрию
Когда пошли модерация, срочные заявки, переносы между периодами и возвраты на доработку, стало понятно, что схема у процесса уже не игрушечная.
Появились:
-
обычные weekly-заявки;
-
срочные заявки;
-
первый уровень модерации;
-
закупщик;
-
возврат на доработку;
-
окна времени;
-
автоматические переносы;
-
архив;
-
повтор заявки;
-
напоминания.
Сначала логика выглядела симметрично и аккуратно. Но реальный рабочий процесс довольно быстро показал, что красивая схема далеко не всегда полезная схема.
Например, если первый модератор уже полноценно обработал заявку, второй этап может быть уже не «контролем качества», а просто источником лишнего трения. И в какой‑то момент это пришлось честно признать и перестроить логику под реальный процесс, а не под аккуратную диаграмму.
Это, кстати, очень знакомый для техлида паттерн. Красивые схемы хочется любить. Но если схема мешает потоку, ломать надо схему.
Самые мерзкие баги были на стыках
Не в большой идее. Не в headline-фичах. А в пересечении слоёв.
Типичные примеры:
-
кнопка «Подтвердить» снаружи выглядит как зависшая, а внутри падает сравнение timezone‑aware и timezone‑naive дат;
-
черновик существует, но после изменения цены теряется контекст активной заявки;
-
заявка видна в weekly‑списке, но не в очереди модерации, потому что до нужного статуса она ещё не дошла;
-
старые inline‑кнопки из уже отправленных сообщений после релиза начинают приходить в старом формате callback«ов.»
Вот здесь я очень сильно почувствовал одну простую вещь: симптом почти никогда не живёт в том месте, где его видит пользователь.
Именно поэтому проект довольно быстро пришлось обкладывать регрессией:
-
тестами на черновики;
-
тестами на дедлайны;
-
тестами на модерацию;
-
тестами на переносы;
-
тестами на уведомления;
-
тестами на старые callback«и;»
-
тестами на weekly и urgent lifecycle.
В какой‑то момент ручного тестирования уже недостаточно. Если система встроена в реальный процесс, она должна уметь защищаться от повторного появления старых ошибок.
Почему для маленького бота понадобился взрослый деплой
Это ещё одна вещь, которая сначала кажется избыточной, а потом становится очевидной.
Когда бот уже реально встроен в работу команды, релиз «прямо поверх продакшена» становится плохой идеей.
Поэтому пришлось делать отдельный deploy slot:
-
новая версия собирается рядом;
-
миграции идут отдельно и аккуратно;
-
проверки прогоняются до переключения;
-
потом сервис переключается;
-
если что‑то пошло не так, можно быстро откатиться.
По сути это небольшой, но уже взрослый процесс:
-
backup;
-
next‑slot;
-
smoke‑check;
-
rollback path.
Для маленького проекта звучит, может, жирновато. На практике это просто нормальная инженерная гигиена.
Что бы я сделал иначе, если бы начинал заново
Вот тут, мне кажется, лежит самая полезная часть.
Если бы я начинал заново, я бы сразу сделал несколько вещей, до которых пришлось дойти через боль.
Первое. Сразу бы проектировал процесс как систему состояний, а не как последовательность «ну давайте добавим ещё одну кнопку».
Второе. Сразу бы закладывал single‑screen UX и правила очистки чата. Telegram очень быстро наказывает за наивность.
Третье. Сразу бы письменно фиксировал роли, дедлайны и fallback‑сценарии. Очень много ошибок рождается не в коде, а в том, что логика ещё не до конца сформулирована.
Четвёртое. Сразу бы готовил безопасный деплой, даже если проект маленький. Маленькие системы ломаются ничуть не хуже больших, просто тише.
Пятое. Сразу бы принимал тот факт, что agent‑driven разработка требует очень сильной постановки задачи. Если логика мутная, агент только быстрее реализует мутную логику.
Что получилось в итоге
На выходе получился не «бот, который принимает сообщения».
Получился закупочный контур:
-
с wizard‑подачей заявки;
-
с валидацией;
-
с OCR;
-
с черновиками;
-
с историей;
-
с модерацией;
-
со срочными заявками;
-
с weekly batches;
-
с HTML‑отчётами;
-
с архивом;
-
с напоминаниями;
-
с безопасным деплоем;
-
и с инфраструктурой, на которой всё это живёт.
Но, если честно, главный результат для меня не в этом.
Главный результат в том, что этот проект очень хорошо показал разницу между «уметь писать код» и «уметь собирать систему».
Кодинг руками — важный навык. Но архитектура, декомпозиция, работа с неопределённостью, умение вовремя упростить решение, удержать роли, статусы и границы, довести процесс до MVP и не дать системе рассыпаться — это отдельная инженерная компетенция.
Агентные системы не убирают эту компетенцию.
Наоборот, в каком‑то смысле они делают её ещё важнее.
И, пожалуй, это для меня главный вывод всей истории.
Я не «заменил разработчика нейросетью». Я получил новый интерфейс к реализации и проверил на практике, что системная инженерная роль может быть вполне эффективной и в software‑контуре, если не терять голову по дороге.
ссылка на оригинал статьи https://habr.com/ru/articles/1025150/