1. Visão geral
O título do ADK 2 é três padrões de orquestração. Este codelab ensina os três conceitos criando um app, o Marathon Race Day Coach, uma etapa executável por vez. Cada nível responde a uma única pergunta, adiciona uma ideia e é executado por conta própria.
O que você vai aprender
- Fluxos de trabalho de gráficos (pilar 1): quando você pode desenhar o fluxo antes da chegada da entrada.
- Agentes colaborativos (pilar 2): quando você conhece a equipe, mas a solicitação escolhe o subconjunto, e todos os três modos de colaboração (
chat/task/single_turn) são executados ao vivo. - Fluxos de trabalho dinâmicos (pilar 3): quando o formato do trabalho em si depende da entrada.
- Como escolher: uma árvore de decisão com uma pergunta e como os padrões são compostos.
O tema central
estrutura conhecida → equipe / subconjunto de variáveis conhecido → forma desconhecida → escolha a certa

O que você vai criar
Um app, o Marathon Race Day Coach, montou um nível executável por vez. Cada nível é um módulo Python simples que você executa no terminal. No L5, as peças abaixo são todas suas.
A imagem é extraída do código em execução: cada linha contínua foi lida de Workflow.graph.edges. Essa é a primeira lição: as partes que podem ser criadas com antecedência são exatamente o Pilar 1, e as partes que não podem são o motivo da existência dos Pilares 2 e 3.

