Мультиагентные системы - главный тренд в AI-разработке. AutoGPT, CrewAI, LangGraph, Microsoft AutoGen обещают армию специализированных агентов, которые вместе решат любую задачу.

Сделал систему на 5 агентов, а потом передумал и сделал на одного.

История о том, что иногда с ИИ надо упрощать, а не усложнять.

Что делал

Сервис для автоматической генерации дашбордов из данных. Пользователь загружает файл (CSV, Excel, JSON, PDF), AI анализирует данные и создаёт интерактивный дашборд с графиками. Бесплатный, без регистрации.

За пару недель работы:

  • 153 дашборда сгенерировано

  • 93 уникальных пользователя

  • Среди них крупные FMCG-сети

Частый размер данных пользователей: 50-2000 строк. В принципе, таблицы, которые открываются в Excel без проблем.

Пример готового дашборда
Пример готового дашборда

Версия 1: Пять агентов

Начал мы "по книжке". Разбили задачу на этапы, каждому этапу — свой агент:

┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│ Preprocessor│ → │  DataAgent  │ → │ DesignAgent │ → │ RenderAgent │ → │  Validator  │
│             │   │             │   │             │   │             │   │             │
│ Семплирует  │   │ Анализирует │   │ Проектирует │   │ Генерирует  │   │ Исправляет  │
│ большие     │   │ типы колонок│   │ layout      │   │ JSON для    │   │ ошибки      │
│ датасеты    │   │ и паттерны  │   │ и цвета     │   │ графиков    │   │ виджетов    │
└─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘

Каждый агент получал результат предыдущего и добавлял свою часть работы.

Код оркестратора выглядел красиво:

class AIOrchestrator:
    """
    Pipeline (5 stages):
        Data Upload -> [PREPROCESSOR] -> [DATA AGENT] -> [DESIGN AGENT] 
                    -> [RENDER AGENT] -> [VALIDATOR]
    """
    
    async def generate(self, parsed_data, filename, preferences):
        # Stage 0: Preprocessing — семплирование больших датасетов
        preprocessed = await self._run_preprocessor(parsed_data, filename)
        
        # Stage 1: Data Analysis — типы колонок, паттерны
        data_analysis = await self._run_data_agent(preprocessed.data, filename)
        
        # Stage 2: Design — layout, цветовая схема
        design_spec = await self._run_design_agent(data_analysis, filename, preferences)
        
        # Stage 3: Render — финальный JSON для Recharts
        render_spec = await self._run_render_agent(data_analysis, design_spec, preprocessed.data)
        
        # Stage 4: Validation — исправление сломанных виджетов
        validated_render = await self._run_validator_agent(render_spec, preprocessed.data)
        
        return self._build_dashboard_spec(validated_render)

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

Что не так

Проблема 1: вопрриятие

Попал на потерю контекста между агентами.

DataAgent анализирует данные и находит паттерн "продажи по месяцам". Передаёт в DesignAgent структуру DataAnalysis. Но это уже не сырые данные, а метаданные:

async def design(self, analysis: DataAnalysis, filename: str, preferences: dict):
    """
    Create a dashboard design based on data analysis.
    
    Args:
        analysis: DataAnalysis from Data Agent  # <-- Только метаданные!
    """

DesignAgent видит suggested_charts: [{chart_type: "line", title: "Monthly Sales"}], но не видит, что в данных есть колонка "region" для группировки. RenderAgent получает инструкцию "сделай line chart", но не знает какие именно данные использовать.

Пятый агент уже был на своей волне.

Проблема 2: ошибки

Ошибка на любом этапе каскадировалась до конца:

DataAgent:    "Колонка 'date' — это datetime"
              (на самом деле текст "Январь 2024")
                    ↓
DesignAgent:  "Построим line chart с осью времени"
                    ↓
RenderAgent:  "Сортируем по дате... ошибка парсинга"
                    ↓
ValidatorAgent: "Виджет сломан → конвертируем в table"
                    ↓
Пользователь: Получает скучную таблицу вместо графика

ValidatorAgent существовал именно для этого — чинить то, что сломали предыдущие агенты:

VALIDATOR_SYSTEM_PROMPT = """You analyze dashboard widgets and decide which need fixing.

Widget needs fixing if:
- type="table" but title suggests chart ("Spend by Category", "Top Merchants")
- data_length > 20 (raw data instead of aggregated)
- column_count > 4 (raw rows with many fields)
"""

Пятый агент исправлял ошибки четвёртого, который неправильно понял третьего. Играем в паровозик.

Проблема 3: данных мало, а работы много

PreprocessorAgent был нужен для семплирования больших датасетов:

DIRECT_PARSE_THRESHOLD = 2000

if row_count > DIRECT_PARSE_THRESHOLD:
    strategy = PreprocessingStrategy(
        method="sampling",
        target_rows=2000,
        reasoning="Large dataset - sampling to 2000 rows"
    )

