Разработка ИИ-агентов · Модуль 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, но уже на уровне между агентами.
// 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, всплеск стоимости | Ограничивать параллелизм семафором/пулом |
Конкурентная запись в общую map | Data race, паника | Писать в слайс по индексу или в канал; защищать общее состояние |
| Передавать субагенту всю историю | Раздувает окно, размывает фокус, дорого | Минимальный контекст: подзадача + нужные факты; обмен сводками |
| Параллелизм без context/таймаута | Зависшая ветка держит всю операцию | Прокидывать context.Context для отмены и таймаута |
Практическое задание
- Найдите в своей задаче независимые подзадачи и запустите их параллельно горутинами.
- Соберите результаты без гонок (слайс по индексу или канал) и дождитесь всех через
sync.WaitGroup. - Ограничьте степень параллелизма семафором, чтобы не упереться в rate limit.
- Прокиньте
context.Contextс таймаутом и проверьте отмену зависшей ветки. - Сделайте так, чтобы субагент возвращал компактную сводку, а не весь свой диалог; координатор работает со сводкой.
Проверка знаний
Какие подзадачи можно безопасно выполнять параллельно?
Верный ответ: B
B верно. Параллелить можно независимые части; если B ждёт выход A — это последовательность. «Любые» (A) приводит к гонкам/мусору; число задач (C) роли не играет.
Почему параллельный запуск субагентов нужно ограничивать (семафор/пул)?
Верный ответ: B
B верно. Каждый агент — сетевой вызов; «сто разом» бьёт по лимитам и кошельку. Семафор/пул держат параллелизм в рамках.
Сколько контекста стоит передавать субагенту?
Верный ответ: B
B верно. Минимальный достаточный контекст сохраняет фокус субагента, экономит окно и деньги. Полная история (A) раздувает контекст; «ничего» (C) лишает агента нужных данных.