O que é necessário
- Uma Conta do Google (para o Colab): não é necessário fazer configuração local.
- 50 minutos (os dois níveis L4 são os mais longos. Planeje o tempo necessário).
- Uma das duas maneiras de acessar um modelo do Gemini. Escolha seu caminho: execute uma etapa de configuração e pule a outra:
🎓 Workshop | 🏠 Para levar | |
Quem | Você está em um workshop presencial e o instrutor deu um link para reivindicar crédito | Todas as outras pessoas, incluindo os participantes do workshop, depois |
Você precisa | O link de reivindicação e uma Conta do Google que pode criar um projeto na nuvem | Uma chave de API do AI Studio sem custo financeiro |
Executado em | Vertex AI, em um projeto faturado com seu crédito do workshop | Google AI Studio |
Custo | Coberto pelo crédito | Nível sem custo financeiro |
Etapa de configuração | Configuração do workshop (próxima etapa) | Configuração para levar para casa (a próxima etapa) |
Tudo do prólogo em diante é idêntico de qualquer maneira. A faixa só decide com qual endpoint de modelo o notebook se comunica.
Duas maneiras de acompanhar
Cada etapa abaixo é mapeada para uma célula no notebook do Colab e uma pasta no repositório do GitHub. Escolha uma das opções:
- ▶ Colab (recomendado) : abra o notebook → execute as células de cima para baixo.
- 💻 Local :
git cloneo repositório,./setup_venv.she execute cada nível como um módulo (python -m ...) ou navegue por todos eles com./run.sh(adk web).
2. Configuração do workshop: reivindique seu crédito e mude para a Vertex AI
No workshop, você recebe créditos do Google Cloud. Você vai reivindicá-lo, criar um projeto faturado para ele e apontar o notebook para a Vertex AI em vez do AI Studio. Uma célula faz tudo depois da reivindicação.
1 · Resgate seu crédito (~1 min)
- Abra o link de reivindicação que o professor compartilhou. O caminho é parecido com
https://me.developers.google.com/benefits/claim/your-workshop-name. - Faça login e siga as instruções na página para aceitar o crédito.
- Anote qual Conta do Google você usou. Todas as etapas abaixo precisam ser executadas com essa mesma conta.
2 · Abra o notebook e instale o ADK 2 (~1 min)
Clique em Abrir no Colab ▶ e execute a primeira célula de código. Ele fixa a versão exata do ADK 2 em que este codelab foi verificado e imprime ✓ installed.
3 · Execute a célula "Configuração do workshop" (cerca de 3 minutos)
Essa é a célula intitulada 🎓 Programa A: workshop. Execute o comando e o Colab vai pedir autorização. Escolha a mesma Conta do Google que você usou para reivindicar o crédito e permita o acesso.
Ele faz quatro coisas: cria um projeto chamado adk-2-tutorial-XXXX no seu crédito, ativa a API Vertex AI nele, define as quatro variáveis de ambiente que cada célula posterior lê e, em seguida, faz uma ligação de teste para a Vertex e espera até que ela responda. Assim, a configuração termina de funcionar ou informa o motivo, em vez de falhar mais tarde em um nível.
Saída esperada: a última linha é o que importa:
Signed in as: you@example.com
...
Successfully created GCP project 'adk-2-tutorial-4817'.
Successfully linked 'adk-2-tutorial-4817' to billing account '01ABCD-...'.
waiting for Vertex AI to come up on the new project... (10s)
waiting for Vertex AI to come up on the new project... (20s)
✅ Vertex AI on adk-2-tutorial-4817 · us-central1 · gemini-2.5-flash — answered a test call
4 · Pule a etapa "Configuração para levar para casa"
Não execute a célula de chave do AI Studio. Isso faria com que o notebook voltasse para o AI Studio e desfaria o que você acabou de fazer. A célula evita isso e se recusa a ser executada, mas a ação mais organizada é simplesmente pular. Vá direto para a célula Blocos de construção compartilhados.
5 · Execute a célula "Elementos básicos compartilhados"
Execute uma vez. Ele define os esquemas do Pydantic e os cenários de maratona predefinidos que todos os níveis do L2 em diante reutilizam. Você vai ver ✓ schemas + scenarios ready.
Após o workshop
Seu crédito e o projeto criado por ele não vão durar para sempre. Para continuar executando esses níveis sem custos financeiros depois que o workshop terminar, siga a etapa Configuração para levar para casa. Ela oferece uma chave sem custo financeiro do AI Studio, sem projeto na nuvem nem faturamento. Essa célula é a única coisa que muda.
Para fazer a limpeza antes, abra o console do Cloud, selecione adk-2-tutorial-XXXX e exclua. Nada mais neste codelab cria recursos faturáveis.
3. Configuração para levar para casa · Chave de API do AI Studio
Tudo neste caminho é executado com uma chave de API sem custo financeiro do Google AI Studio. Não é necessário ter um projeto na nuvem do Google Cloud, faturamento ou instalação local. Toda essa etapa leva cerca de três minutos.
1 · Abra o notebook
Clique em Abrir no Colab ▶. Você vai acessar o notebook, que tem uma introdução em Markdown e uma célula executável por nível. Você executa as células de cima para baixo, e cada uma imprime a própria saída logo abaixo.
2 · Instalar o ADK 2 (~1 min)
Execute a primeira célula de código. Ele fixa a versão exata em que este codelab foi verificado:
%pip install -q "google-adk==2.3.0" python-dotenv pydantic nest_asyncio
Aguarde a conclusão. Você vai ver ✓ installed. A instalação leva de 30 a 60 segundos na primeira vez. Depois disso, ela é armazenada em cache.
3 · Receba sua chave da API Gemini no AI Studio (~1 minuto)
- Abra aistudio.google.com/app/apikey em uma nova guia do navegador.
- Faça login usando sua Conta do Google.
- Clique em Criar chave de API (canto superior direito).
- Escolha um projeto do Google ou deixe que ele crie um.
- Copie a chave. Ela começa com
AIza...e tem cerca de 40 caracteres.
4 · Adicione sua chave ao Colab (~1 minuto)
Opção A: Colab Secrets (recomendada; a chave fica oculta)
- Clique no ícone de chave 🔑 na barra lateral esquerda do Colab.
- Clique em + Adicionar novo secret.
- Defina o Nome como exatamente
GOOGLE_API_KEY. - Cole a chave em Valor.
- Ative a opção Acesso ao notebook.
Opção B: colar quando solicitado (rápido): pule o secret. Quando você executar a próxima célula, um prompt oculto 🔑 Enter your Google AI Studio API key: vai aparecer. Cole e pressione "Enter".
5 · Execute a célula principal
Ele lê o secret (ou volta ao comando de colar) e aponta o ADK para o AI Studio (não a Vertex AI):
import os
# 🏠 TAKE-HOME ONLY — if you ran the Workshop setup cell, skip this one.
if os.environ.get("GOOGLE_GENAI_USE_VERTEXAI") == "True":
raise SystemExit("✋ You're set up on the workshop path (Vertex AI). Skip this cell.")
# Google AI Studio API key — add GOOGLE_API_KEY in the 🔑 Secrets panel (or paste when prompted).
try:
from google.colab import userdata
key = userdata.get("GOOGLE_API_KEY")
except Exception:
import getpass
key = getpass.getpass("Enter your Google AI Studio API key: ")
os.environ["GOOGLE_API_KEY"] = "".join(key.split()) # drop any stray whitespace/newlines
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False" # use AI Studio, not Vertex AI
print("✅ API key set — using Google AI Studio.")
Resposta esperada: ✅ API key set — using Google AI Studio.
6 · Execute a célula "Elementos básicos compartilhados"
Execute a célula Elementos básicos compartilhados uma vez. Ele define os esquemas do Pydantic e os cenários de maratona predefinidos que todos os níveis do L2 em diante reutilizam. Você vai ver ✓ schemas + scenarios ready.
Tudo pronto! 🎽 Uma pequena pausa antes do L0, a versão que todo mundo cria primeiro.
4. Prólogo · Por que não um comando grande?
⚡ Antes de executar, defina UMA coisa para observar: de onde vem cada número específico? Esse é o exercício completo. Todo o resto é decoração.
Antes da escada, execute o que ela substitui: um agente cuja solicitação promete tudo: buscar a previsão do tempo, analisar o curso, ler o registro de treinamento, criar rotas por condições e gerar o plano.
O que você vai ver:uma estratégia confiante, específica e bem formatada... cujos números são inventados. Em uma execução ao vivo, ele começou com "Eu extraí as métricas de clima de hoje" e informou 11 °C, um vento de 14 km/h e uma análise de um registro de treinamento que nunca tinha visto. Não há API de clima, dados de curso nem registro. Uma chamada de modelo opaco fabrica as entradas ou as torna inúteis.
Essa é a doença, e ela tem quatro sintomas que valem a pena mencionar:
- Não é possível confiar nele: os dados são inventados, mas de forma fluente.
- Não é possível testar: o roteamento da etapa 4 fica dentro da prosa. Não há
ifpara teste de unidade. - Não é possível trocar uma etapa: não há uma junção em que uma API de clima real possa ser conectada.
- Você paga por tudo, sempre: cinco etapas, uma chamada gigante, sem armazenamento em cache de uma parte determinística.
Mantenha essa sensação. Os nove níveis seguintes removem essas etapas do comando, uma de cada vez: busca de funções (L1–L2a), rotas de uma instrução if (L2b), especialistas dividem o trabalho (L3a–L3b) e o código limita a forma (L4a–L4b).

💻 Local: python -m shared.prologue
5. L0 · Seu primeiro agente do ADK 2

