18+
реклама
18+
Бургер менюБургер меню

Ранас Мукминов – Оркестрация ИИ-агентов. Claude Opus 4.7 (страница 86)

18

В-третьих, веер. Оркестратор обычно общается не с одним воркером, а с несколькими (см. главу 8, fan-out/fan-in). Если он ждёт каждого синхронно и последовательно, латентности складываются; вся выгода параллелизма теряется ещё до того, как воркеры начнут работать. Именно здесь синхронный паттерн, безобидный в сервисной архитектуре, превращается в архитектурный дефект роя.

Эти три фактора означают, что для роя агентов вопрос «синхронно или асинхронно» стоит острее и решается раньше, чем для обычной распределённой системы. То, что в микросервисах считается приемлемым умолчанием (синхронный REST-вызов между сервисами), в рое агентов на горячем пути часто недопустимо.

Синхронный режим привлекателен простотой рассуждения: запросил — получил — продолжил, поток управления линеен и совпадает с потоком данных. Эта простота реальна и ценна, особенно на этапе проектирования и отладки. Но за неё платят связанностью и латентностью.

Линейность потока управления — главная выгода. Когда оркестратор синхронно вызывает роль-исполнителя и ждёт результат, состояние «что уже сделано, что в работе, что осталось» полностью отражено в позиции исполнения оркестратора. Не нужно отдельно хранить, что мы кого-то ждём: сам факт ожидания — это место в коде. Обработка ошибок локальна: ответ либо пришёл, либо нет, и решение принимается тут же. Причинность очевидна: ответ — следствие именно этого запроса, корреляция тривиальна (см. главу 78 о причинности в распределённой трассировке). Для коротких цепочек с жёсткой зависимостью «следующий шаг невозможен без результата предыдущего» синхронный паттерн не просто допустим — он естественен и его не следует усложнять асинхронностью без нужды.

Синхронный паттерн вводит связанность по доступности: отправитель не может продвинуться, пока получатель недоступен или медлит. Эта связанность транзитивна и потому опасна. Если оркестратор синхронно ждёт воркера A, а воркер A синхронно ждёт инструмент или субагента B, то недоступность B блокирует A, а блокировка A блокирует оркестратор. Цепочка синхронных вызовов так же надёжна, как её слабейшее звено, и так же быстра, как сумма всех звеньев. В терминах надёжности это означает, что вероятности успеха перемножаются, а латентности складываются — обе зависимости работают против системы по мере роста глубины.

Латентность складывается линейно по глубине синхронной цепочки. Это прямое следствие, которое в части о масштабировании рассматривается как часть последовательной доли по закону Амдала (см. главу 56): синхронная цепочка — это сериализованный участок, не поддающийся распараллеливанию, и именно он ограничивает потолок ускорения роя.

Самый частый и самый дорогой дефект — выполнить веер синхронно и последовательно. Оркестратор, которому нужны результаты пяти воркеров, вызывает первого, ждёт, вызывает второго, ждёт, и так далее. Суммарная латентность — сумма пяти, хотя задачи независимы и могли исполняться параллельно.

# Антипаттерн: латентность = сумма

for подзадача in подзадачи:

результат = синхронно_вызвать_воркера(подзадача) # блокировка на каждом

результаты.append(результат)

Правильная форма fan-out требует выпустить все запросы, не дожидаясь ответов, и собрать результаты по мере готовности; латентность тогда определяется самым медленным воркером, а не суммой.

# Корректно: латентность = максимум

дескрипторы = [запустить_воркера(п) for п in подзадачи] # без блокировки

результаты = собрать_по_готовности(дескрипторы) # fan-in

Граница между двумя формами — это и есть граница между синхронным и асинхронным паттерном на уровне веера. Транспорт может быть один и тот же; разница в том, блокирует ли отправитель себя на каждом запросе или развязывает отправку и сбор. Подробно стратегии сбора рассматриваются в главе о fan-in и агрегации (см. главу 37); здесь важно зафиксировать: синхронный последовательный веер — это почти всегда ошибка, а не выбор.

Между чистой блокирующей синхронностью и полной развязкой есть третья точка, специфичная для агентов, — потоковая (streaming) выдача. Получатель не отдаёт результат одним пакетом в конце, а выпускает его по частям по мере порождения: токенами, фрагментами рассуждения, промежуточными артефактами. Формально отправитель всё ещё ждёт завершения, то есть связь синхронна по итогу; но он начинает получать содержимое раньше полного ответа и может реагировать на частичное.

Для роя агентов стриминг важен по двум причинам. Первая — он снижает воспринимаемую латентность ожидания, не меняя полной: оркестратор или человек видят прогресс и могут начать обработку первых фрагментов до конца генерации. Вторая, более существенная, — он даёт точку раннего вмешательства. Агент-надзиратель, читающий поток рассуждения исполнителя, способен прервать его, не дожидаясь финала, если поток уходит в неверное русло, нарушает границу или зацикливается (см. главу 74 о зацикливании). Это превращает синхронное ожидание из «чёрного ящика на минуты» в наблюдаемый процесс с возможностью досрочной остановки.

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

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

Развязка по доступности — главная выгода. Отправитель и получатель не обязаны присутствовать одновременно: сообщение переживает временную недоступность получателя в буфере или очереди (см. главу 23 о свойствах канала сообщений). Медленный получатель не замораживает отправителя — он лишь накапливает отставание, которое можно наблюдать и которым можно управлять. Параллелизм становится естественным: выпустив N сообщений, отправитель не ждёт ни одного и сразу свободен; обработчики работают одновременно. Буфер между отправителем и получателем сглаживает всплески нагрузки: отправитель может производить сообщения быстрее, чем получатель их обрабатывает, и разница оседает в очереди, а не приводит к отказу. Это та самая развязка, которая делает возможными топологии blackboard (см. главу 11) и событийные сети (см. главу 28), где у сообщения нет фиксированного адресата во времени.

Развязка во времени имеет цену, и эта цена ложится ровно в те зоны, где недетерминированный исполнитель наиболее хрупок.

Состояние ожидания становится явным. В синхронном режиме «я жду ответ» — это просто позиция исполнения. В асинхронном это состояние нужно где-то хранить: отправитель выпустил запрос и пошёл дальше, а когда придёт ответ, нужно вспомнить, к чему он относится, восстановить контекст задачи, понять, все ли ожидаемые ответы получены. Это состояние корреляции (см. главу 25 о форматах сообщений и корреляционных идентификаторах) живёт вне потока управления и становится отдельным носителем состояния роя — со всеми рисками распределённого состояния (см. часть VII).

Причинность размывается. Ответ приходит отдельным сообщением, возможно, спустя время и после множества других событий. Связать ответ с породившим его запросом, восстановить причинную цепочку «событие → реакция → следствие» становится отдельной задачей, требующей корреляционных идентификаторов и трассировки (см. главы 77 и 78). В синхронном режиме причинность видна по стеку; в асинхронном её приходится реконструировать.

Порядок не гарантирован. Асинхронные сообщения могут прийти не в том порядке, в каком отправлены; обработка может пересечься во времени. Это поднимает вопросы семантики доставки и порядка, которым посвящена отдельная глава (см. главу 29). Синхронный режим эти вопросы снимает по построению: один запрос, один ответ, порядок тривиален.

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