Всем привет! ИИ-агенты и их производные стремительно ворвались в повседневную практику разработки, вызывая восторг у менеджеров и головную боль у архитекторов. Если с одиночным агентом обычно не возникает проблем, то когда их становится много, появляется вопрос: как ими управлять? В этой статье рассмотрим основные подходы к созданию управляющего слоя в такой системе. На примере выясним, почему не все очевидные подходы работают и какие классические архитектурные паттерны актуальны в новой реальности. И, конечно же, я расскажу, как это работает в Домклик. Обещаю, будет интересно!

Проблемы оркестрации

Давайте разберёмся, в чём заключается сложность взаимодействия агентов. Казалось бы, всё работает и хорошо, дай им базу, навешай API сверху — и этот кубик можно встраивать в бизнес-процессы. Один агент (или просто LLM) легко встраивается в любую архитектуру, но когда бизнес требует множества агентов, особенно с разными ролями, возникает интересный вопрос: как обеспечить совместимость агентов и повторяемость результатов? Основные препятствия включают разроз��енную автоматизацию, недоверие к ИИ и негибкость систем. Добавьте к этому сложность реагирования на изменения: по статистике, более 72% компаний с ИИ-агентами не успевают их актуализировать.

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

Концептуальная схема пайплайна
Концептуальная схема пайплайна

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

Динамическая оркестрация через LLM

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

Динамическая оркестрация с помощью LLM
Динамическая оркестрация с помощью LLM

Детерминированная логика

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

Гибридный подход

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

Гибридная оркестрация, когда в качестве дополнительного маршрутизатора используется Camunda
Гибридная оркестрация, когда в качестве дополнитель��ого маршрутизатора используется Camunda

Изоляция агентов и возможность контроля

Переход от схемы к реализации ставит перед нами интересную задачу: как изолировать агентов? На первый взгляд, этот сервис должен быть единым: один контекст, отсутствие сетевых издержек, простота добавления хранилищ или кешей результатов. Однако это сопряжено с большими рисками: чем больше мы встраиваем в один сервис, тем меньше контроля за происходящим внутри. И если API пропустит новомодную атаку prompt injection, то на выходе можем получить ощутимое воздействие на другие системы, вплоть до повреждения данных. Поэтому каждый агент запускается в собственной, полностью изолированной «песочнице», например, на уровне контейнера. Такой подход также обеспечивает гибкое управление ресурсами, сбор метрик и логов.

Пример Docker для нескольких агентов с реестром функций
services:
  # Агенты
  orchestrator-agent:
    build:
      context: .
      dockerfile: agents/orchestrator_agent/Dockerfile
    environment:
      AGENT_ID: orchestrator_agent
      TASK_QUEUE_URL: ${MQ_ORCHESTRATOR_TASK_QUEUE_URL}
      RESULT_QUEUE_URL: ${MQ_RESULT_QUEUE_URL}
      ORCHESTRATOR_VISION_TASK_QUEUE_URL: ${MQ_VISION_TASK_QUEUE_URL}

    depends_on:
      - function-registry
    networks:
      - agents-net

  vision-agent:
    build:
      context: .
      dockerfile: agents/vision_agent/Dockerfile
    environment:
      AGENT_ID: vision_agent
      TASK_QUEUE_URL: ${MQ_VISION_TASK_QUEUE_URL}
      RESULT_QUEUE_URL: ${MQ_RESULT_QUEUE_URL}
      
    depends_on:
      - function-registry
    networks:
      - agents-net

  video-agent:
    build:
      context: .
      dockerfile: agents/vision_agent/Dockerfile
    environment:
      AGENT_ID: vision_agent
      TASK_QUEUE_URL: ${MQ_VISION_TASK_QUEUE_URL}
      RESULT_QUEUE_URL: ${MQ_RESULT_QUEUE_URL}
      
    depends_on:
      - function-registry
    networks:
      - agents-net

  # Реестр функций
  function-registry:
    build: ./function_registry
    ports:
      - "8001:8000"
    networks:
      - agents-net

  # Роутер тасок
  task-router:
    build: ./task_router
    ports:
      - "8002:8000"
    environment:
      TASK_QUEUE_URL: ${MQ_ORCHESTRATOR_TASK_QUEUE_URL}
      RESULT_QUEUE_URL: ${MQ_RESULT_QUEUE_URL}
    restart: unless-stopped
    networks:
      - agents-net