⚡ Resumo:um agente é um modelo + uma instrução + ferramentas que ele pode chamar; um Runner executa isso. Tudo depois desse nível é apenas mais agentes, organizados em formas melhores.
A pergunta:é possível fazer um modelo responder e usar código real quando a aritmética é importante?
A ideia única — três partes:
Agent: o que faz o raciocínio (um modelo do Gemini + uma instrução).Runner: o que executa um agente em uma sessão e transmite eventos.- uma ferramenta: uma função Python simples (
pace_splits) que o modelo decide chamar. O ADK lê a assinatura e a docstring e entrega uma declaração ao modelo, sem escrever esquemas.
Depois do prólogo, este é o primeiro reparo: um LLM que faz aritmética de ritmo na cabeça pode errar. pace_splits é Python determinístico, então os números na resposta são calculados, não improvisados.
▶ Colab:execute a célula L0 · 📁 GitHub: L0_first_agent/ · 💻 Local: python -m L0_first_agent.agent

def pace_splits(target_finish: str) -> dict:
"""Convert a goal time like '3:30:00' into exact per-mile / per-km paces."""
... # deterministic Python — no LLM
pace_coach = Agent(
name="pace_coach", model=MODEL,
tools=[pace_splits], # the model may call it; ADK reads the signature
instruction="You are a friendly, concise marathon coach. ... If the runner "
"mentions a goal time, call pace_splits — never do arithmetic yourself.",
)
runner = Runner(node=pace_coach, session_service=InMemorySessionService(), auto_create_session=True)
async for event in runner.run_async(user_id="u1", session_id="s1", new_message=msg):
... # events carry the model's text
🔍 Os marcadores : Agent(...) · tools=[pace_splits] · Runner(...). E na saída, a linha 🔧 — é o modelo decidindo, no meio da resposta, chamar seu código.
O que você vai ver:
🔧 model called tool → pace_splits({'target_finish': '3:30:00'})
🔧 tool returned → {'per_mile': '8:00', 'per_km': '4:58', ...}
🧠 Coach: To finish in 3:30:00, you need an average pace of 8:00 per mile...
As linhas 🔧 são a lição: no meio da resposta, o modelo escolheu chamar sua função, e o 8:00/mile exato na resposta veio do seu código, não das estatísticas de token.
❓ Talvez você esteja se perguntando: o modelo sempre chama a ferramenta? Não, ele decide por pergunta. Pergunte algo sem números e as linhas 🔧 vão desaparecer (o playground pede para você fazer exatamente isso).
👀 Leia: pace_splits (uma função simples) e a linha tools=[pace_splits]. · ▶ Execute. · ✏️ Mudança:faça a pergunta geral (sem tempo de meta). Observe que as linhas 🔧 desaparecem: o modelo decide quando vale a pena chamar uma ferramenta. Em seguida, reescreva o instruction e execute novamente. A instrução é o restante do programa.
6. L1 · Seu primeiro fluxo de trabalho

⚡ Resumo:uma função simples e um agente de LLM são o mesmo tipo de nó. Trabalho previsível → função (0 LLM, determinística); raciocínio → agente.
A pergunta:como misturar código simples e um LLM em um fluxo sem pagar por uma chamada de modelo nas partes que são apenas código?
A ideia:em um Workflow, uma função simples do Python e um agente de LLM são apenas nós na mesma lista edges.
START ──► fetch_conditions (function, 0 LLM) ──► advise (agent, 1 LLM)
▶ Colab:execute a célula L1 · 📁 GitHub: L1_graph_basics/ · 💻 Local: python -m L1_graph_basics.workflow

O nó de função imprime os dados que produziu (sem chamada de modelo). Em seguida, o agente dá conselhos que fazem referência à temperatura e ao vento reais que recebeu:
def fetch_conditions(node_input): # function node — 0 LLM
return Event(output=Conditions(temp_f=78, wind_mph=12, conditions="sunny").model_dump())
advise = Agent(name="advise", model=MODEL, mode="single_turn",
input_schema=Conditions, instruction="...give pacing + gear advice...")
workflow = Workflow(edges=[(START, fetch_conditions, advise)])
🔍 Os marcadores:uma tupla de aresta — (START, fetch_conditions, advise) — com uma função Python simples no meio dela e input_schema= validando a transferência.
Novidades em relação ao L0 : Workflow(edges=[...]), START (onde a entrada é inserida), um nó de função que retorna Event(output=...) e input_schema=Conditions para que a saída da função seja validada em relação a esse esquema antes de o agente vê-la (como texto JSON — input_schema valida o limite, não entrega um objeto Python ao agente).
❓ Talvez você esteja se perguntando: a ordem "função-agente" é obrigatória? Não. Qualquer ordem, qualquer mistura, qualquer contagem. advise é executado em segundo lugar apenas porque precisa dos dados de fetch_conditions. A lição é a igualdade, não a sequência.
👀 Leitura: fetch_conditions retorna dados sem chamada de modelo; advise tem input_schema=Conditions. · ▶ Execute. · ✏️ Mudança:defina temp_f=30 na função e execute novamente. A recomendação muda, e a função ainda custa 0 chamadas de LLM.
7. L2a · Distribuição de dados paralela + JoinNode (Pilar 1a)

⚡ Resumo:ramifique em paralelo (sem custo financeiro), aguarde todos, agrupe e entregue a um agente o panorama completo.
A pergunta:você pode desenhar o fluxo antes que a entrada chegue. Comece com o esqueleto: colete dados em paralelo, agrupe-os e entregue a um agente.
O formato:
START ──► fetch_weather ──┐
START ──► analyze_course ─┼─► JoinNode ─► strategy (1 agent)
START ──► pull_fitness ───┘ (bundles)
▶ Colab:execute a célula L2a · 📁 GitHub: L2a_parallel_join/ · 💻 Local: python -m L2a_parallel_join.workflow

