Orquestração do ADK 2: fluxos de trabalho gráficos, colaborativos e dinâmicos

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

Seu roteiro de aprendizado

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 app inteiro e o que um gráfico não pode mostrar

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 clone o repositório, ./setup_venv.sh e 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)

  1. Abra o link de reivindicação que o professor compartilhou. O caminho é parecido com https://me.developers.google.com/benefits/claim/your-workshop-name.
  2. Faça login e siga as instruções na página para aceitar o crédito.
  3. 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)

  1. Abra aistudio.google.com/app/apikey em uma nova guia do navegador.
  2. Faça login usando sua Conta do Google.
  3. Clique em Criar chave de API (canto superior direito).
  4. Escolha um projeto do Google ou deixe que ele crie um.
  5. 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)

  1. Clique no ícone de chave 🔑 na barra lateral esquerda do Colab.
  2. Clique em + Adicionar novo secret.
  3. Defina o Nome como exatamente GOOGLE_API_KEY.
  4. Cole a chave em Valor.
  5. 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:

  1. Não é possível confiar nele: os dados são inventados, mas de forma fluente.
  2. Não é possível testar: o roteamento da etapa 4 fica dentro da prosa. Não há if para teste de unidade.
  3. Não é possível trocar uma etapa: não há uma junção em que uma API de clima real possa ser conectada.
  4. 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).

O coach de megaprompt: confiante, sem nada atrás do gráfico

💻 Local: python -m shared.prologue

5. L0 · Seu primeiro agente do ADK 2

Roteiro — você está aqui: L0

⚡ 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

Fluxo L0

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

Roteiro — você está aqui: L1

⚡ 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

Fluxo L1

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)

Roteiro — você está aqui: L2a

⚡ 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

Fluxo L2a

🔍 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.
  • JoinNode aguarda os três e os agrupa em um payload tipado (BundledRunData), com chave pelo nome da função.
  • Um agente strategy lê o pacote e grava um RaceStrategy.

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)

Roteiro — você está aqui: L2b

⚡ 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

Fluxo L2b

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ção if, 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

Roteiro — você está aqui: L3a

⚡ 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?"

Fluxo L3a

🔍 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

Roteiro — você está aqui: L3b

⚡ 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

Fluxo L3b

gear_fitter segurando a tarefa aberta: uma tarefa pausada, não um travamento

🔍 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:

  1. 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.
  2. A próxima mensagem retomou o MESMO agente de tarefas: sem redirecionamento nem nova delegação. A sessão sabe quem estava esperando.
  3. finish_task encerrou a conversa — um ADK de ferramenta injetou porque de mode="task". O agente precisa chamar para concluir, e o payload precisa ser validado em relação a output_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

chat (padrão de subagente): assistente de suporte, copiloto aberto

conversa completa

não

manual (via transferência)

task: triagem, agendamento e solução de problemas

apenas perguntas de esclarecimento

não

automática (via finish_task, com um objeto validado)

single_turn — classificar · extrair · julgar · gerar

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_fittermode="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)

Roteiro — você está aqui: L4a

⚡ 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

Fluxo da L4a

🔍 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 chame ctx.run_node. O ADK gera um ValueError sem 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)

Roteiro — você está aqui: L4b

⚡ 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

Fluxo L4b

@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?

Roteiro — você está aqui: L5

⚡ 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)

L5 · which pattern

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 if

Colaborativo

criável via encanamento AgentTool; ParallelAgent always-all, transfer_to_agent serial

uma equipe declarada: sub_agents + mode="single_turn"

Dinâmica

a recursão tira você do framework

parallel_worker + ctx.run_node recursivo dentro da biblioteca

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 graph.edges contém

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 sub_agents + mode

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 single_turn (L3a)

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 task (L3b)

formulários de admissão, fluxos de reserva, integração, KYC — qualquer "coletar e agir"

22_agent_in_workflow

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.

O app inteiro e o que um gráfico não pode mostrar

💡 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

Nove agentes, um bastão, uma chegada organizada

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 primeiro Workflow (nós de função + nós de agente como iguais).
  • L2a / L2b: fluxos de trabalho de gráficos: distribuição de dados paralela + JoinNode e roteamento determinístico: uma chamada de LLM.
  • L3a: agentes colaborativos. A mesma equipe executa chat (isolado) e single_turn (subconjunto paralelo + síntese). Uma flag, dois mundos.
  • L3b — Modo task: uma pergunta esclarecedora pausada, um currículo programado, finish_task retornando 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.md e depois os temas que este codelab não abordou: 07_loop, 17_request_input e 22_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.