Blog Logo

Orquestração de agentes de IA em Go: tutorial passo a passo

Um agente de IA respondendo perguntas já não impressiona ninguém. O negócio fica interessante quando você precisa de vários trabalhando juntos: um que classifica o pedido, outro que pesquisa, um terceiro que escreve, e uma pessoa que dá o aval antes de publicar qualquer coisa. Coordenar tudo isso é o que se chama de orquestração de agentes, e é exatamente o que vamos montar neste tutorial, do zero e passo a passo, usando Go e a versão 2.0 do kit do Google.

Se você nunca ouviu falar em orquestração, relaxa: começamos pelo conceito, com diagramas incluídos. Se já tem o modelo mental, pule direto para o tutorial.

Logo do Agent Development Kit


O que é orquestrar agentes de IA (e por que importa)

Um agente de IA é, em essência, um LLM com instruções e ferramentas: entra texto, ele decide o que fazer e devolve um resultado. Os servidores MCP estão se consolidando como a forma padrão de conectar essas ferramentas. Um único agente funciona bem para tarefas delimitadas, mas fica curto quando o problema tem várias fases ou precisa de especialização.

Orquestrar agentes é definir como vários deles se coordenam para completar um trabalho: quem vai primeiro, quem recebe a saída de quem, quais decisões abrem um caminho ou outro, o que roda em paralelo e em quais pontos uma pessoa intervém. É a mesma ideia de uma orquestra: cada músico toca a sua parte, mas alguém precisa marcar o compasso.

Os quatro padrões básicos que cobrem 95% dos sistemas reais:

SEQUENCIAL          ROTEAMENTO          PARALELO            HUMAN-IN-THE-LOOP

A ──▶ B ──▶ C       A ──▶ tipo? ──┬─▶ B      ┌─▶ B ─┐          A ──▶ [pessoa] ──▶ B
                                  ├─▶ C      ├─▶ C ─┼─▶ E
                                  └─▶ D      └─▶ D ─┘

O problema é que, se você montar isso na mão em Go, acaba com goroutines e canais por todo lado, um switch gigante para decidir qual agente roda agora e, quando precisa que uma pessoa aprove algo no meio do caminho, um sistema caseiro de “pausa e retomada” com Redis e boa vontade. Funciona, mas é código de encanamento que não agrega nada ao seu produto.

É aí que entra a versão 2.0 do Agent Development Kit do Google para Go: ela traz um motor de workflows baseado em grafos como primitiva do próprio framework. Você modela o fluxo como um grafo de nós, declara as arestas, e o scheduler cuida do paralelismo, do roteamento e das pausas de human-in-the-loop. Vamos montar os quatro padrões do diagrama usando os exemplos oficiais de workflow.


Passo 1: preparar o ambiente

O módulo v2 tem seu próprio path de import, como manda a convenção do Go:

go get google.golang.org/adk/v2

A referência completa da API está em pkg.go.dev/google.golang.org/adk/v2. Workflows que usam apenas nós de função não precisam de mais nada. Os que chamam um LLM precisam de credenciais do Gemini: ou uma API key em GOOGLE_API_KEY, ou Vertex AI com Application Default Credentials (gcloud auth application-default login mais GOOGLE_GENAI_USE_VERTEXAI=true, GOOGLE_CLOUD_PROJECT e GOOGLE_CLOUD_LOCATION), conforme indica o README dos exemplos.

Para acompanhar o tutorial, clone o repositório com os exemplos:

git clone https://github.com/google/adk-go.git
cd adk-go

Passo 2: seu primeiro workflow sequencial

O motor vive no pacote google.golang.org/adk/v2/workflow. Todo workflow é um grafo dirigido: os nós são unidades de trabalho e as arestas definem quem entrega dados para quem. Segundo a documentação dos exemplos, existem cinco tipos de nó:

  • FunctionNode: qualquer função Go, com entrada/saída tipadas. O cavalo de batalha.
  • AgentNode: envolve um LlmAgent para que ele participe como mais um nó.
  • ToolNode: envolve um tool.Tool.
  • JoinNode: barreira de fan-in; espera todos os seus predecessores e entrega um map[nomeDoNó]output.
  • DynamicNode: orquestrador imperativo para quando você quer decidir em Go, em tempo de execução, quais filhos rodar.

O exemplo mais simples é o basic: dois FunctionNode encadeados com workflow.Chain. O primeiro coloca o texto do usuário em maiúsculas e o segundo adiciona um sufixo:

nodeA := workflow.NewFunctionNode("upper", upperFn, nodeConfig)
nodeB := workflow.NewFunctionNode("suffix", suffixFn, nodeConfig)

