К нам обратились с мобильным приложением, которое уже работало в продакшене, имело пользователей и платных клиентов. Значительная часть кода была создана с помощью AI, а логика AI-чата находилась в одном промпте объёмом около 500 строк.
Продукт выполнял свои задачи, но развивать его становилось всё сложнее. В кодовой базе оставались неиспользуемые фрагменты, похожая логика встречалась в нескольких местах, а новая инструкция в промпте могла неожиданно повлиять на уже работающий сценарий.
Особенно хорошо это стало видно на одной небольшой задаче. Команде нужно было изменить одно правило в приложении, однако перед самой правкой разработчику пришлось разобраться, какая из похожих реализаций актуальна, почему рядом лежит ещё одна версия той же логики и что произойдёт после удаления старых методов.
Само изменение заняло меньше времени, чем подготовка к нему.
Во время дальнейшей ревизии команда обнаружила две связанные проблемы: последствия последовательных AI-итераций внутри кодовой базы и монолитную структуру AI-логики, которая мешала предсказуемо менять поведение чата.
- когда локальные AI-правки начинают мешать друг другу;
- промпт, в котором всё зависело от всего;
- как разделили AI-логику;
- что изменилось после переработки;
- что сделали с самим приложением.
Когда локальные AI-правки начинают мешать друг другу
Vibe coding хорошо работает внутри конкретного запроса. Модель получает задачу, меняет нужный участок и выдаёт результат.
При следующей итерации контекст уже может быть другим. AI создаёт новую реализацию, но не всегда замечает, что прежняя версия больше не нужна.
Об обезличивании примеров.
Названия сущностей и пользовательские сценарии изменены, чтобы не раскрывать внутреннюю логику продукта. Сами типы проблем соответствуют тому, с чем столкнулась команда.
Во время одной из итераций модель сгенерировала новый способ работы с данными. Приложение начало использовать его, а связанные со старой версией модели и вспомогательные методы остались в проекте. Там же продолжала лежать неактуальная логика кеширования.
Ничего не падало. Пользователь вообще не мог заметить проблему.
Зато при следующей доработке старый и новый код выглядели как две возможные реализации одного процесса. Они появлялись в поиске, ссылались на соседние компоненты и мешали быстро понять, какую часть системы можно менять.
В другом месте похожая ситуация возникла с валидацией. Вместо использования существующей функции AI несколько раз решил задачу заново для разных экранов. Правило оказалось продублировано, причём версии немного отличались друг от друга.
Одна правка теперь означала работу сразу с несколькими участками. Сначала нужно было найти все варианты логики и понять, какие из них действительно вызываются. Затем проверить, как изменение влияет на состояние интерфейса.
Так технический долг накапливался незаметно. Каждая отдельная AI-итерация решала свою задачу, однако общая структура приложения постепенно становилась менее очевидной.
Промпт, в котором всё зависело от всего
Похожая проблема возникла внутри AI-чата.
Около 500 строк промпта содержали правила обработки запросов, ограничения, требования к формату и инструкции для отдельных сценариев. Пока логики было немного, такой подход позволял быстро добавлять новое поведение.
Со временем промпт стал хрупким.
Вернуть результат в строго заданной JSON-структуре.
Вести естественный диалог и подробно объяснять действия пользователю.
Результат:
модель могла добавить обычный текст за пределами JSON или вернуть сухую структуру там, где ожидался полноценный ответ.
Обе инструкции по отдельности выглядели корректно. Вместе они давали нестабильный результат.
Исправление одной формулировки не гарантировало, что проблема исчезнет. На ответ влияло сочетание правил, расположенных в разных частях промпта. Новая инструкция могла улучшить один сценарий и незаметно изменить другой.
Поэтому очередная доработка чата превращалась в поиск связи между десятками условий, которые формально находились в одном документе, но отвечали за разное поведение.
Как разделили AI-логику
Команда отказалась от идеи, что один промпт должен одновременно понимать запрос, выбирать сценарий, находить нужные данные и формировать итоговый ответ.
Эти задачи распределили между несколькими агентами.
Router
Определяет тип запроса и выбирает маршрут дальнейшей обработки.
Retriever
Получает контекст и правила, которые нужны для выбранного сценария.
Generator
Формирует ответ на основе данных, подготовленных на предыдущих этапах.
Теперь отдельный компонент не должен учитывать всю логику продукта. Он получает ограниченный контекст и отвечает за конкретный этап.
Но простое разделение промпта не решает задачу само по себе. Появляются связи между агентами, и эти связи тоже нужно контролировать.
Поэтому результаты передаются между компонентами в структурированном виде.
Перед следующим этапом система проверяет, присутствуют ли обязательные поля и соответствует ли результат ожидаемому формату.
Если один агент вернул некорректную структуру, ошибка останавливается на этом этапе и не распространяется дальше по цепочке.
Что изменилось после переработки
Такая архитектура упростила тестирование. QA-команда могла отдельно проверять классификацию запроса, получение контекста и формирование ответа. Не приходилось после каждой небольшой правки заново разбирать весь промпт на 500 строк.
Чат мог нарушить заданный формат, пропустить ограничение или изменить поведение в одном сценарии после правки другого.
Такие отклонения стали встречаться реже, а поведение чата в повторных тест-кейсах стало стабильнее.
Теперь QA-команда может изменять и проверять отдельную часть AI-логики в рамках конкретной задачи, не анализируя весь промпт целиком.
Что сделали с самим приложением
Полный rewrite не потребовался.
Команда сначала восстановила фактическую структуру проекта: нашла используемые реализации, отделила их от устаревшего кода и убрала дублирование там, где оно мешало следующим изменениям.
Рабочие части приложения сохранили. Проблемные участки перерабатывали постепенно, не останавливая развитие продукта ради переписывания всей системы.
Приложение осталось в продакшене, а команда получила кодовую базу и AI-логику, с которыми можно продолжать работу. Новые задачи больше не начинались с попытки определить, какой из похожих фрагментов актуален и какое правило внутри огромного промпта повлияет на ответ.