mozak.tech Engenharia de IA Corporativa 95%

Parte V — Projetando Sistemas Inteligentes para Produção

5.3 — Colaboração entre Agentes: Supervisor e Transferência de Controle (Multi-agent)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Comparar Sequential, Parallel, Supervisor e Handoff patterns

Implementar supervisorPattern() — Supervisor decide qual agente delegar e quando parar

Implementar handoffPattern() — Agente passa controle quando está fora do domínio

Completar os 2 TODOs do starter TypeScript

Escolher o padrão certo para cada tipo de problema

Por que isso importa

Sequential e Parallel (já implementados) são simples mas inflexíveis. Supervisor adiciona tomada de decisão dinâmica: o sistema decide em runtime qual agente chamar, não na hora do design. Handoff resolve o problema de um agente generalista saber que precisa de um especialista — como um médico clínico geral encaminhando para um cardiologista.

Conceitos Fundamentais

TODO 1: Supervisor Pattern

O Supervisor usa tool_use para delegar para sub-agentes especializados:

async function supervisorPattern(tarefa: string): Promise<string> {

  const SUPERVISOR_TOOLS: Anthropic.Tool[] = [

    {

      name: 'delegar_para_pesquisador',

      description: 'Coleta fatos e dados sobre o tópico.',

      input_schema: { type: 'object' as const, properties: { topico: { type: 'string' } }, required: ['topico'] },

    },

    {

      name: 'delegar_para_analista',

      description: 'Analisa dados e identifica padrões.',

      input_schema: { type: 'object' as const, properties: { dados: { type: 'string' } }, required: ['dados'] },

    },

    {

      name: 'delegar_para_redator',

      description: 'Escreve relatório final com os insights.',

      input_schema: { type: 'object' as const, properties: { analise: { type: 'string' } }, required: ['analise'] },

    },

    {

      name: 'finalizar',

      description: 'Encerra o processo quando o resultado final está pronto.',

      input_schema: { type: 'object' as const, properties: { resultado: { type: 'string' } }, required: ['resultado'] },

    },

  ]

  

  const mensagens: Anthropic.MessageParam[] = [{

    role: 'user',

    content: `Coordene os agentes para completar esta tarefa: ${tarefa}`,

  }]

  

  while (true) {

    const response = await client.messages.create({

      model: 'claude-haiku-4-5-20251001',

      max_tokens: 500,

      system: 'Você é um supervisor de agentes. Delegue para os agentes especializados na ordem certa e finalize quando tiver o resultado completo.',

      tools: SUPERVISOR_TOOLS,

      messages: mensagens,

    })

    

    mensagens.push({ role: 'assistant', content: response.content })

    

    if (response.stop_reason === 'end_turn') break

    

    const resultados: Anthropic.ToolResultBlockParam[] = []

    

    for (const bloco of response.content) {

      if (bloco.type !== 'tool_use') continue

      

      const args = bloco.input as Record<string, string>

      let resultado: string

      

      if (bloco.name === 'finalizar') {

        resultados.push({ type: 'tool_result', tool_use_id: bloco.id, content: 'Finalizado.' })

        mensagens.push({ role: 'user', content: resultados })

        return args.resultado

      }

      

      // Chamar sub-agente especializado

      const subAgentSystem = bloco.name === 'delegar_para_pesquisador'

        ? 'Você é um pesquisador. Colete fatos relevantes de forma concisa.'

        : bloco.name === 'delegar_para_analista'

        ? 'Você é um analista. Identifique padrões e insights dos dados.'

        : 'Você é um redator executivo. Escreva de forma clara e concisa.'

      

      const subInput = Object.values(args)[0]

      const subResponse = await chamarLLM(subInput, subAgentSystem)

      

      console.log(`  [${bloco.name}] concluído`)

      resultados.push({ type: 'tool_result', tool_use_id: bloco.id, content: subResponse })

    }

    

    mensagens.push({ role: 'user', content: resultados })

  }

  

  return 'Supervisor encerrou sem resultado'

}

TODO 2: Handoff Pattern

