Паттерны мультиагентных систем: субагенты, передача управления, навыки, маршрутизатор, пользовательские рабочие процессы.
Мультиагентная система координирует несколько специализированных исполнителей. Нужна она реже, чем кажется: один агент справляется с большинством задач.
Признаков ровно три, и все они наблюдаемые.
Инструментов стало столько, что модель выбирает неправильный. Обычно это заметно после двух-трёх десятков инструментов: агент вызывает похожий по названию вместо нужного.
Задачи требуют разного контекста, который не помещается вместе. Юридические формулировки, техническая документация и правила скидок в одном системном промпте мешают друг другу.
Подзадачи независимы и их выгодно выполнять параллельно. Сравнение трёх поставщиков быстрее делать одновременно, а не по очереди.
Если ни один признак не выполняется, разделение на агентов добавит вызовов модели, задержку и новые классы ошибок, ничего не улучшив.
Главный агент получает субагентов как инструменты. Он формулирует задачу, субагент её выполняет в собственном контексте и возвращает результат.
from deepagents.middleware.subagents import SubAgentMiddleware
middleware = SubAgentMiddleware(
subagents=[
{
"name": "researcher",
"description": "Ищет и сверяет информацию во внешних источниках",
"tools": [search, fetch_page],
"model": "openai:gpt-4o-mini",
},
{
"name": "writer",
"description": "Готовит связный текст по собранным материалам",
"tools": [],
"model": "openai:gpt-5.5",
},
],
)Главное свойство здесь — это изоляция контекста. Двадцать страниц поисковой выдачи остаются у субагента, наверх поднимается только вывод. Побочное свойство не менее полезно: субагентам можно назначать разные модели, отдавая дешёвой рутину, а дорогой финальный текст.
Вся маршрутизация проходит через главного агента, поэтому он остаётся узким местом: каждая делегация — это дополнительный вызов модели.
При хендоффах агенты передают работу друг другу напрямую. Инструмент передачи возвращает Command, который обновляет состояние и переключает выполнение:
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command
@tool
def transfer_to_billing(runtime: ToolRuntime) -> Command:
"""Передать разговор специалисту по счетам и платежам."""
return Command(
goto="billing_agent",
graph=Command.PARENT,
update={
"messages": [
ToolMessage(content="Передаю в биллинг.", tool_call_id=runtime.tool_call_id)
]
},
)Разница с субагентами принципиальная. Субагент — это подчинённый, который отчитывается наверх. Агент после хендоффа сам разговаривает с пользователем, пока не передаст управление дальше. Поэтому хендоффы естественны для поддержки и продаж, где разговор переходит между специалистами.
Отсюда же и экономия на повторных запросах: активный агент уже в контексте, повторная маршрутизация не нужна.
Навык — это не отдельный агент, а загружаемый по требованию набор знаний и инструкций. Агент остаётся один, но получает доменный контекст, когда задача того требует.
from deepagents.middleware.skills import SkillsMiddleware
middleware = SkillsMiddleware(backend=backend, sources=["./skills/"])При старте агент видит только имена и описания навыков, а полный текст подтягивается при обращении к области. Это самый дешёвый по вызовам модели способ специализации: делегирования нет вовсе, добавляется только контекст.
Ограничение тоже понятно: у навыков нет собственного контекстного окна. Если доменная работа порождает гору промежуточных данных, они осядут в общем контексте, и выигрыш пропадёт.
Отдельный шаг классифицирует запрос и направляет его специализированному агенту:
from typing import Literal
from langgraph.graph import StateGraph, START, END
def route(state: State) -> Literal["billing_agent", "tech_agent", "sales_agent"]:
category = classifier.invoke(state["messages"][-1].text)
return f"{category}_agent"
builder.add_conditional_edges(START, route)Классификация может делаться моделью или обычными правилами. Правила дешевле и предсказуемее, поэтому для устойчивых категорий с ними и начинают.
Маршрутизатор хорош, когда категории запросов известны и почти не пересекаются. Он же плохо переносит запросы «обо всём сразу»: одна ветка получает задачу, для которой у неё нет инструментов.
Наконец, поток можно описать графом целиком, смешивая детерминированные шаги и агентные. Это уже не паттерн, а конструктор: проверка входа обычным кодом, агент для анализа, параллельные ветви для сбора данных, узел сборки ответа.
Вариант выбирают, когда процесс устойчив и его выгодно зафиксировать, а не оставлять на усмотрение модели.
Документация LangChain приводит замеры для типовых запросов, и они полезны как ориентир.
Одиночный запрос вроде «купи кофе»: хендоффы, навыки и маршрутизатор укладываются в три вызова модели, субагенты требуют четырёх из-за шага делегирования.
Повторный запрос того же рода: хендоффы и навыки обходятся пятью вызовами суммарно, маршрутизатор шестью, субагенты восемью. Разница накапливается там, где состояние сохраняется между запросами.
Запрос по нескольким доменам сразу, например сравнение трёх языков программирования: субагенты и маршрутизатор дают около пяти вызовов и порядка девяти тысяч токенов, хендоффы семь и более вызовов при вдвое большем расходе токенов, навыки укладываются в три вызова, но с наибольшим расходом токенов, потому что весь контекст лежит в одном окне.
Вывод из этих чисел не «выберите лучший паттерн», а «выберите под профиль нагрузки». Диалоговые сценарии выигрывают от хендоффов, разовые многодоменные от субагентов и маршрутизатора.
Начните с одного агента. Замерьте, где именно он ошибается: путает инструменты, теряет контекст, работает слишком долго.
Если проблема в выборе из большого набора инструментов, попробуйте сначала LLMToolSelectorMiddleware. Это дешевле мультиагентности и часто достаточно.
Если проблема в объёме промежуточных данных, берите субагентов: изоляция контекста их сильная сторона.
Если проблема в специализации знаний, а не в объёме, начните с навыков.
Если разговор действительно переходит между ролями, нужны хендоффы.
Если категории запросов чёткие и стабильные, ставьте маршрутизатор перед специализированными агентами.
Потеря контекста при передаче. Субагент получает задачу без предыстории и переспрашивает то, что пользователь уже говорил. Лечится явной передачей нужных фактов в постановке задачи.
Пинг-понг между агентами. Двое передают работу друг другу по кругу, потому что каждый считает её чужой. Помогают лимиты вызовов и явное правило в промпте о том, кто принимает решение при неоднозначности.
Расплывчатые описания субагентов. Описание — это единственное, по чему главный агент выбирает исполнителя, и «помогает с разными задачами» гарантирует неверный выбор.
Молчание в интерфейсе. Пока субагент работает, поток родителя пуст, и кажется, что система зависла. Включайте события подграфов при стриминге.
Далее: Продвинутые темы