Но 90% пользователей загружали файлы меньше 2000 строк. Для них PreprocessorAgent лишний вызов API, который ничего не делал кроме лени что-то делать.

Для файла в 100 строк было 5 вызовов API вместо одного.

Проблема 4: надо платить деньги

Посчитали расход токенов на типичный дашборд:

Агент

Tokens In

Tokens Out

Время

Preprocessor

~5K

~1K

~2s

DataAgent

~10K

~3K

~3s

DesignAgent

~8K

~2K

~2s

RenderAgent

~15K

~5K

~4s

Validator

~5K

~1K

~2s

Итого

~43K

~12K

~13s

13 секунд ожидания и 55K токенов на один дашборд. Это должна была быть идеальная картина, по факту code execution занимал сильно больше времени. Доходило до 3х минут.

Версия 2: один в поле воин

Ключевое изменений - убил всех, но родил одного.

┌──────────────────────────────────────────────────────────┐
│                    Single Agent                          │
│                                                          │
│  Grok + code_execution (pandas)                          │
│                                                          │
│  1. Парсит файл напрямую                                 │
│  2. Анализирует структуру и паттерны                     │
│  3. Проектирует layout                                   │
│  4. Генерирует JSON для графиков                         │
│                                                          │
│  ВСЁ В ОДНОМ ВЫЗОВЕ API                                  │
└──────────────────────────────────────────────────────────┘

Промпт стал проще:

SINGLE_AGENT_SYSTEM_PROMPT = """You are a Dashboard Generation AI.

## Your Task
1. Parse the attached file using code_execution (pandas)
2. Analyze data structure, types, patterns
3. Design optimal widget layout
4. Generate complete Recharts-ready JSON

## File Parsing
- The file is ATTACHED to this message
- Use code_execution to load it with pandas
- For Excel: Check for title rows before headers
- For CSV: Try encodings utf-8, cp1251, latin1
"""

Агент сам парсит файл через Python, сам находит паттерны, сам решает какие графики нужны, сам генерирует JSON. Паровозик всё.

Результаты:

Метрика

Мультиагенты

Single Agent

Tokens In

~43K

~20K

Tokens Out

~12K

~3K

Время

~13s

~5s

Точек отказа

5

1

В 2-3 раза меньше токенов, в 2.5 раза быстрее. AI-парень сталь радовать.

Retry-логика тоже упростилась:

for attempt in range(max_retries):
    result = await self._generate_with_streaming(...)
    
    if result.get("success"):
        return result
    
    # Retry только на ошибках парсинга JSON
    if "Invalid" not in error_msg and "JSON" not in error_msg:
        return result

return {"success": False, "error": "Failed after retries"}

В мультиагентной системе retry на каждом этапе создавал комбинаторный взрыв: что делать, если DataAgent отработал, а DesignAgent упал? Откатывать всё? Пробовать только DesignAgent? А если он опять упадёт, потому что DataAgent дал плохой анализ?

Почему code_execution и кто это

Раньше агент работал через текстовое описание в промпте. Контекстное окно ограничено, что-то большое в него не вмещается.

С code_execution агент может:

  • Загрузить файл любого размера через pandas

  • Посмотреть на данные своими глазами

  • Попробовать разные подходы к парсингу

  • Агрегировать данные как нужно

Этот уже не нужен PreprocessorAgent - семплирование для уменьшения контекста. Агент сам работает с данными.

Когда банда гномов нужна

Мультиагентные системы имеют смысл в других сценариях.

Большие данные (>100K строк)

Если данные не влезают в память или требуют распределённой обработки — нужен отдельный агент для препроцессинга. Один агент не справится с терабайтной базой.

Разные инструменты для разных этапов

Если один этап требует SQL, другой Python, третий специализированного API визуализации, тогда имеет смысл разделить на агентов со своими возможностями.

Параллельная обработка

Если можно анализировать разные аспекты данных параллельно и потом объединить — мультиагенты дадут выигрыш по времени.

Сложные многоэтапные пайплайны

Генерация кода → Тестирование → Code review → Рефакторинг. Каждый этап — отдельная задача с разными требованиями.

Для наших малых данных (50-2000 строк) ничего из этого не актуально. Один агент с code_execution покрывает все потребности.

Матрица выбора архитектуры

Критерий

Single Agent

Multi-Agent

Размер данных

< 50K строк

> 100K строк

Задержка критична

Параллельная обработка

Простота отладки

Сложная диагностика

Стоимость токенов

Ниже

Выше

Разные инструменты

Один toolset

Разные capabilities

Сложность задачи

Линейная

Ветвящаяся логика

Выводы

Начинайте с простого. 

Лучше один агент с полным пониманием задачи, чем пять агентов - паровозиков.

Используй code_execution.

Измеряйте, не предполагайте. 

Мультиагентные системы - мощно, но не везде.

Стек: FastAPI, Next.js, Grok (xAI) с code_execution