O Google Agent Development Kit entrou em uma nova fase com a versão 2. A grande mudança é o reconhecimento explícito de que agentes baseados em LLM são probabilísticos, e que produção exige previsibilidade. A resposta do framework foi unir dois mundos no mesmo motor. De um lado, workflows declarativos em grafo com nodes, edges, JoinNode e suporte nativo a fan-out e fan-in. De outro, workflows dinâmicos imperativos com o decorator @node e a API ctx.run_node(), que aceita loops, recursão e human-in-the-loop sem perder checkpoints automáticos.
A versão 2, disponibilizada para general availability em 19 de maio de 2026 (a série já chegou à 2.2.0 em junho), é pronta para produção, mas não é retrocompatível com a 1.x. A instalação voltou a ser o pip install google-adk padrão. Este artigo apresenta as decisões arquiteturais por trás do ADK 2.0, os blocos de construção essenciais (Workflow, BaseNode, JoinNode, Event, Context), os breaking changes que você precisa conhecer antes de migrar e exemplos Python funcionais que mostram quando preferir o grafo declarativo, quando usar o modo dinâmico e como combinar os dois para sistemas reais.
Insights
- O ADK 2.0 transformou orquestração probabilística em fluxos determinísticos ao introduzir um motor de grafos com nodes e edges explícitos no mesmo framework
- A classe
Workflowpermite declarar arestas entre agentes, funções Python e ferramentas, eliminando a dependência exclusiva do raciocínio do LLM para roteamento - O
JoinNoderesolve fan-in para múltiplas execuções paralelas, enquanto várias arestas saindo do mesmo nó disparam fan-out sem código adicional - O modo imperativo dinâmico com
@nodeectx.run_node()libera loops, recursão e human-in-the-loop com checkpoints automáticos e resume nativo - A versão 2 é GA desde 19 de maio de 2026 e já está em produção, instalada com o
pip install google-adkpadrão, mas traz breaking changes em relação à 1.x
A natureza probabilística do LLM era o calcanhar de Aquiles do ADK 1.x
Antes do ADK 2.0, orquestrar agentes complexos no Google ADK significava confiar que o LLM faria a escolha certa em cada bifurcação. O framework oferecia SequentialAgent, ParallelAgent e LoopAgent para padrões fixos, mas qualquer roteamento condicional dependia de um agente coordenador interpretando descrições e decidindo para quem delegar.
Esse modelo funcionava em demos. Em produção, a falta de determinismo virava um problema operacional. Auditoria difícil. Debugging não reproduzível. Custos de token imprevisíveis. Times de engenharia precisando criar testes estatísticos para validar fluxos que um if/else trivial resolveria.
O ADK 2.0, disponibilizado para general availability em 19 de maio de 2026, repensou essa abordagem. O motor agora oferece modelos de orquestração que coexistem no mesmo framework.
flowchart LR
A[Entrada do usuário] --> B{Modelo de orquestração}
B -->|Fluxo conhecido| C[Workflow declarativo<br/>nodes + edges]
B -->|Fluxo dinâmico| D[Workflow imperativo<br/>ctx.run_node]
C --> E[Execução determinística]
D --> E
E --> F[Checkpoints automáticos]
F --> G[Resume nativo]A escolha do modelo virou uma decisão arquitetural consciente, não uma limitação do framework.
A instalação do ADK 2 voltou a ser o pip padrão
Diferente do período de preview, em que a versão exigia a flag --pre, o ADK 2.0 GA é instalado com o comando normal. Use um ambiente virtual limpo para isolar a major nova de qualquer projeto 1.x.
|
1 2 3 4 5 6 |
# Ambiente virtual limpo é recomendado python -m venv .venv-adk2 source .venv-adk2/bin/activate # Linux/Mac # .venv-adk2\Scripts\activate # Windows pip install google-adk |
Verifique a versão antes de continuar. A série 2.x já está na 2.2.0.
|
1 2 |
pip show google-adk # Version: 2.2.0 |
Se você precisa continuar em produção na 1.x enquanto avalia a migração, fixe a major anterior em outro ambiente.
|
1 |
pip install "google-adk~=1.0" |
A 2.0 exige Python 3.10 ou superior.
Os workflows declarativos transformam o grafo em código Python
O bloco principal do modo declarativo é a classe Workflow. Você descreve o grafo de execução como uma lista em edges, onde cada item define a transição entre nodes. Um node pode ser um agente, uma função Python comum ou outro Workflow.
A aresta sequencial encadeia nodes diretamente
A forma mais simples de aresta liga START ao primeiro node e segue em sequência dentro da mesma tupla.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from google.adk import Workflow from google.adk.agents import Agent # Dois agentes que serão executados em sequência extractor_agent = Agent( name="extractor_agent", model="gemini-2.0-flash", description="Extrai entidades nomeadas do texto", instruction="Identifique pessoas, lugares e organizações no texto fornecido. Retorne em JSON." ) summarizer_agent = Agent( name="summarizer_agent", model="gemini-2.0-flash", description="Resume textos longos", instruction="Resuma o texto em duas frases." ) pipeline = Workflow( name="extract_and_summarize", edges=[ ("START", extractor_agent, summarizer_agent), ] ) |
A leitura é direta. Comece, execute o extractor_agent, depois execute o summarizer_agent. Sem espaço para o LLM “decidir” outro caminho.
O node de função é uma função Python comum
No ADK 2.0 você não embrulha funções em nenhuma classe especial. Uma função Python comum é um node: ela recebe o node_input da aresta anterior e devolve um Event. O campo output carrega o resultado para o próximo node.
|
1 2 3 4 |
from google.adk import Event def normalizar_texto(node_input: str): return Event(output=node_input.upper()) |
O roteamento condicional usa um dicionário de rotas
Quando o próximo passo depende de uma decisão, você adiciona uma função roteadora que retorna um Event com a route desejada. As destinações ficam em um dicionário associado à aresta de saída.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from google.adk import Workflow, Event def triage_router(node_input: dict): """Roteador determinístico baseado no tipo de ticket.""" ticket_type = node_input.get("ticket_type", "outros") if ticket_type == "tecnico": return Event(route="TECH") if ticket_type == "financeiro": return Event(route="BILLING") return Event(route="GENERIC") support_workflow = Workflow( name="support_triage", edges=[ ("START", triage_router), (triage_router, { "TECH": tech_support_agent, "BILLING": billing_agent, "GENERIC": generic_agent, }), ] ) |
O roteador é Python puro. *Sem inferência de LLM. Sem custo de token. Sem variabilidade.* Para regras de negócio bem definidas, esse padrão substitui o agente coordenador da 1.x.
O fan-out e o fan-in viraram cidadãos de primeira classe
Disparar tarefas paralelas e consolidar resultados é o padrão clássico de orquestração distribuída. No ADK 2.0, o fan-out é declarado por múltiplas arestas saindo do mesmo node, e o fan-in é resolvido pelo JoinNode.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from google.adk import Workflow, Event from google.adk.workflow import JoinNode def fetch_pricing(node_input): return Event(output={"price": 99.90, "source": "pricing_api"}) def fetch_inventory(node_input): return Event(output={"stock": 42, "source": "inventory_api"}) def fetch_reviews(node_input): return Event(output={"avg": 4.6, "source": "reviews_api"}) def consolidate(node_input): # node_input recebe a coleção de outputs reunida pelo JoinNode return Event(output={"summary": node_input}) join_node = JoinNode(name="product_join") product_workflow = Workflow( name="product_aggregator", edges=[ ("START", fetch_pricing, join_node), ("START", fetch_inventory, join_node), ("START", fetch_reviews, join_node), (join_node, consolidate), ] ) |
A topologia fica clara em um diagrama.
flowchart LR
Start((START)) --> P[fetch_pricing]
Start --> I[fetch_inventory]
Start --> R[fetch_reviews]
P --> J[JoinNode<br/>product_join]
I --> J
R --> J
J --> C[consolidate]O JoinNode espera todas as arestas convergentes terminarem antes de continuar e repassa a coleção de saídas para o próximo node. A ordem de chegada não importa, e a paralelização acontece automaticamente sem que você gerencie threads ou futures manualmente. Se um dos nodes a montante não produzir um Event, o JoinNode fica bloqueado e a execução para — comportamento documentado e intencional.
Os workflows dinâmicos resolvem o que o grafo estático não cobre
Nem todo problema cabe em um grafo desenhado de antemão. Loops com condição de parada, recursão sobre dados arbitrários e fluxos que dependem de aprovação humana são casos onde o modelo declarativo se torna verboso ou impossível.
Para esses cenários o ADK 2.0 oferece o modo imperativo dinâmico. Você marca funções com @node e usa ctx.run_node() para executar sub-nós dentro da função pai.
O decorator @node transforma uma função em node executável
|
1 2 3 4 5 6 7 8 9 |
from google.adk import Context from google.adk.workflow import node @node(name="city_workflow", rerun_on_resume=True) async def city_workflow(ctx: Context, node_input): city = await ctx.run_node(city_generator_agent) city_time = await ctx.run_node(city_time_function, node_input=city) report = await ctx.run_node(city_report_agent, node_input=city_time) return report |
O retorno de ctx.run_node() é o resultado direto do node executado. *Não há manipulação manual de state**. Cada chamada gera automaticamente um checkpoint que o motor usa para resume* em caso de falha.
O loop com condição de parada fica trivial em modo dinâmico
Imagine um agente coder que gera código, valida com lint, e itera até o resultado passar na validação. Em grafo estático isso seria desconfortável. Em modo dinâmico é um while.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from google.adk import Context from google.adk.workflow import node @node(name="code_workflow", rerun_on_resume=True) async def code_workflow(ctx: Context, node_input): code = await ctx.run_node(coder_agent, node_input=node_input) check = await ctx.run_node(compile_lint_check, node_input=code) while check.findings: code = await ctx.run_node( fixer_agent, node_input={"code": code, "findings": check.findings} ) check = await ctx.run_node(compile_lint_check, node_input=code) return code |
Se o processo falhar na quinta iteração, o resume retoma exatamente daquela iteração. Os nodes anteriores que já tiveram sucesso são pulados, porque cada ctx.run_node() gravou seu checkpoint.
A paralelização dinâmica usa asyncio.gather sobre ctx.run_node
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import asyncio from typing import Any from google.adk import Context from google.adk.workflow import node, BaseNode @node(rerun_on_resume=True) async def parallel_supervisor( ctx: Context, node_input: list[Any], real_node: BaseNode ): tasks = [ctx.run_node(real_node, node_input=item) for item in node_input] results = await asyncio.gather(*tasks) return results |
A flag rerun_on_resume=True no decorator sinaliza que o node pai precisa reentrada idempotente após interrupção. Se um worker falhou, apenas ele é reexecutado no resume, não todo o supervisor.
O human-in-the-loop é nativo no modo dinâmico
Aprovações manuais sempre foram o pesadelo de orquestradores síncronos. O ADK 2.0 trata isso com RequestInput e rerun_on_resume=False para o node que pausa.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from google.adk import Context from google.adk.workflow import node from google.adk.events import RequestInput @node(rerun_on_resume=False) async def get_user_approval(ctx: Context, node_input): yield RequestInput(message="Aprovar este pagamento? (sim/nao)") @node(rerun_on_resume=True) async def payment_workflow(ctx: Context, node_input): user_response = await ctx.run_node(get_user_approval) if user_response.lower() == "sim": result = await ctx.run_node(execute_payment, node_input=node_input) return {"status": "approved", "result": result} return {"status": "denied"} |
O get_user_approval pausa o workflow e espera entrada externa. Quando ela chega (via Web UI, API ou outro mecanismo), o motor retoma o payment_workflow no ponto exato. Sem polling. Sem state machine manual. Sem timeout custoso.
A Web UI ganhou um Graph View para inspeção visual da topologia
A interface web do ADK, acessada com adk web, exibe um Graph View que renderiza a hierarquia de agentes e nodes do workflow selecionado. Você consegue visualizar:
- A árvore de agentes raiz e suas dependências
- A topologia completa do
Workflow, incluindo arestas paralelas e JoinNode - O caminho efetivamente percorrido durante a execução
- O estado de cada node (executado, em execução, falhou, pulado no resume)
Para abrir, basta executar o comando padrão na pasta pai do agente.
|
1 |
adk web |
Acesse http://localhost:8000, selecione o agente que contém o Workflow e abra a aba Graph View. Para sistemas multi-agente complexos isso reduz drasticamente o tempo de debugging.
Os workflows aninhados permitem composição e reuso
Um Workflow pode aparecer dentro de outro Workflow como um node qualquer. Isso é fundamental para modularização de sistemas grandes.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from google.adk import Workflow # Workflow especialista, definido em arquivo separado billing_workflow = Workflow( name="billing_pipeline", edges=[ ("START", validate_payment, charge_card), (charge_card, send_receipt), ] ) # Workflow principal usa o billing_workflow como um node order_workflow = Workflow( name="order_pipeline", edges=[ ("START", check_inventory, billing_workflow), (billing_workflow, fulfill_order), ] ) |
O motor faz resume parcial em workflows aninhados. Se o billing_workflow falhou na metade, o resume do order_workflow retoma de dentro do workflow aninhado, no ponto exato onde a falha ocorreu.
A migração da 1.x para a 2.0 exige planejamento
A 2.0 não é drop-in. O modelo de execução inteiro mudou para grafos, e isso quebra suposições da 1.x. As mudanças mais importantes a considerar antes de migrar:
| Aspecto | ADK 1.x | ADK 2.0 GA |
|---|---|---|
| Orquestração | SequentialAgent, ParallelAgent, LoopAgent | Workflow declarativo + @node dinâmico |
| Roteamento condicional | Via agente coordenador (LLM) | Função roteadora retornando Event(route=...) |
| Fan-in | Manual com agregador | JoinNode nativo |
| Resume / checkpoints | Limitado | Automático por node |
| Modelo de agente | Classe própria | BaseAgent herda de BaseNode; _run_async_impl() deixa de ser chamado |
| Extensão de comportamento | Override de _run_async_impl() | BeforeAgentCallback e AfterAgentCallback |
Event | Sem node_info/output | Novos campos node_info e output no payload |
| Mutação de eventos | session.events.append() manual | Proibido — você deve yield o evento |
| Sessões | — | Geradas na 2.0 são lidas pela 1.28+ (campos extras ignorados); incompatíveis com 1.x antigo |
| Instalação | pip install "google-adk~=1.0" | pip install google-adk |
A recomendação prática é manter ambientes separados durante a transição. Atualize os schemas de banco para aceitar os novos campos do Event antes de gravar sessões 2.0 em uma base compartilhada, troque overrides de execução pelos callbacks, e deixe as exceções propagarem para que o mecanismo de retry do framework atue.
Os critérios de escolha entre declarativo e dinâmico são claros
Use workflow declarativo quando:
- O fluxo é conhecido em tempo de design
- Você precisa de auditoria visual clara da topologia
- O paralelismo é fixo (N tarefas conhecidas convergindo em um Join)
- Você quer maximizar previsibilidade para revisão por times de compliance
Use workflow dinâmico quando:
- Há loops com condição de parada dependente de dados
- O número de iterações ou workers paralelos é descoberto em runtime
- Você precisa de human-in-the-loop com pausas indeterminadas
- A lógica de roteamento envolve recursão ou estruturas de dados arbitrárias
Sistemas reais frequentemente combinam os dois modelos. Um workflow declarativo no nível mais alto, com workflows dinâmicos aninhados onde a flexibilidade é necessária.
flowchart TD
A[Decisão de design] --> B{Fluxo conhecido<br/>em design time?}
B -->|Sim| C{Precisa de loops<br/>condicionais?}
B -->|Não| D[Modo dinâmico<br/>@node + ctx.run_node]
C -->|Não| E[Modo declarativo<br/>Workflow + edges]
C -->|Sim| F[Combine ambos<br/>Declarativo no topo<br/>Dinâmico aninhado]FAQ
1. O ADK 2.0 já está pronto para produção?
Sim. O ADK 2.0 chegou ao general availability em 19 de maio de 2026 e é uma versão estável, voltada a produção. A série já avançou para a 2.2.0. O cuidado não é mais com estabilidade, e sim com retrocompatibilidade: a 2.0 traz breaking changes em relação à 1.x. Planeje a migração antes de atualizar sistemas existentes.
2. Posso reaproveitar agentes da 1.x dentro de um Workflow 2.0?
Em parte, mas o modelo de execução mudou. Na 2.0 o BaseAgent passou a herdar de BaseNode, e overrides de _run_async_impl() deixam de ser chamados — você os substitui por BeforeAgentCallback e AfterAgentCallback. Agentes que não dependiam desses overrides tendem a funcionar como nodes, mas valide cada um isoladamente no novo motor antes de migrar um sistema completo.
3. Qual a vantagem real do JoinNode sobre asyncio.gather no Python?
O JoinNode opera no plano do motor de execução, não no plano da aplicação. Isso traz três ganhos. Os checkpoints cobrem cada aresta paralela individualmente, então o resume reexecuta apenas o que falhou. O motor controla ordenação e espera de todas as saídas antes de prosseguir. E a topologia fica visível na Graph View da Web UI, o que asyncio.gather não oferece.
4. Como testar workflows determinísticos sem chamar o LLM?
Substitua os nodes de agente por funções Python comuns que retornam Event(output=...) previsíveis. Como o roteamento e a topologia são definidos no grafo, e não na inferência do LLM, a substituição não muda o caminho percorrido. Isso permite testes unitários rápidos do fluxo independente da camada de modelo.
5. O que acontece se eu usar ctx.run_node sem rerun_on_resume=True no node pai?
Se o node pai não declara rerun_on_resume=True e o workflow é interrompido, o motor não consegue garantir que a chamada ctx.run_node() será reexecutada de forma idempotente após o resume. O resultado pode ser execução duplicada de sub-nós ou estado inconsistente. A regra prática é simples. Todo node pai que orquestra outros via ctx.run_node() deve declarar rerun_on_resume=True.
Referências oficiais
- Bem-vindo ao ADK 2.0 — https://adk.dev/2.0/
- Workflows (visão geral) — https://adk.dev/workflows/
- Graph routes — https://adk.dev/graphs/routes/
- Dynamic workflows — https://adk.dev/graphs/dynamic/
- Releases ADK Python — https://github.com/google/adk-python/releases
- google-adk no PyPI — https://pypi.org/project/google-adk/