networks:
  agents-net:
    driver: bridge

Протоколы взаимодействия между агентами

Исходя из нашей схемы, сразу просится event-based паттерн. Однако современные тенденции также диктуют применение различных протоколов: A2A, ACP, MCP и т.п. В случае с MCP ситуация относительно проста, но при работе с агентами возникают сложности. Каждый агент должен выполнять свою роль и иметь чётко определённые каналы взаимодействия с другими агентами. Как мы изначально верно предположили, проще создать собственный протокол на основе любой шины. Это позволит полностью настроить взаимодействие и маршрутизацию, эффективно управлять полезной нагрузкой и реализовать сквозной мониторинг. Этот подход особенно актуален для существующих микросервисных архитектур, где уже используются собственные протоколы. В таком случае агенты могут обрести некую степень самостоятельности, интегрируясь с другими сервисами вне основного маршрутизатора.

Пример сообщения для общения между агентами
{
  "message_id": "msg_12345",
  "correlation_id": "corr_67890",
  "timestamp": "2024-07-15T12:00:00Z",
  "protocol_version": "1.0",
  "message_type": "task",
  "sender": "http_gateway",
  "recipients": ["broadcast", "vision_agent", "socials_agent", "text_recognize_agent", "uploader_agent"],
  "content": {
    "task_id": "task_abc123",
    "task_type": "multimodal_analysis",
    "priority": 5,
    "deadline": "2024-07-15T12:30:00Z",
    "payload": {
      "text": "Проанализируй изображение и опиши что на нем происходит",
      "image_url": "https://storage.example.com/image.jpg",
      "audio_url": "https://storage.example.com/audio.mp3" 
    },
    "requirements": {
      "response_format": "json",
      "quality_level": "high",
      "context_ttl": 3600
    },
    "context": {
      "session_id": "sess_xyz",
      "user_id": "user_123",
      "previous_tasks": ["task_prev1", "task_prev2"]
    }
  },
  "metadata": {
    "idempotency_key": "idemp_key_123",
    "source": ["http_api", "direct_queue", "vlm_layer"],
    "retry_count": 0
  }
}

A2A можно использовать в качестве промежуточного слоя для решения задач идентификации агентов.

А если добавить BPMN

Как можно улучшить процессы, добавив прозрачности
Как можно улучшить процессы, добавив прозрачности

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

Пример ИИ-агента, обрабатывающего заявки на утерянный багаж, в котором могут быть реализованы различные паттерны
Пример ИИ-агента, обрабатывающего заявки на утерянный багаж, в котором могут быть реализованы различные паттерны

Мультиагентный паттерн

Наш базовый сценарий, повторённый в BPMN. Мы просто копируем и описываем логику на основе сообщений (message flow) между пулами.

Пример BPMN-схемы с вызовом нескольких агентов через систему сообщений протокола Zeebee
Пример BPMN-схемы с вызовом нескольких агентов через систему сообщений протокола Zeebee

Human-in-the-loop

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

Long-running processes

Если процесс работает слишком долго, его можно прервать по истечении заданного времени (таймауту). Это может произойти автоматически через события или по решению агентов.

Prompt chaining

Управление процессом путём корректировки промптов: по результатам работы одного агента корректируется или заново составляется промпт для следующего.

Orchestrator-workers

Для улучшения оркестрации решения о маршрутизации задач принимают сразу несколько LLM или задач с логикой. В этом случае необходимо реализовать подходящий по сценарию алгоритм консенсуса.

Evaluator-optimizer

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

А как же Dify, n8n, Langflow

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

А как в Домклик

Мы реализовали несколько агентов и создали собственную платформу для их управления. Для модерации и OCR у нас существуют отдельные пайплайны, частично использующие BPMN. В настоящее время мы оптимизируем пайплайны OCR, добавляя маршрутизаторы на основе BPMN-схем. Придерживаться единого паттерна по организации оркестрации между агентами сложно, так как их количество пока невелико и они только осваиваются бизнесом на собственных задачах. Кроме того, прежде чем решать задачи оркестрации, необходимо настроить guardrails — набор правил и ограничений, которые не позволят агентам «разгуляться».

Подробнее про ИИ-агентов в Домклик вы можете узнать из статьи моей коллеги @Quvele.