async function handoffPattern(tarefa: string): Promise<string> {

  const estado: EstadoHandoff = {

    historico: [],

    contexto: { tarefa_original: tarefa },

    agenteAtual: 'generalista',

  }

  

  const AGENTES: Record<string, string> = {

    generalista: 'Você é um assistente geral. Se a tarefa exigir expertise em direito, finanças ou tecnologia, use a tag [HANDOFF:juridico|financeiro|tecnico] no início da resposta.',

    juridico: 'Você é um especialista jurídico. Analise aspectos legais e regulatórios.',

    financeiro: 'Você é um especialista financeiro. Analise métricas, ROI e impacto financeiro.',

    tecnico: 'Você é um especialista técnico. Analise aspectos de arquitetura e implementação.',

  }

  

  for (let i = 0; i < 5; i++) {  // max 5 handoffs

    const agente = estado.agenteAtual

    const historico_str = estado.historico.join('\n')

    

    const prompt = i === 0

      ? tarefa

      : `Contexto acumulado:\n${historico_str}\n\nContinue a análise de ${agente}.`

    

    const resposta = await chamarLLM(prompt, AGENTES[agente])

    estado.historico.push(`[${agente}]: ${resposta}`)

    

    // Detectar handoff request

    const handoffMatch = resposta.match(/\[HANDOFF:(juridico|financeiro|tecnico)\]/)

    if (handoffMatch) {

      const proximo = handoffMatch[1]

      console.log(`  [HANDOFF] ${agente} → ${proximo}`)

      estado.agenteAtual = proximo

      estado.contexto.handoff_de = agente

      continue

    }

    

    // Sem handoff = resultado final

    return resposta

  }

  

  return estado.historico[estado.historico.length - 1]

}

Aprofundamento Técnico

Supervisor vs Parallel: Quando Usar

Critério

Parallel

Supervisor

Ordem de execução conhecida

Sim → Parallel

Não → Supervisor

Sub-tarefa B depende de A

Não funciona

Funciona

Quantidade de agentes

Fixo

Dinâmico

Debugging

Simples

Complexo

Supervisor tem overhead de latência (mais LLM calls). Use somente quando a ordem de execução depende do output de agentes anteriores.

Handoff vs Supervisor: Diferença Chave

Supervisor: coordenador externo decide quem age

Handoff: o próprio agente decide passar o controle

Handoff é mais autônomo. Supervisor tem mais controle.

Exemplos Anotados

Exemplo 1: Supervisor Flow

Tarefa: "Adotar IA para atendimento ao cliente"



Supervisor → delegar_para_pesquisador("IA atendimento cliente")

Pesquisador → "Casos de uso: chatbot FAQ, triagem de tickets..."



Supervisor → delegar_para_analista("Casos de uso: chatbot FAQ...")

Analista → "Oportunidades: -40% custo operacional. Riscos: qualidade..."



Supervisor → delegar_para_redator("Oportunidades: -40%...")

Redator → "Recomendação: implementar IA em fases..."



Supervisor → finalizar("Recomendação: implementar IA...")

→ Resultado final entregue

Padrões e Armadilhas

Padrões

Padrão 1: Sub-agentes com system prompts focados Cada sub-agente tem um system prompt que o confina ao seu domínio. Agente de pesquisa que começa a analisar está fora do papel.

Padrão 2: Estado compartilhado via EstadoHandoff

estado.contexto[chave] = valor  // persiste entre handoffs

Armadilhas

⚠️ Armadilha 1: Supervisor recursivo infinito Se o Supervisor chamar delegar_para_pesquisador indefinidamente, coloque limite de iterações.

⚠️ Armadilha 2: Handoff sem contexto

Agente Jurídico recebe apenas "continue" sem o contexto anterior

→ repete análise do zero

Sempre passe historico_str no prompt do agente receptor.

⚠️ Armadilha 3: Perda de tool_use_id

// Para cada bloco de tool_use, criar tool_result correspondente com MESMO id

resultados.push({ type: 'tool_result', tool_use_id: bloco.id, content: resultado })
⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

TODO 1 — supervisorPattern(tarefa): crie 4 tools para o Supervisor (delegar_para_pesquisador, delegar_para_analista, delegar_para_redator, finalizar). Loop agent com sub-agentes especializados via chamarLLM.

TODO 2 — handoffPattern(tarefa): generalista detecta [HANDOFF:tipo] em sua resposta, atualiza estado.agenteAtual, passa histórico acumulado para o próximo agente.

Agora você está pronto para o lab.