1. Présentation
Le titre de l'ADK 2 est trois schémas d'orchestration. Cet atelier de programmation vous apprendra à les utiliser en créant une application, un coach pour le jour du marathon, une étape exécutable à la fois. Chaque niveau répond à une seule question, ajoute une idée et s'exécute de manière autonome.
Points abordés
- Workflows graphiques (pilier 1) : lorsque vous pouvez dessiner le flux avant l'arrivée de l'entrée.
- Agents collaboratifs (pilier 2) : lorsque vous connaissez l'équipe, mais que la demande sélectionne le sous-ensemble, et les trois modes de collaboration (
chat/task/single_turn), chacun s'exécute en direct. - Workflows dynamiques (pilier 3) : lorsque la forme du travail lui-même dépend de l'entrée.
- Comment choisir : un arbre de décision à une seule question et la façon dont les modèles se composent.
Le fil conducteur
Structure connue → Sous-ensemble connu d'équipes / de variables → Forme inconnue → Choisir la bonne

Ce que vous allez faire
Une application, Marathon Race Day Coach, a été assemblée un niveau exécutable à la fois. Chaque niveau est un module Python simple que vous exécutez depuis le terminal. Au niveau 5, les éléments ci-dessous vous appartiennent.
L'image est tirée du code en cours d'exécution : chaque ligne pleine a été lue à partir de Workflow.graph.edges. C'est la première leçon : les parties qui peuvent être dessinées à l'avance correspondent exactement au pilier 1, et les parties qui ne le peuvent pas expliquent l'existence des piliers 2 et 3.

