August 20

Мультиагентная система — это распределённая система. Пора относиться к ней соответственно

Один AI-агент обычно выглядит обманчиво просто. У него есть промпт, набор инструментов, доступ к данным и понятная задача. Он получает запрос, что-то делает и возвращает результат. Если результат плохой, мы правим промпт, меняем модель или добавляем контекст.

Проблемы начинаются, когда вместо одного агента появляется несколько. Один собирает информацию, второй её проверяет, третий анализирует, четвёртый принимает решение, а пятый оформляет результат. На диаграмме всё выглядит великолепно: аккуратные прямоугольники соединены стрелочками, каждый агент занят своим делом, а где-то сверху написано *AI-powered workflow*.

Но пять агентов — это не просто пять промптов в плаще. Это уже распределённая система.

И ломаться она будет соответствующе: из-за гонок данных, устаревшего состояния, повторных запросов, частичных сбоев, потерянных событий и невозможности понять, какой именно компонент превратил нормальный результат в уверенную чушь.

Модель может работать идеально, а система — всё равно ошибаться

В выступлении о production-grade мультиагентных системах приводится пример автоматизированной кредитной проверки. Один агент рассчитывает кредитный рейтинг, другой проверяет доход, третий оценивает риск, четвёртый ищет признаки мошенничества, а последний принимает решение.

По отдельности каждый компонент мог работать нормально. Проблема возникла между ними: один агент записывал новое значение в PostgreSQL, пока другой продолжал читать старое из кеша. В результате система принимала неправильные решения, хотя ни модель, ни промпт формально не были сломаны.

Это довольно важный сдвиг в мышлении. Когда AI-система ошибается, мы по привычке начинаем изучать ответы модели: достаточно ли хороший был промпт, не галлюцинировала ли она, хватило ли контекста. Но в мультиагентном воркфлоу ошибка может вообще не иметь отношения к интеллекту агентов. Агент получил корректные данные и принял корректное решение — просто данные были вчерашними.

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

Оркестр или танцпол

Есть два базовых способа организовать взаимодействие агентов.

При оркестрации существует центральный координатор. Он вызывает исследовательского агента, получает результат, параллельно запускает проверку фактов и анализ, ждёт их завершения, объединяет ответы и передаёт их следующему агенту. Сами агенты ничего не знают друг о друге. Они получают входные данные, выполняют свою работу и возвращают результат.

Такой подход не выглядит особенно революционно, зато позволяет открыть один trace и увидеть, что именно происходило: кто был вызван, с какими данными, сколько времени отвечал и где всё упало. Оркестратор может управлять таймаутами, безопасными повторами, параллельным выполнением и остановкой процесса, если результат одного из этапов не прошёл проверку.

В хореографии центрального координатора нет. Агенты реагируют на события. Исследователь публикует research.completed, аналитик подхватывает событие и после своей работы публикует analysis.ready, а генератор отчёта слушает уже его.

Это даёт компонентам больше автономности и позволяет добавлять новых участников, не переписывая весь центральный процесс. Но вместе с автономностью появляется распределённый детектив. Было ли событие опубликовано? Получил ли его нужный агент? Не обработал ли он его дважды? Что произойдёт, если события пришли в неправильном порядке?

Хореография хорошо подходит системам, которые действительно живут вокруг событий и не требуют строгого линейного контроля. Для сложных процессов, где важны порядок, аудит и предсказуемое поведение при ошибках, оркестрация обычно оказывается разумнее. Между ними нет религиозного выбора: на практике система может централизованно управлять критическим процессом, но публиковать события для независимых вторичных действий.

Проблемы начинаются не тогда, когда команда выбирает «неправильный» паттерн, а когда вообще не делает выбор. Агенты просто начинают вызывать друг друга, писать в общую базу и надеяться, что как-нибудь разберутся. Охуенная идея, только вот работать будет ровно до первого параллельного запроса.

Общее состояние — удобный способ устроить хаос

Представим, что два агента одновременно читают одно значение. Первый обновляет его с 680 до 750, второй — с 680 до 720. Если оба просто записывают результат обратно, последнее обновление побеждает, а первое бесследно исчезает.

Транзакции, блокировки и уровни изоляции существуют не первый день и могут решить часть этой проблемы. Но в агентских системах состояние часто размазано между базой, кешем, очередью сообщений, векторным хранилищем и контекстом самой модели. Транзакция PostgreSQL уже не очень помогает, если один компонент прочитал старые данные из Redis, а другой получил событие повторно спустя пять минут.

Более устойчивый подход — не позволять агентам бесконечно переписывать один общий объект, а сохранять новые версии состояния. Агент получает конкретный snapshot, выполняет работу и создаёт следующий. Предыдущие версии остаются неизменными.