🔍 Os marcadores:três bordas que começam em START (o é a distribuição de dados) e JoinNode, o ponto de encontro.
- As três buscas são funções que são executadas em paralelo, sem chamadas de LLM.
JoinNodeaguarda os três e os agrupa em um payload tipado (BundledRunData), com chave pelo nome da função.- Um agente
strategylê o pacote e grava umRaceStrategy.
O que você vai ver:cada busca imprime um carimbo de data/hora started / finished. Todos os três começam em 0,0s e a distribuição de dados termina em 2,0s — a busca mais lenta, não os 4,5s que as durações somariam. Essa sobreposição é o paralelismo. O tempo decorrido total impresso no final é de aproximadamente 8 segundos porque também contém a chamada de LLM do agente de estratégia. Leia os carimbos de data/hora de busca para a declaração paralela, não o total.
💡 Callback do prólogo:o mega-comando inventou o clima. Aqui, a temperatura vem de uma função de busca: código real, junção real. Troque o dicionário predefinido por uma API de clima real e nada mais vai mudar.
❓ Você deve estar se perguntando: quanto
JoinNode
preciso entender? Uma frase: ele aguarda até que cada ramificação paralela seja concluída, empacota as saídas em um dicionário com chave pelo nome da função upstream e não calcula nada por conta própria. É exatamente por isso que o roteador do L2b pode gravar node_input["fetch_weather"]["temp_f"].
👀 Leitura:três arestas se abrem em leque a partir de START; JoinNode as agrupa para um agente. · ▶ Execute e leia os carimbos de data/hora, não o total. · ✏️ Mudança:faça uma busca para suspensão 3.0. Primeiro, preveja o novo horário de término da distribuição de dados e depois verifique.
8. L2b: adicione o roteador determinístico (pilar 1b)

⚡ Resumo:L2a sem alterações + um if simples decide qual um agente será executado. Ramificação sem perguntar ao modelo.
A pergunta:o plano deve ser diferente para clima quente e frio. Como você cria ramificações sem pedir que o modelo decida?
A forma (L2a + um roteador):
... JoinNode ─► route_by_weather ─► hot_strategy
(if-statement) ─► normal_strategy
─► cold_strategy
▶ Colab:execute a célula L2b. Tente run("NORMAL") / run("COLD") · 📁 GitHub: L2b_router/ · 💻 Local: python -m L2b_router.workflow COLD

def route_by_weather(node_input): # an if-statement, 0 LLM
temp = node_input["fetch_weather"]["temp_f"]
route = "HOT" if temp >= 70 else "COLD" if temp <= 40 else "NORMAL"
return Event(output=node_input, route=route)
(route_by_weather, {"HOT": hot_strategy, "NORMAL": normal_strategy, "COLD": cold_strategy})
🔍 Os marcadores : Event(output=..., route=...), um nó de função nomeando o caminho, e a aresta de dicionário {"HOT": ..., "NORMAL": ..., "COLD": ...} que mapeia nomes para nós.
A conclusão: três tipos de trabalho, três casas:
- Trabalho previsível → funções (as três buscas paralelas)
- Uma regra clara → roteamento explícito (
route_by_weatheré uma instruçãoif, não uma decisão do modelo) - Raciocínio → o modelo (exatamente um agente de estratégia em execução)
O que você vai ver : temp=78F -> route=HOT e um RaceStrategy estruturado. Custo líquido: uma chamada de LLM.
⚠️ Se você adicionar uma quarta ramificação,também dê ao dicionário de rotas uma entrada DEFAULT_ROUTE. Uma rota que não corresponde ao dicionário não é um erro. A ramificação simplesmente termina, e o programa sai 0 sem saída, o que é um beco sem saída confuso para depurar.
❓ Você talvez esteja se perguntando: então o L2b é literalmente o L2a mais um roteador? Sim, as buscas e a junção não são alteradas, e ainda é exatamente uma chamada de LLM. O que mudou: "sempre o mesmo agente" passou a ser "um de três, escolhido por dados".
👀 Leia : route_by_weather — o roteador é uma instrução if, não um agente. · ▶ Executar run("COLD") também. · ✏️ Mudança:adicione uma ramificação WINDY com um quarto agente e leia o aviso DEFAULT_ROUTE acima antes de fazer isso.
9. L3a · Agentes colaborativos: uma flag, dois mundos — Pilar 2

⚡ TL;DR:mesma equipe, uma flag. O chat entrega a conversa inteira a um especialista e nunca mais volta. O single_turn transforma cada especialista em uma ferramenta: subconjunto paralelo, retorno automático, uma síntese.
A pergunta:você conhece a equipe, mas a solicitação decide quais membros devem responder. Como deixar um LLM escolher o subconjunto e executá-los simultaneamente?
O formato:um coordenador de seis especialistas (médico, clima, ritmo, equipamentos, nutrição, mental). Nesse nível, a mesma equipe é executada duas vezes: o mesmo comando do coordenador e os mesmos seis especialistas. A única diferença é uma flag nos subagentes. O contraste é a lição.
▶ Colab:execute a célula L3a · 📁 GitHub: L3a_collaborative/ · 💻 Local: python -m L3a_collaborative.concierge --mode chat "What about fueling?"