Prérequis
- Un compte Google (pour Colab) : aucune configuration locale n'est requise.
- Environ 50 minutes (les deux niveaux 4 sont les plus longs, prévoyez-le).
- L'une des deux façons d'accéder à un modèle Gemini. Choisissez votre méthode : vous exécutez une étape de configuration et ignorez l'autre :
🎓 Atelier | 🏠 À emporter | |
Participants | Vous participez à un atelier en direct et l'instructeur vous a fourni un lien pour demander un crédit. | Toutes les autres personnes, y compris les participants à l'atelier, après l'atelier |
Ce dont vous avez besoin | Le lien de revendication et un compte Google pouvant créer un projet Cloud | Une clé API AI Studio sans frais |
Fonctionne sur | Vertex AI, dans un projet facturé sur votre crédit d'atelier | Google AI Studio |
Coût | Couvert par l'avoir | Niveau sans frais |
Étape de configuration | Configuration de l'atelier (étape suivante) | Configuration à domicile (étape suivante) |
Tout ce qui suit le prologue est identique dans les deux cas. La voie ne fait que déterminer le point de terminaison du modèle auquel le notebook s'adresse.
Deux façons de suivre
Chaque étape ci-dessous correspond à une cellule du notebook Colab et à un dossier du dépôt GitHub. Choisissez l'une des options suivantes :
- ▶ Colab (recommandé) : ouvrez le notebook et exécutez les cellules de haut en bas.
- 💻 Local :
git clonele dépôt,./setup_venv.sh, puis exécutez chaque niveau en tant que module (python -m ...) ou parcourez-les tous avec./run.sh(adk web).
2. Configuration de l'atelier : revendiquer votre crédit et passer à Vertex AI
Lors de l'atelier, vous recevez un crédit Google Cloud. Vous allez le revendiquer, créer un projet facturé sur celui-ci et pointer le notebook vers Vertex AI au lieu d'AI Studio. Une cellule effectue tout après la revendication.
1 · Demandez votre avoir (environ 1 min)
- Ouvrez le lien de revendication partagé par votre enseignant. Il se présente comme suit :
https://me.developers.google.com/benefits/claim/your-workshop-name. - Connectez-vous et suivez les instructions sur la page pour accepter le crédit.
- Notez le compte Google que vous avez utilisé. Chaque étape ci-dessous doit être exécutée avec ce même compte.
2 · Ouvrez le notebook et installez ADK 2 (environ 1 min)
Cliquez sur Ouvrir dans Colab ▶, puis exécutez la première cellule de code. Il épingle la version exacte d'ADK 2 sur laquelle cet atelier de programmation a été validé et affiche ✓ installed.
3) Exécutez la cellule "Workshop setup" (Configuration de l'atelier) (~3 min)
Il s'agit de la cellule intitulée 🎓 Parcours A · Atelier. Exécutez-le. Colab vous demandera de l'autoriser. Choisissez le même compte Google que celui avec lequel vous avez demandé le crédit et autorisez l'accès.
Il effectue quatre actions : il crée un projet appelé adk-2-tutorial-XXXX sur votre compte, active l'API Vertex AI sur ce projet, définit les quatre variables d'environnement que chaque cellule ultérieure lit, puis effectue un appel de test à Vertex et attend qu'il réponde. La configuration se termine donc ou vous indique pourquoi elle ne fonctionne pas, au lieu d'échouer plus tard dans un niveau.
Résultat attendu : la dernière ligne est celle qui compte :
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 · Passer l'étape "Configuration à domicile"
N'exécutez pas la cellule de clé AI Studio, car cela rétablirait AI Studio pour le notebook et annulerait ce que vous venez de faire. (La cellule se protège contre cela et refusera de s'exécuter, mais la méthode la plus propre consiste simplement à la passer.) Accédez directement à la cellule Shared building blocks (Blocs de construction partagés).
5 · Exécutez la cellule "Shared building blocks" (Composants de base partagés).
Exécutez-le une fois. Il définit les schémas Pydantic et les scénarios de marathon prédéfinis que chaque niveau à partir du niveau 2 réutilise. Vous verrez ✓ schemas + scenarios ready.
Après l'atelier
Votre crédit et le projet qu'il a créé ne sont pas éternels. Pour continuer à exécuter ces niveaux sans frais une fois l'atelier terminé, exécutez plutôt l'étape Configuration à emporter. Vous obtiendrez une clé AI Studio sans frais, sans projet Cloud ni facturation. Seule cette cellule change.
Pour libérer de l'espace plus tôt, ouvrez la console Cloud, sélectionnez adk-2-tutorial-XXXX, puis supprimez-le. Aucune autre ressource payante n'est créée dans cet atelier de programmation.
3. Configuration à domicile : clé API AI Studio
Tout ce qui se trouve sur ce chemin d'accès s'exécute sur une clé API Google AI Studio sans frais. Vous n'avez pas besoin de projet Google Cloud, de facturation ni d'installation locale. Cette étape prend environ trois minutes.
1) Ouvrez le notebook.
Cliquez sur Ouvrir dans Colab ▶. Vous serez redirigé vers le notebook, qui contient une introduction en Markdown, puis une cellule exécutable par niveau. Vous exécutez les cellules de haut en bas. Chacune imprime son propre résultat juste en dessous.
2 · Installer ADK 2 (environ 1 min)
Exécutez la première cellule de code. Il épingle la version exacte sur laquelle cet atelier de programmation a été validé :
%pip install -q "google-adk==2.3.0" python-dotenv pydantic nest_asyncio
Attendez la fin de l'opération. Le message ✓ installed s'affichera. (L'installation prend environ 30 à 60 secondes la première fois, puis elle est mise en cache.)
3 · Obtenez votre clé API Gemini depuis AI Studio (environ 1 min)
- Ouvrez aistudio.google.com/app/apikey dans un nouvel onglet du navigateur.
- Connectez-vous à l'aide de votre compte Google.
- Cliquez sur Créer une clé API (en haut à droite).
- Sélectionnez un projet Google existant ou laissez-le en créer un.
- Copiez la clé, qui commence par
AIza...et comporte environ 40 caractères.
4 · Ajoutez votre clé à Colab (~1 min)
Option A : Secrets Colab (recommandée, la clé reste masquée)
- Cliquez sur l'icône en forme de clé 🔑 dans la barre latérale de gauche de Colab.
- Cliquez sur + Ajouter un secret.
- Définissez Nom sur
GOOGLE_API_KEY. - Collez votre clé dans Valeur.
- Activez l'option Accès au notebook.
Option B : coller lorsque vous y êtes invité (rapide) : ignorez le secret. Lorsque vous exécutez la cellule suivante, une invite masquée 🔑 Enter your Google AI Studio API key: s'affiche. Collez le secret et appuyez sur Entrée.
5 : Exécuter la cellule clé
Il lit le secret (ou revient à l'invite de collage), puis pointe ADK vers AI Studio (et non 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.")
Résultat attendu : ✅ API key set — using Google AI Studio.
6 Exécutez la cellule "Shared building blocks" (Composants de base partagés).
Exécutez la cellule Shared building blocks (Composants de base partagés) une fois. Il définit les schémas Pydantic et les scénarios de marathon prédéfinis que chaque niveau à partir du niveau 2 réutilise. Vous verrez ✓ schemas + scenarios ready.
Vous avez terminé la configuration ! 🎽 Avant de passer à L0, la version que tout le monde crée en premier, faisons un petit détour.
4. Prologue : pourquoi ne pas utiliser une seule requête volumineuse ?
⚡ Avant de l'exécuter, concentrez-vous sur UNE chose à observer : d'où proviennent tous les nombres spécifiques ? C'est tout ce qu'il y a à faire. Tout le reste est superflu.
Avant d'utiliser le ladder, exécutez l'élément qu'il remplace : un agent dont l'invite promet tout (récupérer la météo, analyser le parcours, lire le journal d'entraînement, définir l'itinéraire en fonction des conditions, générer le plan).
Ce que vous verrez : une stratégie spécifique, bien structurée et qui inspire confiance, mais dont les chiffres sont inventés. Lors d'un test en direct, il a commencé par "J'ai récupéré les données météo d'aujourd'hui" et a indiqué 11 °C, un vent de 14,5 km/h et une analyse d'un journal d'entraînement qu'il n'avait jamais vu. Il n'y a pas d'API météo ici, pas de données de cours, pas de journal : un appel de modèle opaque fabrique ses entrées ou les couvre de manière inutile.
Il s'agit de la maladie, qui présente quatre symptômes à mentionner :
- Vous ne pouvez pas lui faire confiance : les données sont inventées, mais de manière fluide.
- Vous ne pouvez pas le tester : le routage de l'étape 4 se trouve dans la prose. Il n'y a pas de
ifpour les tests unitaires. - Vous ne pouvez pas échanger une étape : il n'y a pas de joint où une véritable API météo pourrait se brancher.
- Vous payez tout, à chaque fois : cinq étapes, un appel géant, aucune mise en cache d'une partie déterministe.
Gardez cette sensation. Les neuf niveaux suivants suppriment ces étapes de l'invite, une par une : les fonctions fetch (L1–L2a), une instruction if routes (L2b), les spécialistes divisent le travail (L3a–L3b) et le code délimite la forme (L4a–L4b).

💻 Local : python -m shared.prologue
5. L0 · Votre premier agent ADK 2

⚡ En bref : un agent est un modèle + une instruction + des outils qu'il peut appeler. Un Runner l'exécute. Tout ce qui se trouve au-delà de ce niveau n'est qu'un plus grand nombre d'agents, disposés de manière plus esthétique.
La question : pouvez-vous obtenir une réponse d'un modèle et lui faire utiliser du code réel lorsque l'arithmétique est importante ?
Une idée en trois parties :
Agent: l'élément qui raisonne (un modèle Gemini + une instruction).Runner: élément qui exécute un agent dans une session et diffuse des événements.- un outil : une simple fonction Python (
pace_splits) que le modèle décide d'appeler. L'ADK lit la signature et la docstring, puis transmet une déclaration au modèle. Aucune écriture de schéma n'est nécessaire.
Après le prologue, voici la première réparation : un LLM qui fait des calculs de rythme dans sa tête se trompera volontiers. pace_splits est du Python déterministe, donc les nombres de la réponse sont calculés, et non improvisés.
▶ Colab : exécutez la cellule 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
🔍 Les repères : Agent(...) · tools=[pace_splits] · Runner(...). Dans la sortie, la ligne 🔧 correspond à la décision du modèle d'appeler votre code en cours de réponse.
Ce que vous verrez :
🔧 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...
Les lignes 🔧 sont la leçon : au milieu de la réponse, le modèle a choisi d'appeler votre fonction, et le 8:00/mile exact de sa réponse provient de votre code, et non des statistiques sur les jetons.
❓ Vous vous demandez peut-être si le modèle appelle toujours l'outil. Non, il décide pour chaque question. Posez une question sans aucun chiffre et les lignes 🔧 disparaissent (c'est exactement ce que vous devez faire dans Playground).
👀 Lisez pace_splits (une fonction simple) et la ligne tools=[pace_splits]. · ▶ Exécutez-le. · ✏️ Modification : posez une question générale (sans temps cible). Vous remarquerez que les lignes 🔧 ont disparu : le modèle décide quand il est utile d'appeler un outil. Réécrivez ensuite instruction et réexécutez-le. L'instruction correspond au reste du programme.
6. L1 : Votre premier workflow