edges := workflow.Chain(workflow.Start, nodeA, nodeB)

myWorkflow, err := workflowagent.New(workflowagent.Config{
	Name:        "simple_sequence_workflow",
	Description: "Converts string to uppercase and appends a suffix",
	Edges:       edges,
})

O grafo é empacotado como um agente normal com workflowagent.New, então dá para servi-lo com o launcher de console que vem no próprio ADK sem escrever uma linha de servidor. Rode assim:

go run ./examples/workflow/basic/ console

Você tem um chat no terminal para testar o fluxo. Sem Docker, sem UI, sem nada — e sem API key: o basic não toca em nenhum LLM.


Passo 3: roteamento, ou como decidir o caminho com um LLM

O padrão sequencial quebra quando a fase seguinte depende do conteúdo: um e-mail furioso não se trata igual a uma dúvida técnica. É aí que entra o roteamento, e o exemplo routing/llm ensina a separação de responsabilidades correta: o LLM só classifica, e o motor faz o roteamento. Nada de “o modelo devolve JSON com o próximo passo e eu faço o parse e rezo”.

Um AgentNode com um LlmAgent classificador responde com exatamente uma palavra (question, exclamation ou statement). Depois, um nó emissor transforma essa palavra em rotas:

func routeByClassification(ctx agent.Context, input any, emit func(*session.Event) error) (any, error) {
	category := strings.TrimRight(strings.ToLower(strings.TrimSpace(fmt.Sprint(input))), ".")
	if category != "question" && category != "exclamation" && category != "statement" {
		category = "statement" // fallback defensivo se o LLM sair do roteiro
	}
	ev := session.NewEvent(ctx, ctx.InvocationID())
	ev.Routes = []string{category}
	if err := emit(ev); err != nil {
		return nil, err
	}
	return nil, nil
}

E as arestas declaram qual rota ativa cada destino:

edges := workflow.Concat(
	workflow.Chain(workflow.Start, classifyNode, routeNode),
	[]workflow.Edge{
		{From: routeNode, To: question, Route: workflow.StringRoute("question")},
		{From: routeNode, To: statement, Route: workflow.StringRoute("statement")},
		{From: routeNode, To: exclamation, Route: workflow.StringRoute("exclamation")},
	},
)

Existem StringRoute, IntRoute e MultiRoute para roteamento por valor. O detalhe-chave: se o LLM inventar uma categoria, sua função normaliza e cai no ramo padrão. O controle é seu, não do modelo. Uma observação do exemplo que vale copiar: registre os agentes LLM envolvidos no SubAgents da config do workflowagent, senão o runner solta um “Event from an unknown agent” a cada turno. Este exemplo sim precisa das credenciais do passo 1.


Passo 4: paralelismo com fan-out e fan-in

Quando várias tarefas não dependem entre si, rodá-las em série é jogar tempo fora. O exemplo complex monta uma pipeline de pesquisa: três agentes pesquisadores (energia renovável, veículos elétricos, captura de carbono) rodam em paralelo, um JoinNode espera os três, um FunctionNode formata os resultados e um agente de síntese escreve o relatório final. A fiação são quatro linhas:

eb := workflow.NewEdgeBuilder()
eb.AddFanOut(workflow.Start, renewableNode, electricVehicleNode, carbonNode)
eb.AddFanIn(gatherNode, renewableNode, electricVehicleNode, carbonNode)
eb.Add(gatherNode, formatNode)
eb.Add(formatNode, synthNode)

O JoinNode (workflow.NewJoinNode("gather")) dispara uma única vez, depois que todos os predecessores terminam, e entrega ao sucessor um map[string]any indexado pelo nome do nó. É por isso que o exemplo usa constantes para os nomes dos agentes: elas também são as chaves do mapa. Nada de sync.WaitGroup, canal de resultados ou select. Essa é exatamente a parte de encanamento que o motor tira das suas costas.

Isso é diferente de montar uma equipe de agentes autônomos com MiniMax Code, onde cada agente é um papel; aqui o workflow é um grafo dirigido com arestas explícitas.


Passo 5: human-in-the-loop, pausar para uma pessoa decidir

Último padrão, e o que mais me surpreendeu, porque costuma ser a primeira coisa que você acaba implementando na mão em qualquer sistema sério de agentes. No ADK 2.0, pausar um workflow é emitir um evento RequestInput e retornar ErrNodeInterrupted, como faz o exemplo hitl_simple:

ask := workflow.NewEmittingFunctionNode[any, any]("ask_name",
	func(ctx agent.Context, _ any, emit func(*session.Event) error) (any, error) {
		if err := emit(workflow.NewRequestInputEvent(ctx, session.RequestInput{
			InterruptID: "ask_name-" + uuid.NewString(),
			Message:     "What's your name?",
		})); err != nil {
			return nil, err
		}
		return nil, workflow.ErrNodeInterrupted
	},
	workflow.NodeConfig{},
)

