Почему один агент не тянет всё
Чем шире обязанности у единственного агента, тем хуже он работает. Десять инструментов из трёх разных областей — и модель начинает выбирать не тот, путать форматы, тянуть в промпт несовместимые инструкции («ты и аналитик, и программист, и редактор»). Контекст переполняется, рассуждения мутнеют.
В команде людей эту проблему решают разделением труда: каждый специалист отвечает за своё, а руководитель координирует. Мультиагентный паттерн Supervisor переносит это в код: несколько узких агентов-воркеров (у каждого свой промпт и свой набор инструментов) и один супервайзер, который смотрит на задачу и решает, кому делегировать следующий шаг.
Помнишь конец урока про subgraphs: «агент = subgraph, супервайзер сверху»? Вот он. Каждый воркер — самостоятельный агент (часто create_react_agent из урока про ReAct), подключённый узлом, а супервайзер связывает их условными рёбрами. Все кирпичики уже знакомы — собираем из них команду.
Топология: хаб и воркеры
Структура — «звезда»: супервайзер в центре, воркеры по краям. Поток всегда проходит через центр: супервайзер выбирает воркера → воркер делает работу и возвращается к супервайзеру → тот снова решает, кому передать ход или что пора заканчивать.
Состояние команды — общая история messages (как в чат-агенте): каждый воркер дописывает в неё свой результат, и супервайзер видит всю картину, принимая следующее решение. Дополнительное поле next хранит, кого супервайзер выбрал.
Супервайзер как маршрутизатор
Супервайзер — это, по сути, узел-маршрутизатор на LLM. Он смотрит на историю и выбирает имя следующего воркера или FINISH. Чтобы выбор был надёжным (а не свободным текстом), используем структурированный вывод с фиксированным набором вариантов.
from typing import Literal, TypedDict
from langgraph.graph import MessagesState
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
MEMBERS = ["researcher", "coder", "writer"]
OPTIONS = MEMBERS + ["FINISH"]
class State(MessagesState): # messages + наше поле
next: str
class Route(TypedDict):
next: Literal["researcher", "coder", "writer", "FINISH"]
SYS = (f"Ты супервайзер команды: {MEMBERS}. По диалогу реши, кто работает "
f"следующим. Когда задача выполнена — ответь FINISH.")
def supervisor(state: State) -> dict:
messages = [{"role": "system", "content": SYS}] + state["messages"]
route = llm.with_structured_output(Route).invoke(messages)
return {"next": route["next"]}
Воркеры и сборка графа
Каждый воркер — специализированный агент (возьмём create_react_agent со своими инструментами). Узел-обёртка запускает агента и дописывает его ответ в общую историю с именем, чтобы по диалогу было видно, кто что сделал. После воркера ребро всегда ведёт обратно в супервайзера.
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import AIMessage
# --- специализированные агенты (у каждого свои инструменты) ---
research_agent = create_react_agent(llm, tools=[web_search])
code_agent = create_react_agent(llm, tools=[run_python])
def research_node(state: State) -> dict:
result = research_agent.invoke({"messages": state["messages"]})
text = result["messages"][-1].content
return {"messages": [AIMessage(content=text, name="researcher")]}
def code_node(state: State) -> dict:
result = code_agent.invoke({"messages": state["messages"]})
text = result["messages"][-1].content
return {"messages": [AIMessage(content=text, name="coder")]}
# --- сборка ---
builder = StateGraph(State)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", research_node)
builder.add_node("coder", code_node)
builder.add_edge(START, "supervisor")
for member in ["researcher", "coder"]:
builder.add_edge(member, "supervisor") # воркер → ВСЕГДА к супервайзеру
# супервайзер маршрутизирует по полю next
builder.add_conditional_edges("supervisor", lambda s: s["next"],
{"researcher": "researcher", "coder": "coder", "FINISH": END})
graph = builder.compile()
result = graph.invoke({"messages": [{"role": "user",
"content": "Найди формулу площади круга и напиши функцию на Python."}]})
print(result["messages"][-1].content)
Поток: супервайзер видит запрос → шлёт в researcher (тот находит формулу) → возвращается к супервайзеру → тот шлёт в coder (пишет функцию) → возвращается → супервайзер видит, что всё готово → FINISH. Решение «кто следующий» каждый раз принимается заново по актуальной истории.
Если супервайзер никогда не возвращает FINISH, команда будет гонять задачу по кругу. В системном промпте чётко опиши критерий завершения, а как страховку — держи recursion_limit (урок про conditional edges). Мультиагентные циклы особенно склонны зацикливаться, если воркеры «пингуют» друг друга.
Готовый create_supervisor и иерархии
Паттерн настолько частый, что его вынесли в отдельный пакет langgraph-supervisor. Функция create_supervisor собирает такой граф за тебя — передаёшь список агентов и промпт супервайзера.
# pip install langgraph-supervisor
from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent
researcher = create_react_agent(llm, tools=[web_search], name="researcher",
prompt="Ты исследователь.")
coder = create_react_agent(llm, tools=[run_python], name="coder",
prompt="Ты программист.")
app = create_supervisor(
agents=[researcher, coder],
model=llm,
prompt="Ты супервайзер. Делегируй задачи researcher и coder.",
).compile()
Команда может вырасти в иерархию: воркером супервайзера становится не отдельный агент, а целая под-команда со своим супервайзером (это снова сабграф). Так строят «команды команд» для очень сложных задач. Начинай с плоского супервайзера и одного уровня — иерархию добавляй, только когда воркеров становится слишком много для одного управляющего.
Когда супервайзер, а когда нет
| Ситуация | Подход |
|---|---|
| Задача в одном домене, немного инструментов | один ReAct-агент (супервайзер избыточен) |
| Разные домены, чёткие специализации | Supervisor с воркерами |
| Нужна централизованная координация и контроль | Supervisor (всё через центр) |
| Очень много специализаций | иерархия супервайзеров |
| Линейный конвейер без ветвления | обычный граф-цепочка, не команда |
Типичные ошибки
Без ребра worker → supervisor координация рушится: после воркера некому решить следующий шаг. В классическом супервайзере все воркеры ведут обратно в центр.
Если супервайзер не умеет завершать, команда зациклится. Дай ему вариант FINISH и явный критерий завершения в промпте.
Если зоны ответственности пересекаются, супервайзер путается, кому делегировать, и гоняет задачу между похожими агентами. Роли должны быть узкими и непересекающимися.
Мультиагентная команда — это накладные расходы (лишние вызовы LLM на маршрутизацию). Для однодоменной задачи хватит одного агента; супервайзер берут, когда специализации реально разные.
Шпаргалка
# Состояние команды: общая история + кого выбрали
class State(MessagesState):
next: str
# Супервайзер = LLM-маршрутизатор со структурированным выбором
class Route(TypedDict):
next: Literal["researcher", "coder", "FINISH"]
def supervisor(state):
route = llm.with_structured_output(Route).invoke([{"role":"system",...}] + state["messages"])
return {"next": route["next"]}
# Воркеры = специализированные агенты (create_react_agent), дописывают в messages с name
# Сборка (топология «звезда»):
builder.add_edge(START, "supervisor")
for m in MEMBERS:
builder.add_edge(m, "supervisor") # воркер ВСЕГДА назад в центр
builder.add_conditional_edges("supervisor", lambda s: s["next"],
{**{m: m for m in MEMBERS}, "FINISH": END})
# Готовый вариант:
from langgraph_supervisor import create_supervisor
app = create_supervisor(agents=[...], model=llm, prompt="...").compile()
# Правила:
# • каждый воркер возвращается к супервайзеру
# • супервайзер умеет FINISH (+ recursion_limit как страховка)
# • роли воркеров узкие и непересекающиеся
# • один домен → один агент; супервайзер для разных специализаций
Практическое задание
Собери команду агентов:
Задание: супервайзер исследователя и математика
- Сделай двух воркеров через
create_react_agent:researcherс инструментом поиска (заглушка) иmatherс инструментом-калькулятором. - Опиши
State(MessagesState)с полемnextи узел-супервайзер со структурированным выбором (Literal[...]+FINISH). - Собери граф-звезду:
START → supervisor, каждый воркер → обратно в supervisor, условное ребро поnextсFINISH → END. - Дай задачу, требующую обоих («найди население города X и умножь на 2»). Через
streamпосмотри, как супервайзер передаёт ход между воркерами. - Проверь, что без варианта
FINISH(убери его) команда зацикливается — и верниFINISH+recursion_limit. - Со звёздочкой: собери тот же результат через
create_supervisorизlanggraph-supervisorи сравни объём кода с ручной сборкой.