Разработка ИИ-агентов · Модуль 3 · Урок 3.3

Параллельное выполнение и передача контекста между агентами

Когда части независимы — запускайте параллельно

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

Go создан для этого: горутины и каналы делают fan-out/fan-in (разослать задачи — собрать результаты) естественным. Но параллелить стоит только истинно независимые части. Если шаг B нужен вход от шага A — это последовательность, и параллелизм тут не поможет.

Fan-out / fan-in на Go без гонок

Базовый приём: на каждую подзадачу — своя горутина; результаты складываем в слайс по индексу (каждая горутина пишет в свою ячейку — гонок нет) или шлём в канал. sync.WaitGroup дожидается всех. Обязательно прокидывайте context.Context для отмены и таймаута: если одна ветка зависла или вся операция отменена — остальные не должны висеть.

Важные мелочи параллелизма: ограничивайте степень параллелизма (семафор/пул), чтобы не упереться в rate limit провайдера; решите заранее политику ошибок — падать при первой (fail-fast) или собрать частичный результат; не пишите конкурентно в общую map без защиты. Каждый вызов модели — это сетевой запрос со своей стоимостью, поэтому «запустить 100 агентов разом» бьёт и по лимитам, и по кошельку.

Передача контекста: давать только нужное

Ключевой вопрос мультиагентных систем — сколько контекста передавать между агентами. Соблазн «передать всю историю, пусть разберётся» вреден: раздувает окно, размывает фокус субагента и повышает стоимость. Правильно — передавать минимальный достаточный контекст: чёткую формулировку подзадачи и только те факты, что нужны именно для неё.

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

Fan-out/fan-in: параллельные субагенты с context и записью по индексу
// runParallel запускает независимые подзадачи параллельно и собирает результаты.
func runParallel(ctx context.Context, tasks []string, sub SubAgent) ([]string, error) {
    results := make([]string, len(tasks)) // каждая горутина пишет в свою ячейку
    errs := make([]error, len(tasks))

    sem := make(chan struct{}, 4) // ограничиваем параллелизм (rate limit)
    var wg sync.WaitGroup

    for i, task := range tasks {
        wg.Add(1)
        go func(i int, task string) {
            defer wg.Done()
            select {
            case sem <- struct{}{}:
                defer func() { <-sem }()
            case <-ctx.Done():
                errs[i] = ctx.Err()
                return
            }
            results[i], errs[i] = sub(task) // sub должен уважать ctx внутри
        }(i, task)
    }
    wg.Wait()

    for _, err := range errs {
        if err != nil {
            return results, err // политика: вернуть первую ошибку (можно собирать частичное)
        }
    }
    return results, nil
}

Anti-patterns

Анти-паттернПочему плохоКак правильно
Параллелить зависимые шагиB нужен результат A — гонка/мусорПараллель только для истинно независимых частей
Запускать неограниченно много агентов разомRate limit, всплеск стоимостиОграничивать параллелизм семафором/пулом
Конкурентная запись в общую mapData race, паникаПисать в слайс по индексу или в канал; защищать общее состояние
Передавать субагенту всю историюРаздувает окно, размывает фокус, дорогоМинимальный контекст: подзадача + нужные факты; обмен сводками
Параллелизм без context/таймаутаЗависшая ветка держит всю операциюПрокидывать context.Context для отмены и таймаута

Практическое задание

  • Найдите в своей задаче независимые подзадачи и запустите их параллельно горутинами.
  • Соберите результаты без гонок (слайс по индексу или канал) и дождитесь всех через sync.WaitGroup.
  • Ограничьте степень параллелизма семафором, чтобы не упереться в rate limit.
  • Прокиньте context.Context с таймаутом и проверьте отмену зависшей ветки.
  • Сделайте так, чтобы субагент возвращал компактную сводку, а не весь свой диалог; координатор работает со сводкой.

Проверка знаний

Какие подзадачи можно безопасно выполнять параллельно?

  • A Любые — параллелизм всегда ускоряет
  • B Только истинно независимые: где результат одной не нужен другой на входе
  • C Только если их ровно две
  • D Только последовательные

Почему параллельный запуск субагентов нужно ограничивать (семафор/пул)?

  • A Иначе Go упадёт с ошибкой компиляции
  • B Чтобы не упереться в rate limit провайдера и не получить всплеск стоимости/нагрузки
  • C Ограничивать не нужно
  • D Чтобы ответы стали длиннее

Сколько контекста стоит передавать субагенту?

  • A Всю историю координатора — пусть разберётся
  • B Минимально достаточный: формулировку подзадачи и только нужные ей факты; обмениваться компактными сводками
  • C Ничего — субагент догадается сам
  • D Случайную часть истории