Почему один агент не тянет всё

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

В команде людей эту проблему решают разделением труда: каждый специалист отвечает за своё, а руководитель координирует. Мультиагентный паттерн Supervisor переносит это в код: несколько узких агентов-воркеров (у каждого свой промпт и свой набор инструментов) и один супервайзер, который смотрит на задачу и решает, кому делегировать следующий шаг.

ℹ️ Это прямое продолжение сабграфов

Помнишь конец урока про subgraphs: «агент = subgraph, супервайзер сверху»? Вот он. Каждый воркер — самостоятельный агент (часто create_react_agent из урока про ReAct), подключённый узлом, а супервайзер связывает их условными рёбрами. Все кирпичики уже знакомы — собираем из них команду.

Топология: хаб и воркеры

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

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
START supervisor кому передать ход? END researcher поиск, факты coder пишет код writer оформляет FINISH → END делегирует отчитывается всё проходит через центр: воркер всегда возвращается к супервайзеру за следующим решением

Состояние команды — общая история messages (как в чат-агенте): каждый воркер дописывает в неё свой результат, и супервайзер видит всю картину, принимая следующее решение. Дополнительное поле next хранит, кого супервайзер выбрал.

Супервайзер как маршрутизатор

Супервайзер — это, по сути, узел-маршрутизатор на LLM. Он смотрит на историю и выбирает имя следующего воркера или FINISH. Чтобы выбор был надёжным (а не свободным текстом), используем структурированный вывод с фиксированным набором вариантов.

Узел супервайзера со структурированным выбором
python
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 со своими инструментами). Узел-обёртка запускает агента и дописывает его ответ в общую историю с именем, чтобы по диалогу было видно, кто что сделал. После воркера ребро всегда ведёт обратно в супервайзера.

Воркеры-агенты и граф-команда
python
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 — защита от вечного делегирования

Если супервайзер никогда не возвращает FINISH, команда будет гонять задачу по кругу. В системном промпте чётко опиши критерий завершения, а как страховку — держи recursion_limit (урок про conditional edges). Мультиагентные циклы особенно склонны зацикливаться, если воркеры «пингуют» друг друга.

Готовый create_supervisor и иерархии

Паттерн настолько частый, что его вынесли в отдельный пакет langgraph-supervisor. Функция create_supervisor собирает такой граф за тебя — передаёшь список агентов и промпт супервайзера.

Supervisor одной функцией
python
# 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 (всё через центр)
Очень много специализаций иерархия супервайзеров
Линейный конвейер без ветвления обычный граф-цепочка, не команда

Типичные ошибки

Ошибка 1: воркер не возвращается к супервайзеру

Без ребра worker → supervisor координация рушится: после воркера некому решить следующий шаг. В классическом супервайзере все воркеры ведут обратно в центр.

Ошибка 2: нет условия FINISH

Если супервайзер не умеет завершать, команда зациклится. Дай ему вариант FINISH и явный критерий завершения в промпте.

Ошибка 3: расплывчатые роли воркеров

Если зоны ответственности пересекаются, супервайзер путается, кому делегировать, и гоняет задачу между похожими агентами. Роли должны быть узкими и непересекающимися.

Ошибка 4: супервайзер на простую задачу

Мультиагентная команда — это накладные расходы (лишние вызовы LLM на маршрутизацию). Для однодоменной задачи хватит одного агента; супервайзер берут, когда специализации реально разные.

Шпаргалка

Supervisor — всё в одном месте
python
# Состояние команды: общая история + кого выбрали
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 как страховка)
#  • роли воркеров узкие и непересекающиеся
#  • один домен → один агент; супервайзер для разных специализаций

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

Собери команду агентов:

Задание: супервайзер исследователя и математика

  1. Сделай двух воркеров через create_react_agent: researcher с инструментом поиска (заглушка) и mather с инструментом-калькулятором.
  2. Опиши State(MessagesState) с полем next и узел-супервайзер со структурированным выбором (Literal[...] + FINISH).
  3. Собери граф-звезду: START → supervisor, каждый воркер → обратно в supervisor, условное ребро по next с FINISH → END.
  4. Дай задачу, требующую обоих («найди население города X и умножь на 2»). Через stream посмотри, как супервайзер передаёт ход между воркерами.
  5. Проверь, что без варианта FINISH (убери его) команда зацикливается — и верни FINISH + recursion_limit.
  6. Со звёздочкой: собери тот же результат через create_supervisor из langgraph-supervisor и сравни объём кода с ручной сборкой.

Что дальше