⚡ En bref : une fonction simple et un agent LLM sont le même type de nœud. Travail prévisible : fonction (0 LLM, déterministe) ; raisonnement : agent.
La question : comment mélanger du code brut et un LLM dans un même flux, sans payer pour un appel de modèle sur les parties qui ne sont que du code ?
L'idée principale : dans un Workflow, une fonction Python simple et un agent LLM ne sont que des nœuds dans la même liste edges.
START ──► fetch_conditions (function, 0 LLM) ──► advise (agent, 1 LLM)
▶ Colab : exécutez la cellule L1 · 📁 GitHub : L1_graph_basics/ · 💻 Local : python -m L1_graph_basics.workflow

Le nœud de fonction affiche les données qu'il a produites (sans appel de modèle), puis l'agent donne des conseils qui font référence à la température et au vent réels qu'il a reçus :
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)])
🔍 Les marqueurs : un tuple de bordure — (START, fetch_conditions, advise) — avec une fonction Python nue au milieu, et input_schema= validant le transfert.
Nouveautés par rapport à L0 : Workflow(edges=[...]), START (où l'entrée est saisie), un nœud de fonction renvoyant Event(output=...) et input_schema=Conditions afin que la sortie de la fonction soit validée par rapport à ce schéma avant que l'agent ne la voie (en tant que texte JSON : input_schema valide la limite, il ne fournit pas à l'agent un objet Python).
❓ Vous vous demandez peut-être si l'ordre "fonction-puis-agent" est obligatoire. Non. Vous pouvez choisir l'ordre, le mix et le nombre de votre choix. advise s'exécute en deuxième position, car il a besoin des données de fetch_conditions. La leçon est la pairie, pas la séquence.
👀 À lire : fetch_conditions renvoie des données sans appel de modèle, tandis que advise contient input_schema=Conditions. · ▶ Exécutez-le. · ✏️ Modification : définissez temp_f=30 dans la fonction et réexécutez-la. Le conseil est inversé et la fonction coûte toujours 0 appels LLM.
7. L2a · Distribution ramifiée parallèle + JoinNode (Pillar 1a)

⚡ En bref : distribuez les tâches en parallèle (sans frais), attendez que toutes soient terminées, regroupez-les et donnez l'ensemble à un agent.
La question : vous pouvez dessiner le flux avant l'arrivée de l'entrée. Commencez par le squelette : collectez les données en parallèle, regroupez-les et transmettez-les à un agent.
Forme :
START ──► fetch_weather ──┐
START ──► analyze_course ─┼─► JoinNode ─► strategy (1 agent)
START ──► pull_fitness ───┘ (bundles)
▶ Colab : exécutez la cellule L2a · 📁 GitHub : L2a_parallel_join/ · 💻 Local : python -m L2a_parallel_join.workflow

🔍 Les marqueurs : trois bords qui commencent tous à START (c'est ça, la distribution ramifiée) et JoinNode, le point de rencontre.
- Les trois récupérations sont des fonctions qui s'exécutent en parallèle, sans aucun appel de LLM.
JoinNodeattend les trois et les regroupe dans une charge utile typée (BundledRunData), indexée par nom de fonction.- Un agent
strategylit le bundle et écrit unRaceStrategy.
Ce que vous verrez : chaque récupération affiche un code temporel started / finished. Les trois commencent à 0,0 s et la distribution ramifiée se termine à 2,0 s, soit la récupération la plus lente, et non à 4,5 s, qui correspond à la somme de leurs durées. Ce chevauchement correspond au parallélisme. (Le temps écoulé total affiché à la fin est d'environ 8 secondes, car il contient également l'appel LLM de l'agent stratégique. Lisez les codes temporels de récupération pour l'affirmation parallèle, et non le total.)
💡 Rappel du prologue : le méga-prompt a inventé la météo. Ici, la température provient d'une fonction de récupération : code réel, couture réelle. Remplacez le dict prédéfini par une véritable API météo, sans rien changer d'autre.
❓ Vous vous demandez peut-être : combien
JoinNode
dois-je comprendre ? En une phrase : elle attend que chaque branche parallèle atterrisse, regroupe les sorties dans un dictionnaire dont les clés sont les noms des fonctions en amont et ne calcule rien elle-même. C'est précisément ce dictionnaire qui permet au routeur de L2b d'écrire node_input["fetch_weather"]["temp_f"].
👀 Lecture : trois arêtes partent de START ; JoinNode les regroupe pour un agent. · ▶ Exécutez-le et lisez les codes temporels, pas le total. · ✏️ Modification : effectuez une récupération de la veille 3.0. Prédisez d'abord la nouvelle heure de fin de la distribution ramifiée, puis vérifiez-la.
8. L2b · Ajouter le routeur déterministe (pilier 1b)

⚡ En bref : L2a non modifié + un if simple déterminent quel agent s'exécute. Créer des branches sans demander au modèle.
Question : le plan doit être différent selon qu'il fait chaud ou froid. Comment créer une branche sans demander au modèle de décider ?
Forme (L2a + un routeur) :
... JoinNode ─► route_by_weather ─► hot_strategy
(if-statement) ─► normal_strategy
─► cold_strategy
▶ Colab : exécutez la cellule L2b – essayez 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})
🔍 Les marqueurs : Event(output=..., route=...), un nœud de fonction nommant le chemin d'accès, et l'arête de dictionnaire {"HOT": ..., "NORMAL": ..., "COLD": ...} qui mappe les noms aux nœuds.
En résumé : trois types de travail, trois maisons
- Travail prévisible → fonctions (les trois récupérations parallèles)
- Règle claire → Routage explicite (
route_by_weatherest une instructionif, et non une décision du modèle) - Raisonnement → le modèle (exactement un agent de stratégie s'exécute)
Ce que vous verrez : temp=78F -> route=HOT, puis un RaceStrategy structuré. Coût net : un appel LLM.
⚠️ Si vous ajoutez une quatrième branche,ajoutez également une entrée DEFAULT_ROUTE au route-dict. Une route qui ne correspond pas au dictionnaire n'est pas une erreur. La branche se termine simplement et le programme se ferme avec 0 sans sortie, ce qui est une impasse déroutante à déboguer.
❓ Vous vous demandez peut-être : L2b est-il littéralement L2a plus un routeur ? Oui, les récupérations et la jointure sont intactes, et il s'agit toujours exactement d'un appel LLM. Modification : "toujours le même agent" est devenu "l'un des trois, choisi par les données".
👀 Lire : route_by_weather — le routeur est une instruction if, pas un agent. · ▶ Exécutez run("COLD") également. · ✏️ Modification : ajoutez une branche WINDY avec un quatrième agent et lisez l'avertissement DEFAULT_ROUTE ci-dessus avant de le faire.
9. L3a · Collaborative agents: one flag, two worlds — Pillar 2

⚡ En bref : une seule équipe, un seul drapeau. chat transfère l'intégralité de la conversation à un spécialiste et ne revient jamais ; single_turn transforme chaque spécialiste en outil : sous-ensemble parallèle, retour automatique, une synthèse.
La question : vous connaissez l'équipe, mais la demande décide des membres qui doivent répondre. Comment laisser un LLM choisir le sous-ensemble et les exécuter simultanément ?
La forme : un coordinateur pour six spécialistes (médecine, météo, allure, équipement, nutrition, mental). Dans ce niveau, la même équipe est utilisée deux fois (même requête de coordination, mêmes six spécialistes). La seule différence est un indicateur sur les sous-agents. Le contraste est la leçon.
▶ Colab : exécutez la cellule L3a · 📁 GitHub : L3a_collaborative/ · 💻 Local : python -m L3a_collaborative.concierge --mode chat "What about fueling?"

🔍 Les repères : mode="single_turn" dans l'usine et dans la sortie, TRANSFER → (temps 1) par rapport à une série de DISPATCH → lignes partageant un même code temporel (temps 2).
Beat 1 : Exécutez d'abord la valeur par défaut et regardez le job échouer
Aucun mode= écrit → les sous-agents sont définis par défaut sur chat. Nouveautés :
TRANSFER → nutrition_specialist (transfer_to_agent — the only tool chat subagents provide)
Final speaker: nutrition_specialist
Le coordinateur ne dispose d'aucun outil de délégation. Les sous-agents de chat ne lui donnent que transfer_to_agent, un transfert séquentiel de l'intégralité de la conversation à un seul spécialiste. Ce spécialiste répond directement à l'utilisateur, et l'exécution se termine là. Aucune expédition parallèle. Aucun retour. Aucune synthèse. Posez la question générale et la situation empire : six spécialistes, un transfert.
Il ne s'agit pas d'un bug, mais du mode Chat qui fait son travail. La conversation appartient à la personne qui la détient, jusqu'à ce qu'elle soit explicitement transférée. Bonne réponse pour un assistant à réponse ouverte, mais mauvaise réponse pour une étape de pipeline.
Beat 2 : un drapeau, deux mondes
La seule différence : mode="single_turn" pour chaque spécialiste. Même question, exécution à nouveau :
[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>
L'ADK injecte désormais un outil de délégation par spécialiste, nommé d'après le sous-agent et décrit par son description= (le coordinateur lit ce texte lorsqu'il choisit le sous-ensemble ; ignorez-le et basez-vous uniquement sur les noms). Le coordinateur émet plusieurs appels en un tour, l'ADK les exécute en parallèle, chacun renvoyant automatiquement son résultat, et le coordinateur synthétise.
Question | Spécialistes qui tirent |
"Et l'alimentation ?" | nutrition uniquement |
"Mon genou me fait mal au 29e kilomètre" | médical uniquement |
"Dois-je courir aujourd'hui ?" | médical + météo + rythme |
"Y a-t-il quelque chose qui devrait m'inquiéter ?" | les six |
Pourquoi chaque spécialiste reçoit-il l'intégralité du brief ? Chaque sous-agent single_turn s'exécute dans sa propre branche de session isolée. Il ne peut pas voir la conversation ni ses pairs. Rien n'est ambiant : le coordinateur doit transmettre l'intégralité de SpecialistInput (question + stratégie + données du programmeur) séparément à chaque appel parallèle.
💡 Dans ADK 2, cette fonctionnalité est directement intégrée : un LLM sélectionne un sous-ensemble par requête ET l'exécute en parallèle, déclaré via sub_agents + mode="single_turn". Dans la version 1.x, vous pouviez assembler la même forme en encapsulant chaque spécialiste dans AgentTool. La différence est qu'il s'agit désormais d'une déclaration plutôt que d'un raccordement. (ParallelAgent est toujours-tout et transfer_to_agent est sériel.)
⚠️ Deux mises en garde honnêtes : (1) le modèle choisit le sous-ensemble, il est donc moins déterministe que le routeur codé en dur de L2. Le sous-ensemble exact peut varier d'une exécution à l'autre. (2) Il arrive qu'une ligne Error validating input: ... s'affiche pour un spécialiste. Il s'agit presque toujours de la sortie du spécialiste : output_schema permet à Gemini d'appliquer cette règle côté serveur. Il s'agit de l'entrée : le coordinateur doit reproduire l'intégralité de l'SpecialistInput imbriqué à l'identique pour chaque appel parallèle, et il arrive qu'il en oublie un. L'ADK renvoie l'erreur en tant que résultat de cet outil, le coordinateur récupère et la synthèse est toujours effectuée.
❓ Vous vous demandez peut-être : est-ce que
chat
just 1.x-style delegation — one agent at a time? Oui, en gros : il s'agit du comportement par défaut de la version 1.x, mais avec un nom. L'écart par rapport à single_turn est tridimensionnel : ce que le coordinateur détient (un transfer_to_agent par rapport à un outil par spécialiste), le nombre de personnes pouvant travailler (une, propriétaire de la conversation, par rapport à N en parallèle) et le retour du contrôle (jamais par rapport à automatiquement, avec des résultats). À propos du code : la branche if mode == de l'usine n'existe que pour qu'une équipe puisse être créée de deux manières pour ce contraste. Une application réelle code en dur un mode et if disparaît.
👀 Lire : l'usine _specialist – le paramètre mode correspond à l'ensemble du niveau. · ▶ Exécutez les deux rythmes. · ✏️ Modification : demande "mon genou me fait mal au 29e kilomètre" — prédisez d'abord le sous-ensemble, puis vérifiez les lignes DISPATCH.
# 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 · Mode tâche : une conversation avec une ligne d'arrivée — Pilier 2

⚡ En bref : le mode intermédiaire consiste à parler à l'utilisateur jusqu'à ce que les champs soient collectés, puis à revenir automatiquement avec un objet validé.
La question : L3a a laissé un espace vide. chat est propriétaire de l'intégralité de la conversation, tandis que single_turn ne s'adresse jamais à l'utilisateur. Mais le véritable travail d'ingestion se situe entre les deux : "parlez à l'utilisateur JUSQU'À ce que vous ayez collecté X, puis revenez avec un objet validé". Quel mode est-ce ?
Forme :
race_desk (coordinator)
└─ gear_fitter (mode="task", output_schema=GearOrder)
▶ Colab : exécutez la cellule L3b · 📁 GitHub : L3b_task_desk/ · 💻 Local : python -m L3b_task_desk.desk


🔍 Les repères : mode="task" + output_schema= sur le même agent, et dans la sortie, l'appel ⏸ Pause et finish_task.
Ce que vous verrez :
━━ 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.
Trois choses se sont produites que le mode L3a ne peut pas faire :
- L'exécution s'est réellement arrêtée en cours de tâche : il s'agit d'une tâche mise en veille, et non d'un blocage ou d'un échec. L'agent a posé sa question de clarification et a laissé la tâche ouverte. (Dans
adk web, il vous suffit de saisir la réponse. Le script de test la saisit en tant que deuxième message dans la même session.) - Le message suivant a repris le MÊME agent de tâches : aucun réacheminement ni aucune nouvelle délégation. La session sait qui attendait.
finish_taska mis fin à l'opération : un outil ADK injecté parce quemode="task". L'agent doit l'appeler pour terminer, et sa charge utile doit être validée par rapport àoutput_schema. Une conversation avec une ligne d'arrivée saisie au clavier : le contrôle revient automatiquement au coordinateur, et le résultat est joint.
La règle d'une question pour choisir un mode
💡 "L'utilisateur doit-il lui parler, et jusqu'à QUAND ?" chat = indéfiniment ; task = jusqu'à ce que les champs soient collectés ; single_turn = jamais.
Mode | Human-in-the-loop (avec intervention humaine) | Parallèle ? | Retour au parent |
| conversation complète | non | manuelle (par transfert) |
| questions de clarification uniquement | non | automatique (via |
| aucun | oui | automatique (avec son résultat) ; |
mode ne s'applique qu'aux sous-agents, jamais au coordinateur. Les nœuds de workflow sont définis par défaut sur single_turn (c'est pourquoi les niveaux 1 à 2b ne l'ont jamais écrit), tandis que les sous-agents sont définis par défaut sur chat (c'est pourquoi le niveau 3a a dû le faire).
⚠️ Deux notes sur les versions avant de commencer : (1) task
en tant que nœud de graphique statique dépend de la version : sur 2.0.0b1–2.3.0 (version épinglée de cet atelier de programmation), Workflow(...) se déclenche lors de la construction. Utilisez exactement ce que fait ce niveau (un coordinateur de chat avec des sous-agents de tâches) ou distribuez-le via ctx.run_node. Supprimé dans la version 2.5.0. (2) L'erreur "Task agents must be leaf agents" (les agents de tâches doivent être des agents feuilles) est une limite ADK documentée, mais un contrat, et non une protection d'exécution : ni la version 2.3.0 ni la version 2.5.0 ne vous arrêteront. Ne considérez pas l'absence d'erreur comme une autorisation.
💡 En savoir plus : agent task intégré à un workflow de graphe (forme 2.5.0+), avec un routage qui peut relancer la conversation pour une nouvelle tentative : dépôt associé 22_agent_in_workflow · guide du mode complet : docs/agent-modes.md.
❓ Vous vous demandez peut-être : que signifie
task
m'acheter ce que les deux autres ne peuvent pas ? Trois choses : retour automatique (la conversation est interrompue) ; ligne d'arrivée saisie (la charge utile de finish_task doit être validée par rapport au schéma : vous obtenez des données, pas une transcription) ; pause/reprise (⏸ est une tâche en attente d'une intervention humaine, pas un blocage).
👀 À lire : gear_fitter – mode="task" + output_schema correspond à l'intégralité du contrat. · ▶ Exécutez-le. · ✏️ Modifier : run_desk("I need a hydration vest", "2 liters, medium") la question de clarification s'adapte, la ligne d'arrivée reste saisie.
11. L4a : distribution ramifiée parallèle dimensionnée au moment de l'exécution (axe 3a)

⚡ En bref : le squelette comporte toujours trois étapes statiques. L'étape dynamique se cache à l'intérieur de celle du milieu, où la largeur est déterminée par les données au moment de l'exécution.
⚠️ Attention : il s'agit de l'étape la plus difficile. Le niveau précédent comportait 44 lignes, celui-ci environ 120 (trois agents et deux nœuds de workflow, sans aucune marge). Prévoyez environ 15 minutes et appuyez-vous sur la ligne "Lire/Exécuter/Modifier" à la fin : vous n'avez pas besoin de comprendre chaque ligne du premier coup.
La question : la forme de l'œuvre dépend de l'entrée. Vous ne pouvez pas dessiner le graphique à l'avance. Commencez par la largeur de l'exécution : laissez le LLM décider du nombre de sous-questions.
Forme (un niveau de profondeur) :
START ─► decompose ─► research_topic (parallel_worker) ─► synthesize
│ │ │
└──┴──┴─ (flat: no children yet)
Une question ouverte est décomposée en N sous-questions (N est choisi par le LLM au moment de l'exécution, entre 3 et 7). Chaque sous-question fait l'objet d'une recherche en parallèle, puis est synthétisée en un seul briefing.
▶ Colab : exécutez la cellule L4a · 📁 GitHub : L4a_flat_research/ · 💻 Local : python -m L4a_flat_research.deep_research

🔍 Les repères : il n'y en a pas
dynamic=True
switch. "Dynamique" est une façon d'écrire, pas une configuration. Deux marqueurs, et seulement deux : @node(parallel_worker=True) (prend une liste de taille d'exécution, exécute un nœud de calcul par élément) et ctx.run_node(...) (planifie directement les nœuds de code). Si vous voyez l'une ou l'autre de ces options, vous êtes en mode dynamique.
Ce que vous verrez : le décomposeur imprime par exemple cinq sous-questions, il effectue des recherches en parallèle, puis il fournit un briefing synthétisé. Le nombre diffère à chaque exécution, ce que le graphique fixe ne pouvait pas faire.
Deux indicateurs sur le nœud de calcul sont à connaître :
rerun_on_resume=Trueest obligatoire sur tout nœud qui appellectx.run_node. Sans cela, ADK génère uneValueError. Lors de la reprise, il doit réexécuter le nœud de répartition pour reconstruire les enfants qu'il a générés, car ceux-ci ne figurent pas dans le graphique statique.retry_config=bounds how this FAILS. Un nœud de calcul parallèle annule tous les nœuds de calcul frères et relance l'opération instantanément en cas d'échec d'un nœud de calcul enfant. Ainsi, sans nouvelle tentative, un seul code d'erreur 429 transitoire annule l'ensemble de l'exécution, y compris tous les appels déjà payés. La tentative de relance se produit au niveau du nœud interne par élément. Chaque branche effectue donc une nouvelle tentative de manière indépendante.
❓ Vous vous demandez peut-être : comment l'ADK "sait-il" que c'est dynamique ? Elle n'en a pas besoin, car rien n'est déclaré nulle part. Le décomposeur produit une liste au moment de l'exécution. Le nœud de calcul parallèle s'adapte à ce qui arrive. Le dynamisme est une propriété du flux de données que vous avez écrit, et non un mode que vous avez activé.
👀 Lire : les deux indicateurs sur research_topic : parallel_worker et rerun_on_resume. · ▶ Exécutez-le. · ✏️ Modifier : remplacez la question ouverte par la vôtre. N change, car la saisie a déterminé la largeur.
12. L4b · Ajouter la génération récursive (Pillar 3b)

⚡ En bref : la récursivité est écrite, pas donnée. Le nœud de calcul s'appelle lui-même via ctx.run_node, un Python ordinaire. Le frein doit donc également être écrit. Il s'agit de la valeur MAX_DEPTH.
La question : parfois, un résultat de recherche fait apparaître un sous-thème spécifique qui mérite une étude à part entière. Comment faire en sorte qu'une branche génère plus de travail en parallèle tout en le gardant limité ?
Forme (désormais récursive) :
START ─► decompose ─► research_topic (parallel_worker, recursive) ─► synthesize
│ │ │
│ │ └─ research(q3) ─► maybe spawn children
│ └─── research(q2) ─► maybe spawn children
└────── research(q1) ─► maybe spawn children
▶ Colab : exécutez la cellule 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})
🔍 Les marqueurs : ctx.run_node(research_topic, ...) à l'intérieur de research_topic lui-même : l'auto-référence is est la récursion, et la garde depth < MAX_DEPTH se trouve une ligne au-dessus.
Ce que vous verrez : des nœuds de recherche affichant spawning N deeper (récursion en direct), puis une forme d'arborescence d'exécution (par exemple, 5 top-level + 10 recursive children). L'arborescence diffère à chaque exécution.
⚠️ Avant de passer à la vitesse supérieure : le plafond augmente rapidement. MAX_DEPTH=3 passe du pire scénario d'environ 30 appels à environ 93. À la toute fin d'une exécution, vous pouvez voir une ligne de journal cancelling N leftover tasks : cela signifie que l'ADK décompose son groupe de tâches parallèles une fois le résultat terminé. Il s'agit d'un message inoffensif. Selon votre configuration de journalisation, il est possible que vous ne le voyiez jamais.
❓ Vous vous demandez peut-être si le remarketing dynamique n'est pas récursif par défaut. Non. L4a est entièrement dynamique et ne comporte aucune récursion. Dynamic ne vous donne que le flux de contrôle Python ordinaire ; L4b choisit d'écrire la récursivité avec. Et comme vous avez écrit la récursion, vous devez écrire sa limite. C'est là que "laissez le LLM façonner le travail, gardez les limites dans le code" cesse d'être un slogan.
👀 Lu : la protection : if finding.needs_deeper and depth < MAX_DEPTH. · ▶ Exécutez-le. · ✏️ Modification : définissez MAX_DEPTH = 1 et réexécutez. L'arborescence est aplatie (et l'exécution devient moins coûteuse). La limite est la VÔTRE, dans le code.
13. L5 · Quel modèle utiliser ?

⚡ En bref : un axe décide de tout, qui choisit la prochaine étape : le graphique que vous avez dessiné, le LLM ou votre code.
Vous avez créé les trois. Le modèle qui les rend utiles est le suivant : faire correspondre le modèle à la forme de votre problème.
L'axe : qui décide de ce qui sera exécuté ensuite ?
Pilier | Qui décide de ce qui sera diffusé ensuite ? | Intégré |
1 · Graphique | le graphique que vous avez dessiné ; | L2a / L2b |
2 · Collaboration | le LLM | L3a / L3b |
3 · Dynamique | votre code Python, au moment de l'exécution. | L4a / L4b |
Étape 0 : Avez-vous vraiment besoin d'un graphique ?
ADK fournit des agents de workflow prédéfinis : SequentialAgent, ParallelAgent, LoopAgent. Pour une chaîne d'agents simple, il s'agit de la réponse correcte la moins chère et il n'y a pas de graphique à assembler. Contactez-les lorsque vous avez besoin d'un routage explicite (routeur L2b), d'une jointure (JoinNode L2a) ou de nœuds qui ne sont pas des agents (une fonction simple, sans appel LLM). C'est généralement la dernière option qui est la cause.
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)

Comparaison honnête entre 1.x et 2
Il ne s'agit pas de dire que la version 2.0 peut faire des choses que la version 1.x ne pouvait pas faire. La version 1.x pouvait tout faire. La version 2.0 attribue à chaque forme un emplacement plus direct. Ainsi, le flux de contrôle connu quitte l'invite et devient une structure que vous pouvez voir et tester.
Schéma | Coût de la version 1.x | Accueil de l'ADK 2 |
Graphes | 4 appels LLM dans la compilation courante ; routage masqué dans un prompt | Nœuds de fonction et d'agent en tant que pairs → 1 appel, routeur d'instruction |
Collaboratif | buildable via | une équipe déclarée : |
Dynamique | La récursivité vous fait sortir du framework |
|
L'ensemble de l'application et ce qu'un graphique ne peut pas vous montrer
Vous avez maintenant créé chaque élément ci-dessous. Workflow expose sa structure à graph.edges. Cette image est donc générée à partir du code plutôt que dessinée à la main. Ce que l'introspection trouve est le résumé de cet atelier :
Pilier | Contenu de | Pourquoi |
1. Graphique (L2b) | 10 arêtes, itinéraires et tout le reste | vous l'avez dessiné avant l'arrivée de toute entrée. |
2 · Collaboratif (L3a) | 0 bordure : uniquement | Le LLM sélectionne le sous-ensemble par requête. |
3) Dynamique (L4a/L4b) | 3 arêtes (identiques dans les deux) | la récursivité est écrite en Python, et non câblée dans le graphique. |
Cette dernière ligne est la preuve de la réponse à la question L4b : L4a et L4b ont le même graphique, et une seule d'entre elles est récursive.
Ce que vous pouvez créer
Chaque motif que vous venez d'exécuter est une forme de produit réelle :
Vous avez pratiqué | Dans la nature, | Lieu de départ |
Graphique + routeur (L2a/L2b) | Pipelines de documents, étapes ETL avec LLM, chaînes d'examen/d'approbation, harnais d'évaluation | L2b de ce dépôt |
Coordinateur + équipe | un copilote d'assistance avec des équipes spécialisées, des bureaux de triage et un examen multi-lentilles. | Démo marathon mode 2 |
Agents | Formulaires d'inscription, parcours de réservation, onboarding, KYC : tout ce qui consiste à "collecter des données, puis agir" | |
Largeur/profondeur dynamique (L4a/L4b) | agents de recherche, générateurs de rapports, balayages d'audit sur des entrées de taille inconnue. | Mode 3 : démo marathon |
Ils composent
Les trois modèles ne s'excluent pas mutuellement. Un nœud de graphique peut appeler un coordinateur collaboratif, et un spécialiste peut lancer un workflow dynamique. Choisissez le bon modèle pour chaque partie du problème. C'est ainsi que vous éviterez de transformer chaque système d'agent en un seul prompt géant.

💡 Essayez-le sur votre propre workflow : le script qui a généré ce graphique est scripts/graph_dump.py. Pointez-le sur n'importe quel Workflow et il imprimera les bords réels, ce qui vous donnera un schéma structurel sans frais de tout ce que vous construisez.
14. Félicitations

Vous avez créé un coach pour le jour de la course et, au passage, les trois modèles d'orchestration d'ADK 2.
Ce que vous avez appris
- Prologue : le méga-prompt qui a inventé sa propre météo, ou pourquoi la structure existe.
- L0–L1 :
Agent,Runner, un outil réel que le modèle choisit d'appeler, et votre premierWorkflow(nœuds de fonction + nœuds d'agent en tant que pairs). - L2a / L2b : workflows de graphes : distribution ramifiée parallèle +
JoinNode, puis routage déterministe : un appel LLM. - L3a : agents collaboratifs. La même équipe s'exécute dans
chat(échoué) puis danssingle_turn(sous-ensemble parallèle + synthèse) : un indicateur, deux mondes. - L3b : mode
task: une question de clarification mise en pause, une reprise scriptée,finish_taskrenvoyant un objet validé. - L4a / L4b : workflows dynamiques : largeur d'exécution (distribution ramifiée), puis profondeur d'exécution (récursion) avec des limites dans le code.
- L5 : l'arbre de décision et la façon dont les modèles se composent.
Lignes à conserver
Les fonctions préparent le contexte. Les arêtes définissent le workflow. Le routeur choisit le chemin. Le modèle rédige la réponse.
Laissez le LLM façonner le travail, mais gardez les limites dans le code.
Faites correspondre le modèle à la forme de votre problème.
Étapes suivantes
- Exécutez l'application complète à partir de laquelle ces niveaux ont été extraits : Marathon Race Day Coach, une compilation FastAPI + SSE avec une interface utilisateur de navigateur affichant les trois modes en direct : github.com/cuppibla/adk-2-marathon-demo.
- Élargissez vos connaissances : adk-workflows-compared : les 23 exemples de workflow ADK 2 officiels, chacun avec un port 1.x et des conseils d'utilisation. Commencez par
docs/three-pillars.md, puis par les éléments que cet atelier de programmation a ignorés :07_loop,17_request_inputet22_agent_in_workflow. - Portez votre propre problème : quelles parties sont de structure connue (L2), d'équipe connue (L3a/L3b) et de forme inconnue (L4) ?
- Explorez le code : github.com/cuppibla/adk2-tutorial.
- Vous avez suivi l'atelier ? Votre crédit et le projet qu'il a créé ne sont pas éternels. Pour continuer à exécuter ces niveaux sans frais, suivez plutôt l'étape Configuration à emporter : une clé AI Studio sans frais, sans projet Cloud ni facturation. Le seul changement consiste à inverser ces deux cellules.