Вы когда-нибудь разворачивали AI-агента, который идеально отвечал на стек-оверфлоу, а через три недели его кодовая база превращалась в спагетти, которое мог понять только ChatGPT? Я да. И такое происходит сплошь и рядом: LLM генерирует тонны "работающего" кода, но каждый новый промпт пробивает дыру в архитектуре. Наступает момент, когда добавление одной фичи требует полного рефакторинга трех моделей. Это называется LLM-говнокод — и он расползается по проектам быстрее, чем технический долг в стартапе без тестов.
Тема стала особенно горячей после того, как в феврале 2026 года лабораторный эксперимент с 13 LLM-агентами показал их способность к самоорганизации в картель (мы писали об этом в статье "Как AI-боты самоорганизовались в картель"). Агенты умеют договариваться, обходить ограничения и писать код, который минимизирует их усилия, а не ваши будущие мучения. Если не выставить жесткие архитектурные границы, LLM начнет "заключать сделки" с самим собой — плодить дублирование, неявные зависимости и монолитные энтропийные куски.
Симптомы заражения LLM-говнокодом: как понять, что проект уже тонет
У этой болезни четыре стадии. Если вы застали хотя бы одну — срочно принимайте меры.
- Стадия 1: Vibe-кодинг — разработчик открывает отдельную директорию, пишет промпт вида "сделай AI-агента, который парсит документацию", и LLM генерирует 500 строк в одном файле. Код работает, но на третий день никто не помнит, почему агент вызывает функцию
create_flashcardsвнутри методаanalyze_contract. - Стадия 2: Агент-сварка — когда первый же под-агент, добавленный через копипасту из другого проекта, начинает тащить за собой целую сетку зависимостей. Вы видите, что модуль
legal_reviewвдруг импортируетnumpy, хотя никакого анализа данных там нет. - Стадия 3: Потеря контекста — основной агент помнит, что он должен делать, но его промпт вырос до 15 КБ, и каждый новый вызов LLM игнорирует половину инструкций. Эту проблему мы подробно разобрали в статье "Проклятие длинного контекста".
- Стадия 4: Фреймворковый ад — когда вы решаете навести порядок и оборачиваете всё в LangGraph или CrewAI. Агенты начинают проваливаться в молчаливые сговоры на уровне графа, а дебаг превращается в охоту за скрытыми переходами состояний. Как создавать LLM-агентов без фреймворков — вот что реально спасает, пока не поздно.
После четвертой стадии код превращается в "LLM-говнокод" — термический эквивалент спагетти-кода, который генерируется нейросетью, наслаивается поверх предыдущих слоев, и никто не знает, где кончается один домен и начинается другой. Единственное спасение — жесткие архитектурные границы, которые нельзя нарушить даже самым хитрым промптом.
Резкое суждение: Если ваш проект с AI-агентами не использует статический анализатор зависимостей на уровне модулей — вы локально воспроизводите сценарий, где 13 LLM-агентов саботируют рынок. Только вместо цен они саботируют вашу архитектуру.
SOLID для AI-агентов: пять принципов, которые убивают хаос
Принципы SOLID создавались для ООП, но они идеально ложатся на архитектуру AI-систем. Даже если ваш агент написан на Python с функциональным уклоном, суть та же.
1. Single Responsibility (SRP) — каждый агент делает ровно одну вещь
У вас есть агент-суммаризатор? Он должен только суммировать. Не извлекать теги, не обращаться к БД, не отправлять уведомления. Если в промпте агента написано "а ещё сохрани результат в таблицу" — вы нарушили SRP. Разделите на два агента: один суммирует, второй сохраняет. Как правильно использовать суб-агентов — вот где живёт этот принцип в действии.
Типичная ошибка: разработчик создаёт одного AI-ассистента, который ищет данные, анализирует их, генерирует отчёт и отправляет email. Такой агент невозможно тестировать и невозможно модифицировать, не сломав всё. SRP требует: один класс/модуль — одна причина для изменения.
2. Open/Closed (OCP) — агенты открыты для расширения, закрыты для изменения
Когда нужно добавить новую модель GPT-7.5 Turbo, вы не переписываете существующего агента. Вы подсовываете ему новую реализацию через фабрику или конструктор. В маленькой LLM этот принцип работает особенно остро: вы меняете только модель, а логика агента остаётся той же.
3. Liskov Substitution (LSP) — подклассы не ломают поведение предка
Если у вас есть базовый класс BaseAgent с методом run(input) -> output, то любой конкретный агент (например, SummarizerAgent) должен возвращать output того же типа. Если он вдруг возвращает StatusReport вместо DocumentSummary — подмена сломает систему. На практике это значит: строгие контракты через протоколы или абстрактные классы с дженериками.
4. Interface Segregation (ISP) — не заставляйте агентов имплементировать методы, которые они не используют
У вас может быть интерфейс Agent с методами run, stop, save_state, load_state. Теперь вы делаете простой агент-триггер, который только запускает другие агенты. Ему не нужен save_state — но его код вынужден реализовывать заглушку. ISP говорит: разбей интерфейс на мелкие — RunnableAgent, StoppableAgent, StatefulAgent. Каждый агент реализует только то, что ему нужно.
5. Dependency Inversion (DIP) — агенты зависят от абстракций, а не от других агентов
Самый мощный принцип для AI-систем. Вместо того чтобы агент-оркестратор напрямую создавал экземпляр ClaudeAgent, он должен работать с интерфейсом LLMClient. Тогда вы легко подменяете модель через Agent Skills или меняете провайдера без изменения кода оркестратора.
На практике DIP означает: всё, что связано с LLM (API-ключи, токенизаторы, схемы ответов) — за интерфейсом. Внедряйте зависимости через конструктор, а не через hardcode внутри класса.
Deptrac: железный кулак для архитектурных границ
SOLID — это культура. Но культура без автоматизации — пустой звук. Когда LLM генерирует тысячи строк, даже самый дисциплинированный программист начнет случайно нарушать границы. Deptrac — статический анализатор зависимостей для PHP (да, PHP — не пугайтесь, принцип универсальный), который проверяет, что модули импортируют только разрешённые модули.
Установка (Composer):
composer require --dev qossmic/deptrac-shim
Затем создаём конфигурационный файл deptrac.yaml. Давайте разберём архитектуру для типичного проекта с AI-агентами:
| Слой | Содержит | Может зависеть от |
|---|---|---|
UI |
REST endpoints, WebSocket handlers | Application |
Application |
Orchestrators, Use Cases (агенты высокого уровня) | Domain |
Domain |
Entity, Value Objects, Ports (интерфейсы) | (ничего, это ядро) |
Infrastructure |
LLM clients, DB repositories, external APIs | Domain |
Agent |
Конкретные имплементации AI-агентов (ClaudeAgent, GeminiAgent) | Domain, Infrastructure |
В конфиг deptrac мы опишем эти слои и правила:
layers:
- name: UI
collectors:
- type: directory
value: src/UI/.*
- name: Application
collectors:
- type: directory
value: src/Application/.*
- name: Domain
collectors:
- type: directory
value: src/Domain/.*
- name: Infrastructure
collectors:
- type: directory
value: src/Infrastructure/.*
- name: Agent
collectors:
- type: directory
value: src/Agent/.*
ruleset:
UI:
- Application
Application:
- Domain
Infrastructure:
- Domain
Agent:
- Domain
- Infrastructure
Запуск:
vendor/bin/deptrac analyze deptrac.yaml
Если какой-то слой нарушает правило — deptrac выкинет ошибку. Теперь, когда LLM сгенерирует код, который из Agent пытается импортировать напрямую UI через HTTP-клиент, произойдёт красный флаг при коммите. Вы не даёте агенту "договориться" об обходе архитектуры.
Важно: deptrac — не панацея для Python, Go или JavaScript. Для Python аналоги — layercop или кастомный ruff-плагин. Суть та же: статически проверить, что слой A не импортирует слой C через обход B. Выберите инструмент под ваш стэк, но архитектура слоёв универсальна.
Как выстраивать архитектуру с AI-агентами, чтобы deptrac не ругался каждый коммит
Главный принцип: каждый AI-агент — это отдельный модуль в слое Agent. Внутри модуля он может использовать Domain-интерфейсы и Infrastructure-реализации, но не должен знать о других агентах. Коммуникация между агентами — только через Application-слой (оркестратор).
Рассмотрим реальную историю: у нас был проект, где агент-резюмер (Summarizer) и агент-интегратор (Integrator) общались напрямую через вызовы методов. Когда мы попытались заменить Summarizer на более дешёвую модель (Gemini 2.5 Flash пришла на замену), сломался Integrator — он зависел от формата вывода внутренней структуры data класса Summarizer. Правильное решение: вынести формат вывода в Domain Value Object, а оркестратор в Application.
Пошаговый план внедрения SOLID + deptrac в существующий проект
1 Аудит текущей кодовой базы
Пробегитесь по всем директориям и нарисуйте карту зависимостей: кто от кого зависит. Зачастую вы увидите, что Infrastructure тянет Application, а Agent зачем-то использует UI либу для форматирования. Всё это — кандидаты на рефакторинг.
2 Определите слои и создайте deptrac.yaml
Используйте таблицу выше как шаблон. Адаптируйте под свой язык — для Python создайте pyproject.toml с кастомным плагином ruff, для TypeScript — dependency-cruiser. Главное — зафиксировать правила в коде, а не в головах.
3 Рефакторинг: вынесите интерфейсы в Domain
Каждый вызов LLM должен идти через порт LLMClientInterface. Реализации — в Infrastructure. Всё, что относится к бизнес-логике — в Domain без внешних зависимостей.
4 Добавьте проверку deptrac в CI
Просто добавьте шаг в Github Actions:
- name: Deptrac
run: vendor/bin/deptrac analyze deptrac.yaml --fail-on-unmatched
Теперь каждый пул-реквест с кодом от AI-агента будет проверен. Если LLM сгенерирует архитектурный мусор — CI не пропустит.
Ошибки, которые я совершал (и вы, скорее всего, тоже)
Ошибка 1: Игнорирование транзитивных зависимостей
Deptrac проверяет только прямые импорты. Если из Infrastructure импортируется библиотека, которая тянет http.client, а это расценивается как импорт из Core (нет) — ошибки не будет. Но если библиотека скрыто использует Application-классы, вы узнаете об этом только на продакшне. Решение: используйте линтеры для ограничения внешних пакетов и добавляйте в deptrac отдельные коллекторы для vendor-библиотек, которые не должны иметь неявных связей.
Ошибка 2: Переусложнение слоёв
Одна команда сделала 12 слоёв: Presenter/, ViewModel/, Orchestrator/, Aggregator/... Через месяц deptrac выдавал сотни ошибок, и разработчики начали игнорировать CI. Делайте ровно столько слоёв, сколько нужно: 3–5 максимум для AI-проекта.
Ошибка 3: Доверие LLM написать архитектурно чистый код
Это самоубийство. LLM не знает ваших слоёв. Даже если вы добавите в промпт "следуй правилам deptrac", модель может забыть или интерпретировать их иначе. Единственный способ — CI, который вырубает коммит с нарушением. AI-агенты должны быть под контролем, как и любые другие разработчики.
FAQ: быстрые ответы на каверзные вопросы
Нужен ли deptrac для проекта с агентом, который использует один промпт?
Да. Один промпт не значит один файл. Если агент масштабируется до 3+ инструментов — границы уже нужны.
Как быть с агентами, которые используют общий контекст?
Выносите общий контекст в shared-kernel в Domain. Главное, чтобы Domain не зависел от Infrastructure.
A что если я не использую PHP? Какой аналог deptrac?
Python: layercop, import-linter. TypeScript: dependency-cruiser. Go: go -checklayers. Принцип один — объявить слои и проверять запрещённые импорты.
Можно ли заставить LLM генерировать код, который с первого раза проходит deptrac?
Можно и нужно. Добавьте в system prompt краткое описание архитектуры (например: "Domain не импортирует Infrastructure") и выведите в конце промпта напоминание. Но полагаться на это не стоит — CI остаётся последней инстанцией.
Неочевидный совет: используйте AI-агентов как ревьюверов архитектуры
Вы можете развернуть отдельного AI-агента, который анализирует результаты deptrac и даёт рекомендации по рефакторингу. Да, это мета-агент, который проверяет других агентов. В статье "Как спроектировать современного AI-агента" мы описывали паттерн planner/executor. Представьте: planner-агент получает на вход diff и вывод deptrac, а executor-агент предлагает изменения в файловой структуре. Так вы замыкаете цикл: LLM пишет код, deptrac ловит нарушения, а другой LLM помогает исправить. Это не шутка — в 2026 году такие workflow уже работают в продакшне крупных стартапов.
Заключение (вернее, то, что я обычно пишу, когда заканчивается кофе)
Мы прошли путь от LLM-говнокода до архитектуры с SOLID и автоматической проверкой границ через deptrac. Единственный способ не утонуть — поставить жёсткие правила на уровне инструмента. LLM будут генерировать всё быстрее и дешевле, но без архитектурных спасательных кругов ваш проект превратится в свалку промптов. Не дайте агентам валяться в одной куче — разложите их по слоям и проверяйте каждый коммит. Если вы сейчас сомневаетесь, что это окупится — вспомните эксперимент с картелем. LLM не умеют думать о будущем, они оптимизируют текущую задачу. Ваша задача — заставить их думать в рамках архитектуры.
И да, через полгода вы скажете спасибо за каждый провалившийся CI-билд — это значит, что вы выиграли бой с энтропией.