18. Sistemas Multi-Agente — O Padrão Supervisor
Pense no agente de atendimento ao cliente que construímos no Capítulo 16. Ele tinha exatamente uma ferramenta — consulta de pedidos — então, quando um cliente perguntava sobre um pedido, ele informava o status do envio. Com apenas uma ferramenta, simplesmente não havia como ele escolher a errada.
Agora suponha que façamos esse agente crescer até virar um serviço de produção real. A consulta de pedidos sozinha não é suficiente. Precisaríamos de cancelamento de pedidos, rastreamento de envio, alteração de endereço, solicitações de troca, solicitações de devolução, verificações de elegibilidade para reembolso, processamento de reembolsos, verificações de estoque, emissão de cupons, consulta de pontos, criação de tickets de suporte e muito mais. A mecânica é simples: continue adicionando ferramentas e anexando regras de negócio ao prompt de sistema.
Mas, à medida que as ferramentas e regras se acumulam, surgem três problemas.
-
A seleção de ferramentas fica menos precisa. A cada rodada, o modelo lê todas as descrições de ferramentas e decide qual chamar. Conforme você adiciona ferramentas que recebem as mesmas entradas e se sobrepõem em propósito — como solicitação de troca e solicitação de devolução —, as chances de escolher a errada aumentam.
-
O contexto se enche de informações que você não precisa no momento. Mesmo enquanto processa um reembolso, o esquema da ferramenta de verificação de estoque, as regras de emissão de cupons, o procedimento de solicitação de troca e todo o resto seguem junto em cada chamada. Você está enviando o conjunto completo de esquemas de ferramentas e as regras de negócio de cada domínio a cada vez. Quando o contexto está lotado de material irrelevante para a tarefa atual, as partes que importam ficam soterradas e a precisão sofre.
-
Fica difícil de mudar. Ajustar uma única regra de reembolso significa editar um prompt onde as regras de todos os domínios estão emaranhadas, e você não consegue ter certeza de que a mudança não vai repercutir em trocas ou envios. Se domínios diferentes têm donos diferentes, o problema só cresce.
Este capítulo ensina uma forma de lidar com isso. Em vez de empilhar mais ferramentas e regras em um único agente, dividimos o trabalho em um agente por domínio. Continuando o cenário de atendimento ao cliente do Capítulo 16, vamos construir uma equipe formada por um agente que só lida com consultas de pedidos e um agente que só lida com reembolsos. Cada um tem suas próprias ferramentas e seu próprio prompt. Depois adicionamos mais um agente para dirigi-los — esse diretor é chamado de supervisor. Uma configuração com vários agentes como esta é um sistema multi-agente, e o arranjo em que um supervisor comanda os demais é o padrão supervisor.
Sistemas multi-agente têm um custo. Como o supervisor precisa decidir a qual trabalhador delegar a cada etapa, há mais chamadas de LLM, e isso significa mais latência e mais despesa. Então, se você não tem tantas ferramentas e regras, não há necessidade nenhuma de recorrer a um sistema multi-agente.
Aqui está o plano. Em 18.1 vemos o que é um sistema multi-agente e como ele funciona. Em 18.2 construímos os agentes trabalhadores. Em 18.3 construímos um supervisor à mão com o objeto Command do LangGraph. Em 18.4 encapsulamos os trabalhadores como ferramentas e reconstruímos a mesma equipe com muito menos código.
18.1) Compreendendo Sistemas Multi-Agente
18.1.1) O Que É um Sistema Multi-Agente
Vamos projetar um agente que consiga lidar com a solicitação "Meu pedido nunca chegou — se algo estiver errado, faça o reembolso." Poderíamos construí-lo com tudo o que aprendemos até aqui: anexar uma ferramenta de consulta de pedidos e uma ferramenta de reembolso a um único agente e deixar o agente fazer o loop até a tarefa terminar. Um único agente consulta o pedido, confirma que a entrega falhou, vê esse resultado, solicita o reembolso e escreve a resposta final.
Um sistema multi-agente é uma estrutura em que vários agentes dividem esse trabalho entre si. Você divide os agentes por domínio e coloca um supervisor acima deles, e cada agente possui apenas as ferramentas de que precisa. O agente que acabamos de esboçar, por exemplo, se divide naturalmente em um agente de consulta de pedidos e um agente de reembolso. O supervisor primeiro chama o agente de consulta de pedidos para verificar o status do envio; ao ouvir de volta que a entrega falhou, chama o agente de reembolso para processar o reembolso; então reúne os dois resultados para responder ao cliente. O que costumava ser um único agente chamando ferramentas em sequência tornou-se um supervisor chamando agentes em sequência.
Se houvesse apenas duas ferramentas, não haveria razão para dividir assim. Um agente seria suficiente, e adicionar um supervisor só adicionaria chamadas de LLM. Mas, como vimos na introdução, uma vez que há muitas ferramentas, um agente pode escolher a errada, seu contexto se enche de informações não relacionadas à tarefa em questão e seu prompt fica difícil de mudar.
Dividir resolve esses problemas. O agente de consulta de pedidos vê apenas algumas ferramentas relacionadas a pedidos, então escolher de uma lista de dezenas encolhe para escolher entre um punhado. Seu prompt contém apenas as regras de consulta de pedidos, então política de reembolso, condições de cupom e outros assuntos não relacionados à consulta de pedidos não enchem seu contexto. E, quando você precisa mudar uma regra de reembolso, mexe apenas no agente de reembolso, de modo que a mudança não chega aos demais.
O agente de consulta de pedidos e o agente de reembolso aqui não têm nada de especial. São o mesmo tipo de agente que você construiu no Capítulo 16. Você os cria passando um modelo, ferramentas e um prompt para create_agent, e os chama com invoke. Eles apenas cobrem menos terreno.
Então, o que o supervisor faz? O agente de consulta de pedidos e o agente de reembolso não sabem que o outro existe. Cada um apenas faz seu próprio trabalho; nenhum sabe quem deve ir primeiro. No exemplo acima, chamar a consulta de pedidos primeiro e — somente após ver seu resultado — chamar o reembolso foi julgamento do supervisor.
Quem chama quem, e quando. É a isso que chamamos de orquestração.
18.1.2) O Padrão Supervisor
O padrão supervisor é uma estrutura em que um único supervisor central orquestra vários agentes trabalhadores. Ele segue estas regras:
- O supervisor nunca faz o trabalho ele mesmo. Ele não consulta pedidos nem processa reembolsos. Ele apenas decide para quem repassar, depois reúne os resultados retornados em uma resposta final.
- Trabalhadores nunca chamam uns aos outros. O agente de consulta de pedidos nunca chama o agente de reembolso diretamente. Todo caminho passa pelo supervisor.
- Somente o supervisor fala com o cliente. Trabalhadores reportam ao supervisor, não ao cliente.
Então, como o supervisor decide qual agente chamar? O LLM decide. O supervisor lê toda a conversa até o momento e julga. Se ainda não sabe o status do pedido, chama o agente de consulta de pedidos; uma vez confirmado que a entrega falhou e que um reembolso é necessário, chama o agente de reembolso.
O supervisor repete esse julgamento até que a solicitação do usuário esteja concluída. Ele chama um agente, recebe um relatório, relê a conversa agora que o relatório foi adicionado e decide qual agente chamar em seguida.
Esse loop tem a mesma estrutura daquele que você construiu no Capítulo 14.
- Pensar — ler a conversa até o momento e decidir qual agente chamar.
- Agir — executar o agente escolhido.
- Observar — pegar o relatório do agente e adicioná-lo à conversa.
No Capítulo 14 você chamava ferramentas; aqui você chama agentes. É a única coisa que muda.
Observe como as setas voltam ao supervisor. Quando um trabalhador termina, ele reporta ao supervisor, e o supervisor lê esse relatório e decide o próximo passo.
O supervisor é quem encerra o loop. Uma vez que ele julga que a solicitação do cliente foi totalmente atendida, ele produz a resposta final e para.
18.2) Construindo os Agentes Trabalhadores
Vamos construir os dois agentes trabalhadores que projetamos em 18.1: um trabalhador de consulta de pedidos que verifica o status do pedido e um trabalhador de reembolso que processa reembolsos. Construiremos o supervisor em 18.3.
Um agente trabalhador é apenas o agente comum que você construiu no Capítulo 16. Vamos construí-los rapidamente com create_agent.
Primeiro, vamos configurar os dados de pedidos que os dois trabalhadores compartilharão.
ORDERS = {
"12345": {"item": "Fones de Ouvido Sem Fio", "amount": 89,
"status": "in_transit", "status_text": "Em trânsito (chega amanhã)"},
"67890": {"item": "Teclado Mecânico", "amount": 129,
"status": "delivered", "status_text": "Entregue"},
"24680": {"item": "Fones com Cancelamento de Ruído", "amount": 249,
"status": "delivery_failed", "status_text": "Falha na entrega (devolvido — destinatário não encontrado)"},
}O trabalhador de consulta de pedidos tem apenas uma ferramenta.
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def get_order_status(order_id: str) -> str:
"""Consulta o item, o valor pago e o status de envio de um número de pedido."""
order = ORDERS.get(order_id)
if order is None:
return f"Pedido {order_id} não encontrado."
return (f"Pedido {order_id}: {order['item']}, "
f"R${order['amount']:,}, status: {order['status_text']}")
order_agent = create_agent(
name="order_expert",
model="openai:gpt-5.4-mini",
tools=[get_order_status],
system_prompt=(
"Você é um especialista em consulta de pedidos. Consulte o status do pedido e responda.\n"
"Inclua o número do pedido, o item, o valor pago e o status de envio na sua resposta final.\n"
"Não julgue se um reembolso é justificado nem mencione reembolsos de forma alguma. Seu papel é apenas consultar pedidos e reportar status."
),
)O trabalhador de reembolso tem duas ferramentas: uma que decide se um pedido é elegível para reembolso, e outra que efetivamente processa o reembolso.
@tool
def check_refund_eligibility(order_id: str) -> str:
"""Determina se um pedido é elegível para reembolso. Apenas pedidos com falha na entrega qualificam."""
order = ORDERS.get(order_id)
if order is None:
return f"Pedido {order_id} não encontrado."
if order["status"] == "delivery_failed":
return f"Pedido {order_id} é elegível para reembolso (motivo: falha na entrega)."
return f"Pedido {order_id} não é elegível para reembolso (status atual: {order['status_text']})."
@tool
def issue_refund(order_id: str) -> str:
"""Processa um reembolso. Sempre confirme a elegibilidade com check_refund_eligibility antes de chamar isto."""
order = ORDERS.get(order_id)
if order is None:
return f"Pedido {order_id} não encontrado."
return (f"Reembolso concluído: R${order['amount']:,} do pedido {order_id} "
f"será reembolsado em 3–5 dias úteis. (número de aprovação: RF-{order_id})")
refund_agent = create_agent(
name="refund_expert",
model="openai:gpt-5.4-mini",
tools=[check_refund_eligibility, issue_refund],
system_prompt=(
"Você é um especialista em processamento de reembolsos.\n"
"Sempre confirme a elegibilidade com check_refund_eligibility primeiro, e só "
"então chame issue_refund.\n"
"Se você processou um reembolso, inclua o valor e o número de aprovação na sua resposta final.\n"
"Se o pedido não for elegível, não o processe — reporte o motivo em vez disso."
),
)Demos a ambos os trabalhadores um name. Esse nome é o que create_supervisor em 18.5 usa como o nome do nó e o nome da ferramenta de handoff.
Quatro Coisas de Que o Prompt de Sistema de um Trabalhador Precisa
O prompt de sistema de um trabalhador é construído a partir de quatro elementos — princípios que a Anthropic destilou ao construir seu próprio sistema de pesquisa multi-agente. Quando esses elementos são fracos, os trabalhadores duplicam trabalho, deixam tarefas por fazer ou não conseguem encontrar a informação de que precisam.
| Elemento | order_agent | refund_agent |
|---|---|---|
| Papel | Você é um especialista em consulta de pedidos | Você é um especialista em processamento de reembolsos |
| Orientação de ferramentas | Sempre confirme com check_refund_eligibility primeiro, e só então chame issue_refund | |
| Formato de saída | Inclua o número do pedido, o item, o valor pago e o status de envio na sua resposta final | Se você processou um reembolso, inclua o valor e o número de aprovação na sua resposta final |
| Limite de tarefa | Não julgue se um reembolso é justificado nem mencione reembolsos de forma alguma. Seu papel é apenas consultar pedidos e reportar status. | Se o pedido não for elegível, não o processe — reporte o motivo |
Papel fixa, em uma frase, quem é este trabalhador. Fixar sua identidade com "Você é um especialista em consulta de pedidos" mantém o modelo focado no seu próprio trabalho e menos propenso a vagar pelo trabalho de outra pessoa.
Orientação de ferramentas. Escreva isso quando há algo que o esquema da ferramenta sozinho não consegue transmitir — a ordem e as condições sob as quais as ferramentas devem ser usadas, por exemplo. Se não há nada a acrescentar além do esquema, você pode deixar de fora.
Formato de saída e limite de tarefa importam muito em um cenário multi-agente.
Formato de saída. A resposta final de um trabalhador não é uma resposta ao cliente — é um relatório submetido ao supervisor. Qualquer coisa não escrita ali nunca chega ao supervisor. Se um trabalhador consulta um valor com uma ferramenta mas o deixa de fora da resposta final, o supervisor não tem como saber disso.
Limite de tarefa. Este é o escopo que define até onde um trabalhador pode ir e o que ele não deve fazer. O trabalhador de consulta de pedidos deve apenas consultar — nunca reembolsar. É por isso que não lhe demos uma ferramenta de reembolso. Mas negar a ferramenta não é suficiente por si só, porque o modelo ainda pode dizer "A entrega falhou, então vou emitir um reembolso para você" sem ferramenta nenhuma. Se essa frase chegar ao supervisor, o supervisor pode presumir que um reembolso já está em andamento e nunca chamar o trabalhador de reembolso. Por isso o prompt também diz "Não julgue se um reembolso é justificado nem mencione reembolsos de forma alguma", impedindo-o de sequer trazer reembolsos à tona em palavras.
Escolhendo Modelos
Estamos usando gpt-5.4-mini para os trabalhadores e gpt-5.4 para o supervisor. Um trabalhador faz o trabalho simples de chamar algumas ferramentas em uma ordem definida, então um modelo pequeno é mais do que suficiente. O supervisor precisa ler a conversa inteira e julgar quem chamar em seguida, então precisa de um modelo maior. Poder escolher um modelo por agente, adequado à dificuldade do seu trabalho, é outro benefício de dividir.
Agora podemos adicionar o supervisor.
18.3) Construindo o Supervisor à Mão
Vamos construir um supervisor à mão. Na prática, você usará principalmente a abordagem em que o framework lida com isso para você (coberta na próxima seção), mas para entender o que está acontecendo por baixo dos panos, você precisa construí-lo você mesmo uma vez.
Como vimos em 18.1, o que o supervisor faz é um único loop: chamar um trabalhador, ler o relatório e decidir quem chamar em seguida — ou se deve parar — repetidamente.
18.3.1) Handoffs e Command
Para esse loop girar, o controle precisa passar de um lado para o outro entre o supervisor e os trabalhadores. O supervisor repassa o controle — "este trabalhador vai em seguida" — e, quando o trabalhador termina, ele devolve o controle ao supervisor. Essa passagem de controle de um nó para outro é chamada de handoff.
Um handoff precisa de duas informações: para onde ir (o destino) e o que passar adiante (o payload). O destino é sempre obrigatório; o payload é incluído apenas quando há algo a passar. No LangGraph, um nó especifica ambos retornando um Command.
from typing import Literal
from langgraph.graph import MessagesState
from langgraph.types import Command
def some_node(state: MessagesState) -> Command[Literal["refund_expert_proxy"]]:
return Command(
goto="refund_expert_proxy", # onde: o nó a executar em seguida
update={"messages": [...]}, # o quê: o relatório do trabalhador (o payload adicionado ao State)
)goto é o destino; update é o payload. A dica de tipo de retorno Command[Literal["refund_expert_proxy"]] lista, de antemão, os destinos aos quais este nó pode ir. Veremos como esse Command é realmente usado na próxima seção, onde construímos o nó supervisor e os proxies de trabalhador.
18.3.2) Construindo o Loop do Supervisor
A estrutura em si é simples. Fazemos um nó supervisor e tantos proxies de trabalhador quantos precisarmos. Um proxy de trabalhador é um nó que chama o agente trabalhador atribuído em nome do supervisor. O ponto de entrada é o supervisor, e cada proxy de trabalhador, uma vez concluído, volta ao supervisor — formando o loop. O Command que cada nó retorna é o que decide para onde o controle vai em seguida.
As linhas contínuas são handoffs entre nós (goto); as linhas pontilhadas são um proxy de trabalhador chamando seu agente trabalhador (invoke). O supervisor repassa a um proxy de trabalhador, e o proxy de trabalhador, uma vez concluído, retorna ao supervisor. Quando o supervisor decide FINISH, ele sai para END.
Agora vamos transformar esse desenho em código. Primeiro, a classe Route. Route é o esquema para receber a resposta do supervisor como uma resposta estruturada quando perguntamos ao LLM qual proxy de trabalhador chamar em seguida. Se o LLM respondesse em linguagem natural de forma livre, seria difícil saber qual proxy de trabalhador executar. Route contém qual proxy de trabalhador chamar em seguida (next) e o motivo pelo qual decidiu dessa forma (reason).
from typing import Literal
from pydantic import BaseModel, Field
class Route(BaseModel):
reason: str = Field(description="O motivo desta decisão.")
next: Literal["order_expert_proxy", "refund_expert_proxy", "FINISH"] = Field(
description="O nó trabalhador a executar em seguida. FINISH se a solicitação foi totalmente atendida."
)Há uma razão para reason ser declarado antes de next. A saída estruturada é gerada na ordem em que os campos aparecem no esquema, então, com reason primeiro, o LLM escreve seu raciocínio antes de escolher um trabalhador. Raciocinar primeiro e decidir depois produz uma escolha melhor. Inverta a ordem — coloque next primeiro — e o LLM escolhe um trabalhador antes de ter raciocinado, depois preenche uma justificativa para encaixar uma escolha que ele pode já ter feito errada.
Em seguida, o nó supervisor.
from typing import Literal
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.types import Command
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
supervisor_llm = ChatOpenAI(model="gpt-5.4")
SUPERVISOR_PROMPT = (
"Você é o supervisor de uma equipe de atendimento ao cliente. Você gerencia dois trabalhadores:\n"
"- order_expert_proxy: consulta o status do pedido.\n"
"- refund_expert_proxy: verifica a elegibilidade para reembolso e processa reembolsos.\n"
"Para decidir se um reembolso é necessário, você deve verificar o status do pedido primeiro.\n"
"Atribua a um trabalhador de cada vez e responda com FINISH assim que a solicitação for totalmente atendida."
)
def supervisor(
state: MessagesState,
) -> Command[Literal["order_expert_proxy", "refund_expert_proxy", "__end__"]]:
messages = [{"role": "system", "content": SUPERVISOR_PROMPT}, *state["messages"]]
decision = supervisor_llm.with_structured_output(Route).invoke(messages)
print(f"[supervisor] → {decision.next} ({decision.reason})")
if decision.next == "FINISH":
final = supervisor_llm.invoke(
[{"role": "system", "content": "Com base na conversa até o momento, escreva uma resposta ao cliente."},
*state["messages"]]
)
return Command(goto=END, update={"messages": [final]}) # repassa para END
return Command(goto=decision.next) # repassa para o proxy de trabalhadorAté agora, os nós retornavam apenas o State modificado. Mas o nó supervisor retorna um Command. Quando um nó retorna um Command, o LangGraph faz duas coisas: ele aplica o conteúdo de update ao State e executa o nó nomeado em goto em seguida. No código acima, colocamos o nome do proxy de trabalhador retirado de decision.next em goto, então o nó que o LLM escolheu — decision.next — é o que executa.
Em seguida, os proxies de trabalhador. Um proxy de trabalhador faz seu trabalho e devolve o controle ao supervisor — que é por isso que ele repassa com goto="supervisor".
O trabalho de um proxy de trabalhador é simples: chamar seu agente trabalhador com invoke.
def order_expert_proxy(state: MessagesState) -> Command[Literal["supervisor"]]:
result = order_agent.invoke(state)
last = result["messages"][-1]
return Command(
goto="supervisor",
update={"messages": [HumanMessage(content=last.content, name="order_expert")]},
)
def refund_expert_proxy(state: MessagesState) -> Command[Literal["supervisor"]]:
result = refund_agent.invoke(state)
last = result["messages"][-1]
return Command(
goto="supervisor",
update={"messages": [HumanMessage(content=last.content, name="refund_expert")]},
)Observe que pegamos apenas a mensagem final do trabalhador e passamos isso ao supervisor. O supervisor só precisa da conclusão; ele não precisa saber quantas vezes o trabalhador chamou ferramentas internamente.
18.3.3) Ligando e Executando o Grafo
Registramos os três nós e conectamos apenas o ponto de entrada ao supervisor. Todo outro movimento é decidido pelo Command de cada nó, então nenhuma outra aresta é necessária.
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor)
builder.add_node("order_expert_proxy", order_expert_proxy)
builder.add_node("refund_expert_proxy", refund_expert_proxy)
builder.add_edge(START, "supervisor")
team = builder.compile()
result = team.invoke(
{"messages": [HumanMessage(
content="O pedido 24680 ainda não chegou. Se algo estiver errado, por favor faça o reembolso."
)]},
config={"recursion_limit": 15},
)Saída:
[supervisor] → order_expert_proxy (Preciso verificar o status do pedido antes de decidir sobre um reembolso.)
[supervisor] → refund_expert_proxy (A falha na entrega está confirmada, então verifique a elegibilidade para reembolso e processe.)
[supervisor] → FINISH (A verificação do pedido e o processamento do reembolso estão ambos completos.)O supervisor primeiro roteou para a consulta de pedidos. Quando o relatório voltou informando que a entrega havia falhado, ele roteou para o reembolso, e quando o relatório de reembolso concluído chegou, ele finalizou. Ele escolheu cada próximo destino lendo o relatório do agente anterior.
O proxy de trabalhador passa todo o State compartilhado para seu trabalhador via invoke(state), então cada trabalhador vê toda a conversa até o momento. Com apenas dois trabalhadores isso está bem, mas conforme os trabalhadores e a conversa crescem, cada trabalhador acaba lendo mensagens que não têm nada a ver com seu próprio trabalho. Resolveremos isso de forma diferente na próxima seção.
18.4) Delegando aos Trabalhadores Por Meio de Ferramentas
Tendo construído os componentes internos do supervisor à mão em 18.3, vamos agora reconstruir a mesma equipe da forma que é recomendada para projetos reais. Essa abordagem não precisa de nenhuma nova API. Você transforma cada trabalhador em uma ferramenta com @tool e dá essas ferramentas a um agente supervisor. Construiremos o agente supervisor simplesmente com create_agent.
A ideia-chave cabe em uma frase: o supervisor é ele próprio apenas um agente, e cada trabalhador se torna uma ferramenta que o supervisor chama.
Visto dessa forma, o supervisor tem a mesma estrutura do agente de chamada de ferramentas que você construiu no Capítulo 16. Ele apenas possui ferramentas de alto nível que chamam agentes, em vez de ferramentas de baixo nível como get_order_status.
18.4.1) Encapsulando Trabalhadores como Ferramentas
Usamos o order_agent e o refund_agent de 18.2 sem alterações. Tudo o que fazemos é encapsular cada um em uma função @tool.
from langchain.tools import tool
# order_agent e refund_agent são os trabalhadores de 18.2
@tool
def lookup_order(request: str) -> str:
"""Consulta o item, o valor pago e o status de envio de um pedido. Use isto quando precisar saber o status de um pedido.
Entrada: uma solicitação de consulta em linguagem natural (ex.: 'Me diga o status de envio do pedido 24680').
"""
print("[chamada da ferramenta] lookup_order")
print(f" request: {request}")
result = order_agent.invoke({"messages": [{"role": "user", "content": request}]})
return result["messages"][-1].content
@tool
def handle_refund(request: str) -> str:
"""Verifica a elegibilidade para reembolso e processa um reembolso. Use isto quando o cliente quer um reembolso e você já confirmou o status do pedido.
Entrada: uma solicitação de reembolso em linguagem natural. Inclua o número do pedido e o status de envio confirmado pela consulta.
(ex.: 'O pedido 24680 está com status de falha na entrega. Processe um reembolso se elegível.')
"""
print("[chamada da ferramenta] handle_refund")
print(f" request: {request}")
result = refund_agent.invoke({"messages": [{"role": "user", "content": request}]})
return result["messages"][-1].contentTrês coisas mudaram.
-
As descrições das ferramentas substituem a lógica de roteamento. Em 18.3 escrevemos
SUPERVISOR_PROMPTe o esquemaRouteà mão para dizer ao supervisor sua lista de trabalhadores e suas opções. Aqui as docstrings das ferramentas fazem esse trabalho. O LLM do supervisor lê as descrições das ferramentas e decide quando chamar o quê. -
Cada trabalhador começa a partir de um contexto limpo. Colocado lado a lado com 18.3, a diferença fica clara.
python# 18.3 (grafo manual): passa todo o State compartilhado result = refund_agent.invoke(state) # 18.4 (delegação por ferramenta): passa apenas a descrição da tarefa que o supervisor escreveu result = refund_agent.invoke({"messages": [{"role": "user", "content": request}]})Em 18.4 o trabalhador de reembolso recebe apenas uma frase descrevendo sua tarefa. Ele nunca vê a formulação original do cliente, o raciocínio do supervisor ou o histórico de chamadas de ferramentas de outro trabalhador. Mesmo com dez trabalhadores e uma centena de rodadas de conversa, o contexto de cada trabalhador ainda é apenas aquela única descrição de tarefa.
-
Em troca, o supervisor assume o trabalho de passar a informação. Como um trabalhador não pode ver o histórico da conversa, tudo o que ele precisa tem que ser empacotado na string
requestpelo supervisor. É por isso que a docstring dehandle_refundespecifica "Inclua o número do pedido e o status de envio confirmado pela consulta." Sem essa instrução, o supervisor poderia passar apenas"Processe um reembolso", deixando o trabalhador de reembolso sem saber sequer de qual pedido se trata.
18.4.2) Montando e Executando o Supervisor
Nessa abordagem o supervisor, também, é apenas um agente. Nenhum Command, nenhum esquema Route necessário.
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
TOOL_SUPERVISOR_PROMPT = (
"Você é o supervisor de uma equipe de atendimento ao cliente.\n"
"Para decidir se um reembolso é necessário, você deve verificar o status do pedido primeiro.\n"
"Não faça o trabalho você mesmo — delegue aos trabalhadores.\n"
"Os trabalhadores não podem ver esta conversa. Quando você delegar, coloque tudo o que eles precisam na solicitação.\n"
"Quando todo o trabalho estiver concluído, sintetize os resultados dos trabalhadores em uma resposta ao cliente."
)
supervisor_agent = create_agent(
model="openai:gpt-5.4",
tools=[lookup_order, handle_refund],
system_prompt=TOOL_SUPERVISOR_PROMPT,
)
result = supervisor_agent.invoke(
{"messages": [HumanMessage(
content="O pedido 24680 ainda não chegou. Se algo estiver errado, por favor faça o reembolso."
)]}
)
print("\n\n[resposta final]")
print(result["messages"][-1].content)O resultado final é o mesmo de 18.3.
[chamada da ferramenta] lookup_order
request: O cliente diz que o pedido 24680 ainda não chegou. Para decidir se um reembolso é
necessário, por favor me dê o item, o valor pago e o status de envio atual do pedido 24680.
[chamada da ferramenta] handle_refund
request: O pedido 24680 é um Fones com Cancelamento de Ruído, R$249, e seu status de envio está
confirmado como 'Falha na entrega (devolvido — destinatário não encontrado)'. O cliente está
solicitando um reembolso, então verifique a elegibilidade e processe se for elegível.
[resposta final]
Verifiquei, e o pedido 24680 estava com status de falha na entrega (devolvido — destinatário não encontrado).
Ele qualificava para reembolso, e concluí o reembolso.
- Item: Fones com Cancelamento de Ruído
- Valor do reembolso: R$249
- Número de aprovação do reembolso: RF-24680
Dependendo do seu método de pagamento, normalmente leva alguns dias úteis para o reembolso ser lançado.Mas olhe as chamadas de ferramenta que o supervisor fez ao longo do caminho — é aí que a diferença em relação a 18.3 aparece. Olhe o request de handle_refund. O supervisor resumiu o resultado anterior da consulta e escreveu a descrição da tarefa ele mesmo. O trabalhador de reembolso recebe apenas esta única frase. Onde 18.3 entregava ao trabalhador a conversa inteira e o deixava vasculhar, aqui o supervisor seleciona apenas o que é necessário e passa isso adiante.
18.4.3) Adicionando Memória ao Supervisor com um Checkpointer
Dizer que o supervisor é um agente comum significa que o checkpointing que você aprendeu no Capítulo 17 funciona nele tal como está.
from langgraph.checkpoint.memory import InMemorySaver
supervisor_agent = create_agent(
model="openai:gpt-5.4",
tools=[lookup_order, handle_refund],
system_prompt=TOOL_SUPERVISOR_PROMPT,
checkpointer=InMemorySaver(), # o checkpointer vai apenas no agente de nível superior
)
config = {"configurable": {"thread_id": "cs-1"}}
supervisor_agent.invoke(
{"messages": [HumanMessage(content="Onde está o meu pedido 12345?")]},
config,
)
follow_up = supervisor_agent.invoke(
{"messages": [HumanMessage(content="Quanto foi aquilo?")]},
config,
)
print(follow_up["messages"][-1].content)Saída:
Seu pedido 12345, os Fones de Ouvido Sem Fio, foi R$89.O supervisor lê corretamente aquilo no acompanhamento como o pedido 12345 da rodada anterior. O checkpointer funciona no supervisor da mesma maneira.
Não anexe um checkpointer aos agentes trabalhadores (
order_agent,refund_agent). Se você fizer isso, um trabalhador carregará os resultados de sua chamada anterior para a atual, o que pode interferir na tarefa em questão. Sem um, um trabalhador roda com base em nada além da solicitação do supervisor. Para subagentes, este é o padrão recomendado.
18.5) create_supervisor: A Forma Que Você Encontrará em Código Legado
Em bases de código existentes e tutoriais mais antigos, você vai se deparar com o auxiliar create_supervisor do pacote langgraph-supervisor. Dê a ele uma lista de agentes e um prompt, e ele constrói todo o grafo do supervisor para você.
from langgraph_supervisor import create_supervisor
from langchain_openai import ChatOpenAI
workflow = create_supervisor(
agents=[order_agent, refund_agent], # cada agente precisa ter um name definido
model=ChatOpenAI(model="gpt-5.4"),
prompt="Atribua as verificações de pedidos ao order_expert e os reembolsos ao refund_expert.",
)
app = workflow.compile()create_supervisor é um auxiliar que monta uma equipe de supervisor em uma única chamada de função (a abordagem de handoff de 18.3). Não o use em projetos novos, porém. É legado que o LangChain não recomenda mais, e internamente depende de create_react_agent, que foi descontinuado na v1 (a remoção está planejada para a v2). O LangChain recomenda o supervisor baseado em ferramentas que você aprendeu em 18.4.
Neste capítulo pegamos um agente que vivia dentro de um único grafo e o expandimos em uma equipe — dividida por domínio, orquestrada por um supervisor. Construímos a mesma equipe de três maneiras: o grafo manual com Command em 18.3, a abordagem de delegação por ferramenta em 18.4 e o auxiliar legado create_supervisor em 18.5. Para trabalho real, faça da abordagem de delegação por ferramenta seu padrão.