🔍 Os marcadores : mode="single_turn" na fábrica e na saída, TRANSFER → (tempo 1) versus um conjunto de linhas DISPATCH → compartilhando um carimbo de data/hora (tempo 2).
Beat 1 · Run the default first — and watch it fail the job
Nenhum mode= escrito → os subagentes usam o padrão chat. O que você verá:
TRANSFER → nutrition_specialist (transfer_to_agent — the only tool chat subagents provide)
Final speaker: nutrition_specialist
O coordenador não tem ferramentas de delegação. Os subagentes de chat só oferecem transfer_to_agent, uma transferência serial de toda a conversa para um especialista. Esse especialista responde diretamente ao usuário, e a execução termina ali. Sem envio paralelo. Sem devolução. Nenhuma síntese. Faça a pergunta ampla e a situação piora: seis especialistas, uma transferência.
Isso não é um bug, é o modo de chat fazendo o trabalho dele. A conversa pertence a quem a mantém, até que alguém a transfira explicitamente. Certo para um assistente aberto, errado para uma etapa de pipeline.
Tema 2 · Uma bandeira, dois mundos
A única diferença: mode="single_turn" em cada especialista. Mesma pergunta, execute novamente:
[t= 7.8s] DISPATCH → medical_specialist ← same timestamp =
[t= 7.8s] DISPATCH → weather_specialist one turn, many calls
[t=14.5s] ↩ medical_specialist replied ← replies land inside
[t=14.5s] ↩ weather_specialist replied one short window
🧠 Concierge (synthesized): <one answer>
Agora, o ADK injeta uma ferramenta de delegação por especialista, nomeada de acordo com o subagente e descrita pelo description=. Esse texto é o que o coordenador lê ao escolher o subconjunto. Pule essa parte e você estará roteando apenas com base nos nomes. O coordenador emite várias chamadas em uma rodada, o ADK as executa em paralelo, cada uma retorna automaticamente o resultado, e o coordenador sintetiza.
Pergunta | Especialistas que disparam |
"E o combustível?" | somente nutrição |
"Meu joelho dói no quilômetro 29" | somente médico |
"Devo correr hoje?" | medical + weather + pacing |
"Preciso me preocupar com alguma coisa?" | todos os 6 |
Por que cada especialista recebe o briefing completo:cada subagente single_turn é executado na própria ramificação de sessão isolada. Ele não pode ver a conversa nem os colegas. Nada é ambiente: o coordenador precisa encaminhar o SpecialistInput (pergunta + estratégia + dados do executor) inteiro separadamente para cada chamada paralela.
💡 Onde o ADK 2 oferece um lar direto:um LLM escolhe um subconjunto por solicitação E o executa em paralelo, declarado via sub_agents + mode="single_turn". Você pode montar a mesma forma no 1.x envolvendo cada especialista em AgentTool. O que muda é que agora é uma declaração em vez de encanamento. (ParallelAgent é sempre "all" e transfer_to_agent é serial.)
⚠️ Duas observações importantes: (1) o modelo escolhe o subconjunto, então ele é menos determinista do que o roteador codificado do L2. O subconjunto exato pode variar de execução para execução. (2) Às vezes, você vai ver uma linha Error validating input: ... para um especialista. Quase nunca é a saída do especialista. O output_schema faz com que o Gemini aplique isso no lado do servidor. É a entrada: o coordenador precisa reproduzir o SpecialistInput aninhado inteiro de forma literal para cada chamada paralela e, às vezes, ele erra uma. O ADK retorna o erro como resultado da ferramenta, o coordenador se recupera e a síntese ainda é realizada.
❓ Você deve estar se perguntando: é
chat
apenas uma delegação no estilo 1.x — um agente por vez? Sim, é o comportamento padrão da versão 1.x, agora com um nome. A diferença para single_turn é tridimensional: o que o coordenador tem (um transfer_to_agent x uma ferramenta por especialista), quantos podem trabalhar (um, dono da conversa x N em paralelo) e se o controle retorna (nunca x automaticamente, com resultados). Sobre o código:a ramificação if mode == da fábrica existe apenas para que uma equipe possa ser criada de ambas as maneiras para esse contraste. Um app real codifica um modo e o if desaparece.
👀 Leia:a fábrica _specialist. O parâmetro mode é o nível inteiro. · ▶ Execute os dois tempos. · ✏️ Mudança:pergunte "meu joelho dói no quilômetro 29" — preveja o subconjunto primeiro e verifique as linhas de DESPACHO.
# The factory's mode parameter is THE variable this level teaches:
def _specialist(name, domain, focus, mode):
kwargs = {}
if mode == "single_turn": # the structured contract only makes sense for a TOOL
kwargs = dict(mode="single_turn",
input_schema=SpecialistInput, output_schema=SpecialistResponse)
return Agent(name=name, model=MODEL,
description=f"Marathon {domain} specialist. Consult for: {focus}.",
instruction=..., **kwargs)
race_concierge = Agent(name="race_concierge", model=MODEL,
sub_agents=[...six specialists...], # NOTE: no `mode` on the coordinator
instruction="...DECIDE which specialists are relevant... call them IN PARALLEL... SYNTHESIZE...")
10. L3b · Modo de tarefa: uma conversa com uma linha de chegada — Pilar 2

⚡ Resumo:o modo intermediário — converse com o usuário até que os campos sejam coletados e retorne automaticamente com um objeto validado.
A pergunta:L3a deixou uma lacuna. chat é o proprietário de toda a conversa, e single_turn nunca fala com o usuário. Mas o trabalho real de coleta fica entre os dois: "converse com o usuário ATÉ coletar X. Depois, volte com um objeto validado". Qual é esse modo?
O formato:
race_desk (coordinator)
└─ gear_fitter (mode="task", output_schema=GearOrder)
▶ Colab:execute a célula L3b · 📁 GitHub: L3b_task_desk/ · 💻 Local: python -m L3b_task_desk.desk