Это не магически отменяет конкуренцию: системе всё равно нужно решать, какая ветка состояния считается актуальной и можно ли объединять параллельные результаты. Зато появляется происхождение данных. Если финальное решение оказалось плохим, можно открыть предыдущую версию и увидеть не только текущее значение, но и какой агент, на основе каких входных данных и когда его создал.

Версионированное состояние превращает расследование из гадания по логам в воспроизводимый процесс. А ещё позволяет повторно запустить воркфлоу с нужного шага, а не просить всю компанию повторить свои действия ради одного упавшего агента.

Агенты должны общаться через контракты

Одна из самых привлекательных особенностей LLM — способность понимать неструктурированный текст. Из этого легко сделать странный вывод, что между агентами можно передавать произвольный JSON или простыню Markdown, а следующая модель как-нибудь догадается, что имелось в виду.

Она действительно часто догадывается. В этом и ловушка.

Если аналитик ожидает выводы, источники, дату сбора информации и оценку уверенности, эти поля должны быть частью явного контракта. Результат необходимо проверить до передачи следующему этапу. Отсутствующий источник, неправильный формат даты или слишком низкая уверенность должны остановить процесс на границе, а не обнаружиться после того, как ещё три агента построили на плохих данных красивый отчёт.

Контракт не обязан превращать каждую передачу в бюрократический кошмар. Его задача проще: отделить допустимое состояние системы от ситуации, когда агент вернул что-то похожее на ответ.

Это особенно важно потому, что LLM умеют создавать правдоподобные ошибки. Обычная программа чаще падает на неожиданном формате. Модель может принять его, неверно интерпретировать и продолжить работу так уверенно, будто всё идёт по плану.

Сбой должен быть частью дизайна

Любой удалённый вызов иногда не отвечает. Модель достигает лимита, инструмент падает, сеть тормозит, провайдер возвращает ошибку, агент зависает в цикле или выдаёт результат, который не проходит валидацию.

Поэтому таймауты, ограниченные повторы и circuit breaker — не дополнительные production-фичи, которые можно прикрутить потом. Это часть нормальной семантики вызова.

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

Circuit breaker нужен, чтобы один сломанный компонент не утянул за собой весь воркфлоу. После серии ошибок система временно перестаёт обращаться к нему и либо переходит в ограниченный режим, либо использует последний допустимый результат, либо передаёт решение человеку. Что именно делать, определяется не инфраструктурной библиотекой, а смыслом процесса. Пропустить генерацию красивого саммари можно. Тихо пропустить проверку мошенничества — уже не очень.

Сложнее всего обстоят дела с действиями, которые успели изменить внешний мир. В распределённой системе обычно невозможно завернуть пять сервисов и три агента в одну большую транзакцию. Поэтому применяется Saga: после сбоя выполняются компенсирующие действия для уже завершённых шагов.

Компенсация не всегда означает настоящий откат. Отправленное письмо нельзя стереть из чужого почтового ящика, а опубликованный документ мог уже кто-то скачать. Иногда компенсация — это отменяющее сообщение, новая версия записи или передача ситуации человеку. Важен сам факт, что система заранее знает, как жить с частично выполненным процессом, а не впервые задумывается об этом после инцидента.

Observability важнее ещё одного агента

В демо достаточно увидеть финальный ответ. В production нужно понимать, как он появился.

Для каждого запуска должна сохраняться цепочка вызовов: версии промптов и моделей, входы и выходы агентов, использованные инструменты, изменения состояния, ошибки, задержки и стоимость. Чувствительные данные при этом придётся маскировать или хранить отдельно — observability не должна превращаться в крупнейшую утечку всей системы.

Особенно важен единый идентификатор запуска, проходящий через все компоненты. Без него логи пяти агентов остаются пятью независимыми историями, которые кто-то потом будет вручную склеивать по времени. И обязательно выяснится, что часы на одном сервисе спешили.

Хорошая трассировка нужна не только для поиска багов. Она позволяет сравнивать версии моделей, воспроизводить неудачные запуски, видеть, какой агент расходует большую часть токенов, и понимать, действительно ли добавление нового компонента улучшило результат. Без этого мультиагентность легко превращается в дорогой способ сделать систему сложнее, не имея возможности доказать, что она стала лучше.

Хорошая мультиагентная система выглядит скучно

Самая полезная мысль здесь довольно неприятная: основная работа при создании надёжной мультиагентной системы происходит не в промптах.

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

Поэтому production-grade система часто выглядит значительно скучнее демо. В ней есть центральный контроль там, где он нужен, версионированное состояние, явные контракты, идемпотентность, ограничения на повторы, компенсирующие действия, трассировка и человеческое подтверждение перед необратимыми операциями.

Именно это отличает систему от набора агентов, которые бодро разговаривают друг с другом и иногда случайно делают правильную вещь.