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

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

18

На практике синхронность и асинхронность — не бинарный переключатель, а спектр, и зрелые системы располагаются между полюсами, комбинируя режимы осознанно.

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

Часто человеку или внешнему клиенту удобнее синхронный интерфейс («задал задачу — получил ответ»), тогда как внутри рой работает асинхронно и параллельно. Тогда строят синхронный фасад: точка входа принимает запрос и держит соединение, а внутри разворачивает асинхронный веер, собирает результаты и отдаёт их как один синхронный ответ. Это законная и распространённая схема; её риск — фасад наследует худшее из синхронного мира (соединение держится всё время самой долгой внутренней операции) и потому требует тайм-аута на уровне фасада и стратегии частичного результата, если внутренний рой не уложился в срок (см. главу 73). Интерфейсная сторона этой схемы рассматривается в главе об интерфейсах оркестрации (см. главу 94).

Крайняя форма асинхронности — выпустить сообщение и не ждать вообще ничего (fire-and-forget). Она уместна для уведомлений, телеметрии, обновлений общей доски, где отправителю безразличен исход. Опасность — применить её там, где исход на самом деле важен: тогда отказ обработчика проходит незамеченным, потому что никто не ждал результата и некому заметить его отсутствие. Дисциплина проста: fire-and-forget допустим только для связей, где потеря сообщения — приемлемый исход. Если потеря неприемлема, нужен либо ответ (request-reply), либо подтверждение обработки, либо хотя бы наблюдаемость, фиксирующая факт обработки (см. главу 89 об аудите как защите).

Сквозная мысль главы: тайм-аут — это механизм, который превращает любое ожидание, синхронное или асинхронное, в обрабатываемый отказ. Синхронный вызов без тайм-аута зависает; асинхронное ожидание без тайм-аута зависает тише, но так же фатально. Тайм-аут переводит неопределённость («ответ ещё не пришёл — он придёт или нет?») в определённость («ответ не пришёл в срок — действуем по плану отказа»). В рое агентов, где получатели медленны и недетерминированны, а связи пересекают ненадёжные границы, отсутствие тайм-аута на любом ожидании — это не недосмотр, а заложенная в архитектуру возможность бесконечного зависания. Паттерны тайм-аутов, повторов и circuit breaker разбираются как отдельная дисциплина устойчивости (см. главу 70); здесь важно, что они применимы к обоим режимам и обязательны в обоих.

— Синхронность против асинхронности — это ось связанности во времени, ортогональная каналу коммуникации (см. главу 23): один и тот же транспорт несёт оба паттерна, и различает их не наличие очереди, а то, блокирует ли ожидание прогресс отправителя.

— У роя агентов цена синхронного ожидания на порядки выше, чем в обычных распределённых системах: единица работы долгая, отправитель удерживает дорогой контекст, а на веере латентности складываются — поэтому fan-out почти всегда обязан быть асинхронным по выпуску (см. главы 8 и 37).

— Синхронный режим даёт линейность рассуждения и очевидную причинность, но вводит связанность по доступности (каскадные блокировки) и сложение латентностей (последовательная доля по Амдалу, см. главу 56).

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

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

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

— Зрелые системы гибридны и располагаются на спектре: request-reply поверх асинхронного транспорта, синхронный фасад над асинхронным ядром, дисциплинированный fire-and-forget; тайм-аут — общий механизм, превращающий любое ожидание в обрабатываемый отказ.

Глава 25. Форматы сообщений и схемы

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

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

Глава рассматривает форматы и схемы не как вопрос выбора JSON против чего-то ещё, а как вопрос надёжности, стоимости и управляемости. Формат — это решение о том, где платить: за строгость на отправителе или за устойчивость на получателе. Схема — это решение о том, что считать обязательством, а что пожеланием. Валидация — это решение о том, где проходит граница, за которой система отказывается доверять.

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

Конверт принадлежит инфраструктуре роя — оркестратору, шине сообщений, слою маршрутизации. Его поля одинаковы для всех типов сообщений и не зависят от того, что делают агенты: идентификатор сообщения, идентификатор корреляции (см. главу 78), отправитель и адресат (см. главу 27), тип сообщения, временная метка, ключ идемпотентности (см. главу 43), счётчик повторов, причинная ссылка на сообщение-предшественник. Эти поля формируются и читаются кодом обёртки, а не моделью, и потому детерминированы: модель не участвует в заполнении конверта, и это правильно, потому что доставка и координация не должны зависеть от того, что модель решила сгенерировать.

Полезная нагрузка принадлежит роли. Её структура зависит от типа сообщения и от контракта производителя: задача для воркера, результат подзадачи, запрос ревью, отчёт об отказе, фрагмент знания для общей доски. Именно полезная нагрузка — та часть, которую производит недетерминированная модель и которую поэтому нужно извлекать и валидировать. Конверт можно проверить тривиально (поля известны, типы фиксированы); полезная нагрузка — место, где живёт вся трудность парсинга.

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