Teste direto:

go run ./examples/workflow/hitl_simple/ console

O launcher de console mostra o prompt, a pessoa responde, e essa resposta é entregue ao próximo nó como sua entrada tipada. Repare no InterruptID com um UUID novo por requisição: o comentário do próprio exemplo avisa que reutilizar IDs pode fazer a Dev UI tratar um prompt posterior como já respondido. Detalhe pequeno que economiza uma tarde de depuração.

Antes de deixar um LLM tomar decisões críticas, vale documentar o contexto do sistema: um arquivo AGENTS.MD reduz alucinações e define regras de comportamento.

Se o seu caso é “um único nó que pergunta e se reexecuta com a resposta” em vez de passar para outro nó, o exemplo hitl_rerun usa ResumeOrRequestInput para esse padrão de reentrada. E para aprovações no meio de uma pipeline (publicar isso sim ou não, rodar esse deploy sim ou não), o padrão é o mesmo: um nó emissor de RequestInput entre o nó que prepara a ação e o que a executa.


Vindo do ADK 1.0? Os breaking changes que vão te afetar

O README-v2 documenta os breaking changes. Os dois que vão te afetar com certeza:

  1. session.NewEvent agora exige um context.Context como primeiro argumento: session.NewEvent(ctx, ctx.InvocationID()). O ID e o timestamp do evento vêm do pacote platform, o que permite aos motores de workflow produzir eventos determinísticos e reexecutáveis. Se você tinha um helper sem contexto, adicione o parâmetro e passe de cima para baixo; nada de context.Background() no meio da cadeia.
  2. ToolContext e CallbackContext foram fundidos em um único agent.Context (PR #945). Seus mocks de teste vão quebrar se implementavam a interface antiga. A saída confortável: embuta agent.StrictContextMock no seu fake, sobrescreva só o que o teste usa e pare de remendar mocks toda vez que a interface cresce. Métodos não sobrescritos dão panic com “not implemented”, que é exatamente o que você quer num teste.

Do zero ao workflow orquestrado: recapitulando

Agora você tem o modelo mental completo: orquestrar é definir um grafo de agentes e passos, e os quatro padrões que você precisa são cadeia, roteamento, paralelo e pausa humana. Com o ADK Go 2.0 cada padrão são poucas linhas declarativas, e o scheduler engole o encanamento. Se o que você procura é um agente autônomo que trabalhe fora de um grafo fixo, a configuração óptima do OpenClaw é um bom complemento a este tutorial.

Se o seu sistema cabe numa cadeia linear, um Chain com três FunctionNode e pronto — você não precisa de mais nada. Onde o motor de grafos começa a compensar é quando aparece qualquer uma destas três coisas: decisões que dependem da saída de um LLM, trabalho que pode rodar em paralelo, ou uma pessoa que precisa aprovar algo antes de continuar. Nesse ponto, a alternativa é escrever o scheduler você mesmo, e acredite: você não quer dar manutenção num scheduler.

Tudo o que você viu roda local com um go run, e a maioria dos exemplos (7 dos 10 do repositório) nem precisa de API key. A ordem que eu seguiria para aprender: basic, depois hitl_simple, em seguida routing/llm e por fim complex. Numa tarde você sai de não saber o que é orquestração para ter um workflow multiagente funcionando na sua máquina.

Perguntas frequentes

O que é orquestração de agentes de IA?

É coordenar vários agentes de IA para completar um trabalho: definir a ordem de execução, quem recebe a saída de quem, quais decisões abrem caminhos diferentes, o que roda em paralelo e em quais pontos uma pessoa intervém. Costuma ser modelada como um grafo de nós (passos) e arestas (fluxo).

O que preciso para seguir este tutorial?

Go instalado e o repositório google/adk-go clonado. Os exemplos basic, hitl_simple, routing/string, routing/int, hitl_rerun e os de dynamic não precisam de API key. Para routing/llm e complex você precisa de credenciais do Gemini (GOOGLE_API_KEY) ou Vertex AI.

Como funciona o human-in-the-loop no ADK Go 2.0?

Um nó emissor envia um evento RequestInput e retorna workflow.ErrNodeInterrupted. O launcher mostra o prompt, a pessoa responde e a resposta tipada chega ao próximo nó como entrada. Assim é possível modelar aprovações ou perguntas interativas.


O que você achou?

Deixe sua opinião, pergunta ou sugestão. Os comentários são sincronizados com GitHub Discussions .

Voltar ao blog