🔍 Os marcadores : mode="task" + output_schema= no mesmo agente e, na saída, a pausa ⏸ e a chamada finish_task.
O que você vai ver:
━━ TURN 1 ━━ user: 'I need shoes for the marathon.'
race_desk → delegate: gear_fitter
gear_fitter: What is your shoe size?
⏸ The run ENDED — but nothing failed. This is a PAUSED task.
━━ TURN 2 ━━ user: 'Size 9, wide.' (same session → resumes the task)
gear_fitter → finish_task (payload validates as GearOrder)
race_desk: Your order ... in size 9 Wide has been confirmed.
Três coisas aconteceram que nenhum modo L3a pode fazer:
- A execução foi interrompida no meio da tarefa: uma tarefa pausada, não um travamento nem uma falha. O agente fez uma pergunta para esclarecer e está aguardando a resposta. No
adk web, basta digitar a resposta. O arnés a inclui como uma segunda mensagem na mesma sessão. - A próxima mensagem retomou o MESMO agente de tarefas: sem redirecionamento nem nova delegação. A sessão sabe quem estava esperando.
finish_taskencerrou a conversa — um ADK de ferramenta injetou porque demode="task". O agente precisa chamar para concluir, e o payload precisa ser validado em relação aoutput_schema. Uma conversa com uma linha de chegada digitada. Em seguida, o controle volta automaticamente para o organizador, e o resultado é anexado.
A regra de uma pergunta para escolher um modo
💡 "O usuário precisa conversar com ele e ATÉ QUANDO?" chat = indefinidamente · task = até que os campos sejam coletados · single_turn = nunca.
Modo | Human in the loop | Paralelo? | Retorna ao elemento pai |
| conversa completa | não | manual (via transferência) |
| apenas perguntas de esclarecimento | não | automática (via |
| nenhum | sim | automático (com o resultado) |
O mode é executado apenas em subagentes, nunca no coordenador. Os nós de fluxo de trabalho usam single_turn por padrão (por isso, os níveis L1 a L2b nunca escreveram isso), enquanto os subagentes usam chat por padrão (por isso, o nível L3a precisou fazer isso).
⚠️ Duas observações sobre a versão antes de você criar algo com base nisso: (1) task
como um nó de gráfico estático depende da versão: nas versões 2.0.0b1 a 2.3.0 (fixação deste codelab), Workflow(...) é gerado na construção. Use exatamente o que esse nível faz (um coordenador de chat com subagentes de tarefas) ou despache via ctx.run_node. Implementado na versão 2.5.0. (2) "Os agentes de tarefa precisam ser agentes folha" (sem subagentes próprios) é uma limitação documentada do ADK, mas um contrato, não uma proteção de tempo de execução: nem 2.3.0 nem 2.5.0 vão impedir você. Não interprete a ausência de um erro como permissão.
💡 Saiba mais:um agente task incorporado em um fluxo de trabalho de gráfico (a forma 2.5.0 ou mais recente), com roteamento que pode fazer a conversa voltar para uma nova tentativa: repositório complementar 22_agent_in_workflow · guia do modo completo: docs/agent-modes.md.
❓ Você deve estar se perguntando: o que
task
comprar para mim o que os outros dois não podem? Três coisas: retorno automático (o chat continua a conversa) · uma linha de chegada digitada (o payload de finish_task precisa ser validado em relação ao esquema. Você recebe dados, não uma transcrição) · pausar/retomar (o ⏸ é uma tarefa em espera aguardando um humano, não um travamento).
👀 Leia : gear_fitter — mode="task" + output_schema é o contrato completo. · ▶ Execute. · ✏️ Mudança : run_desk("I need a hydration vest", "2 liters, medium") — a pergunta esclarecedora se adapta, e a linha de chegada permanece digitada.
11. L4a · Distribuição de dados paralela dimensionada no tempo de execução (pilar 3a)

⚡ Resumo:o esqueleto ainda tem três etapas estáticas. A dinâmica fica dentro da etapa do meio, em que a largura é decidida pelos dados no tempo de execução.
⚠️ Atenção: esta é a etapa mais íngreme da escada. O nível anterior tinha 44 linhas, e este tem cerca de 120, com três agentes e dois nós de fluxo de trabalho, sem padding. Reserve cerca de 15 minutos e confie na linha "Ler/Executar/Mudar" no final: não é necessário absorver todas as linhas na primeira leitura.
A pergunta:a forma do trabalho depende da entrada. Não é possível desenhar o gráfico com antecedência. Comece com a largura do tempo de execução: deixe o LLM decidir quantas subperguntas.
A forma (um nível de profundidade):
START ─► decompose ─► research_topic (parallel_worker) ─► synthesize
│ │ │
└──┴──┴─ (flat: no children yet)
Uma pergunta aberta é decomposta em N subperguntas. N é escolhido pelo LLM durante a execução (3 a 7). Cada uma é pesquisada em paralelo e sintetizada em um briefing.
▶ Colab:execute a célula L4a · 📁 GitHub: L4a_flat_research/ · 💻 Local: python -m L4a_flat_research.deep_research

🔍 Os marcadores — não há
dynamic=True
. Dinâmico é uma forma de escrever, não uma configuração. Dois marcadores e apenas dois: @node(parallel_worker=True) (usa uma lista de tamanho de tempo de execução, executa um worker por item) e ctx.run_node(...) (agenda nós de código diretamente). Se você vir um dos dois, está no modo dinâmico.
O que você vai ver:o decompositor imprime, por exemplo, cinco subperguntas, que são pesquisadas em paralelo, e depois um resumo sintetizado. O número muda a cada execução, o que não seria possível com o gráfico fixo.
Duas flags no worker que vale a pena entender:
rerun_on_resume=Trueé obrigatório em qualquer nó que chamectx.run_node. O ADK gera umValueErrorsem ele. Ao retomar, ele precisa executar novamente o nó de envio para recriar os filhos gerados, já que eles não estão no gráfico estático.retry_config=limita como isso FALHA. Um worker paralelo cancela todos os irmãos e gera novamente o instante em que um filho falha. Portanto, sem uma nova tentativa, um único 429 transitório descarta toda a execução, incluindo todas as chamadas já pagas. A nova tentativa é feita no nó interno por item, então cada ramificação tenta novamente de forma independente.
❓ Talvez você esteja se perguntando: como o ADK "sabe" que isso é dinâmico? Não é necessário, porque nada é declarado em lugar nenhum. O decompositor produz uma lista no tempo de execução, e o worker paralelo se ajusta ao que chega. O dinamismo é uma propriedade do fluxo de dados que você escreveu, não um modo que você ativou.
👀 Leia: as duas flags em research_topic: parallel_worker e rerun_on_resume. · ▶ Execute. · ✏️ Mudar:troque por sua própria pergunta aberta. N muda porque a entrada decidiu a largura.
12. L4b · Adicionar geração recursiva (Pilar 3b)

⚡ TL;DR:a recursão é escrita, não dada. O worker chama a si mesmo por ctx.run_node, Python comum. Portanto, o freio também precisa ser escrito. É MAX_DEPTH.
A questão:às vezes, uma descoberta de pesquisa revela um subtema específico que merece uma investigação própria. Como você permite que uma ramificação gere mais trabalho paralelo e o mantenha limitado?
O formato (agora recursivo):
START ─► decompose ─► research_topic (parallel_worker, recursive) ─► synthesize
│ │ │
│ │ └─ research(q3) ─► maybe spawn children
│ └─── research(q2) ─► maybe spawn children
└────── research(q1) ─► maybe spawn children
▶ Colab:execute a célula L4b · 📁 GitHub: L4b_recursion/ · 💻 Local: python -m L4b_recursion.deep_research

@node(parallel_worker=True, rerun_on_resume=True)
async def research_topic(ctx, node_input):
finding = coerce(await ctx.run_node(research_agent, node_input=...), ResearchFinding)
if finding.needs_deeper and finding.deeper_questions and depth < MAX_DEPTH: # boundary in CODE
children = await ctx.run_node(research_topic, node_input=deeper) # recursive fan-out
yield Event(output={..., "children": children})
🔍 Os marcadores: ctx.run_node(research_topic, ...) dentro do próprioresearch_topic. A autorreferência é a recursão, e a proteção depth < MAX_DEPTH está uma linha acima.
O que você vai ver:nós de pesquisa imprimindo spawning N deeper (recursão acontecendo ao vivo) e uma forma de árvore de tempo de execução (por exemplo, 5 top-level + 10 recursive children). A árvore muda a cada execução.
⚠️ Antes de aumentar o knob:o teto cresce rapidamente. MAX_DEPTH=3 leva o pior caso de ~30 chamadas para ~93. No final de uma execução, você pode ver uma linha de registro cancelling N leftover tasks. Isso significa que o ADK está desativando o grupo de tarefas paralelas depois que o resultado já foi concluído. Inofensivo e, dependendo da sua configuração de geração de registros, talvez você nunca o veja.
❓ Talvez você esteja se perguntando: o comportamento dinâmico não é recursivo por padrão? Não. O L4a é totalmente dinâmico e tem zero recursão. O Dynamic só oferece um fluxo de controle comum do Python, mas a L4b escolhe escrever recursão com ele. E como você escreveu a recursão, você precisa escrever o limite. É aqui que "deixe o LLM moldar o trabalho, mantenha os limites no código" deixa de ser um slogan.
👀 Leia:o guardião: if finding.needs_deeper and depth < MAX_DEPTH. · ▶ Execute. · ✏️ Mudar:defina MAX_DEPTH = 1 e execute novamente. A árvore fica mais plana, e a execução fica mais barata. O limite é SEU, no código.
13. L5 · Which pattern should you use?

⚡ Resumo: um eixo decide tudo: quem escolhe a próxima etapa: o gráfico que você desenhou, o LLM ou seu código.
Você criou todos os três. O modelo que os torna úteis é: corresponda o padrão à forma do seu problema.
O eixo: quem decide o que será executado em seguida?
Pilar | Quem decide o que vai ser executado em seguida | Integrado |
1 · Gráfico | o gráfico que você desenhou | L2a / L2b |
2 · Colaborativo | o LLM | L3a / L3b |
3 · Dinâmico | seu código Python, no tempo de execução | L4a / L4b |
Etapa 0: você precisa de um gráfico?
O ADK envia agentes de fluxo de trabalho pré-criados: SequentialAgent, ParallelAgent, LoopAgent. Para uma cadeia simples de agentes, essas são as respostas corretas mais baratas, e não há um gráfico para montar. Use-os quando precisar de roteamento explícito (roteador do L2b), uma junção (JoinNode do L2a) ou nós que não são agentes (uma função simples, sem chamadas de LLM). Geralmente, esse último é o motivo.
Would a prebuilt SequentialAgent / ParallelAgent / LoopAgent do?
│
├─ YES ──────────────────────────────► use it; stop here
│
└─ NO — I need routing, a join, or non-agent nodes
│
Can you draw the workflow before the input arrives?
│
├─ YES ───────────────────────────► Pillar 1 · Graph workflow (L2a/L2b)
│
└─ NO
├─ Known team, request picks the subset? ─► Pillar 2 · Collaborative (L3a/L3b)
└─ Does the shape depend on the input? ──► Pillar 3 · Dynamic (L4a/L4b)

O enquadramento honesto de 1.x x 2
Isso não significa que "a versão 2.0 pode fazer coisas que a 1.x não conseguia". A versão 1.x podia criar tudo isso. A mudança é que 2.0 dá a cada forma uma casa mais direta. Assim, o fluxo de controle conhecido sai do comando e se torna uma estrutura que você pode ver e testar.
Padrão | O custo da versão 1.x | Página inicial do ADK 2 |
Gráfico | Quatro chamadas de LLM no build comum; roteamento oculto em um comando | função + nós de agente como iguais → 1 chamada, roteador de declaração |
Colaborativo | criável via encanamento | uma equipe declarada: |
Dinâmica | a recursão tira você do framework |
|
O app inteiro e o que um gráfico não pode mostrar
Agora você criou todas as partes abaixo. O Workflow expõe a estrutura em graph.edges. Portanto, esta imagem é gerada pelo código, e não desenhada à mão. O que a introspecção encontra é o resumo deste laboratório:
Pilar | O que | Por quê? |
1 · Gráfico (L2b) | 10 arestas, rotas e tudo mais | você desenhou antes de qualquer entrada chegar |
2 · Colaborativo (L3a) | 0 arestas: apenas | O LLM escolhe o subconjunto por solicitação |
3 · Dinâmica (L4a/L4b) | 3 arestas — idênticas em ambos | a recursão é escrita em Python, não conectada ao gráfico |
Essa última linha é a prova da pergunta que L4b responde: L4a e L4b têm o mesmo gráfico, e apenas um deles é recursivo.
O que você pode criar agora
Cada padrão que você acabou de executar é uma forma de produto real:
Você praticou | Na natureza, isso é | Iniciar em |
Gráfico + roteador (L2a/L2b) | pipelines de documentos, etapas de ETL com LLM, cadeias de revisão/aprovação, plataformas de avaliação | L2b deste repositório |
Coordenador + equipe | um copiloto de suporte com equipes especializadas, mesas de triagem e revisão com várias lentes | Modo 2 da demonstração de maratona |
Agentes | formulários de admissão, fluxos de reserva, integração, KYC — qualquer "coletar e agir" | |
Largura/profundidade dinâmica (L4a/L4b) | agentes de pesquisa, geradores de relatórios, varreduras de auditoria em entradas de tamanho desconhecido | Modo 3 da demonstração da maratona |
Elas compõem
Os três padrões não são mutuamente exclusivos. Um nó de gráfico pode chamar um coordenador de colaboração, e um especialista pode iniciar um fluxo de trabalho dinâmico. Escolha o padrão certo para cada parte do problema. Assim, você evita transformar todos os sistemas de agentes em um único comando gigante.

💡 Teste no seu próprio fluxo de trabalho:o script que criou isso é scripts/graph_dump.py. Aponte para qualquer Workflow e ele vai imprimir as bordas reais, um diagrama estrutural sem custo financeiro de qualquer coisa que você construir.
14. Parabéns

Você criou um Coach para o dia da corrida de maratona e, ao longo do caminho, os três padrões de orquestração do ADK 2.
O que você aprendeu
- Prólogo: o megacomando que inventou o próprio clima: por que a estrutura existe.
- L0–L1:
Agent,Runner, uma ferramenta real que o modelo escolhe chamar e seu primeiroWorkflow(nós de função + nós de agente como iguais). - L2a / L2b: fluxos de trabalho de gráficos: distribuição de dados paralela +
JoinNodee roteamento determinístico: uma chamada de LLM. - L3a: agentes colaborativos. A mesma equipe executa
chat(isolado) esingle_turn(subconjunto paralelo + síntese). Uma flag, dois mundos. - L3b — Modo
task: uma pergunta esclarecedora pausada, um currículo programado,finish_taskretornando um objeto validado. - L4a / L4b: fluxos de trabalho dinâmicos: largura de tempo de execução (distribuição de dados) e profundidade de tempo de execução (recursão) com limites no código.
- L5: a árvore de decisão e como os padrões se compõem.
Linhas que valem a pena manter
As funções preparam o contexto. As arestas definem o fluxo de trabalho. O roteador escolhe o caminho. O modelo escreve a resposta.
Deixe o LLM moldar o trabalho, mas mantenha os limites no código.
Corresponda o padrão à forma do seu problema.
Próximas etapas
- Execute o app completo de onde esses níveis foram extraídos: o Marathon Race Day Coach, um build do FastAPI + SSE com uma interface do navegador que mostra os três modos ao vivo: github.com/cuppibla/adk-2-marathon-demo.
- Saiba mais: adk-workflows-compared (em inglês): todas as 23 amostras oficiais de fluxo de trabalho do ADK 2, cada uma com uma porta 1.x e orientações sobre quando usar. Comece com
docs/three-pillars.mde depois os temas que este codelab não abordou:07_loop,17_request_inpute22_agent_in_workflow. - Transfira seu próprio problema: quais partes são de estrutura conhecida (L2), equipe conhecida (L3a/L3b) e formato desconhecido (L4)?
- Confira o código: github.com/cuppibla/adk2-tutorial.
- Você veio pelo workshop? Seu crédito e o projeto criado por ele não vão durar para sempre. Para continuar executando esses níveis sem custo financeiro, siga a etapa Configuração para levar para casa: uma chave sem custo financeiro do AI Studio, sem projeto na nuvem e sem faturamento. A troca dessa célula é a única mudança.