Créer un studio créatif multi-agents avec la pile d'agents de Google : ADK, A2A, MCP sur Cloud Run et Agent Runtime

1. Présentation

Dans cet atelier de programmation, vous allez créer AI Creative Studio, un système multi-agents distribué qui transforme une simple requête en une campagne Instagram complète.

Saisissez une phrase. Obtenez des études d'audience, des sous-titres, des concepts visuels, des textes de qualité et une chronologie complète du projet, le tout généré par une équipe d'agents IA collaboratifs.

Les agents que vous allez créer

Agent

Rôle

Responsable de la stratégie de marque

Recherche sur le Web des insights sur l'audience, des analyses de la concurrence et des tendances pour 2025

Copywriter

Rédige des légendes Instagram avec des hashtags et des CTA, grâce à une compétence ADK qui charge les consignes de la plate-forme et les formules de légendes à la demande

Designer

Crée des concepts visuels et génère des images réelles via Gemini, stockées dans GCS

Critique

Examen du texte et des éléments visuels des avis : renvoie APPROVED ou NEEDS_REVISION avec des commentaires spécifiques

Chef de projet

Établit un calendrier de projet et une répartition des tâches, éventuellement synchronisés avec Notion via MCP

Directeur artistique

Orchestre les cinq spécialistes dans l'ordre : vous ne lui donnez qu'une seule requête, et il coordonne le reste.

Les cinq agents sont déployés en tant que microservices Cloud Run indépendants. Ils communiquent via le protocole A2A, une norme ouverte indépendante de la langue qui permet à n'importe quel agent d'appeler n'importe quel autre agent, quel que soit le framework. Le Creative Director s'exécute sur Agent Runtime et se connecte à chaque spécialiste à distance.

Architecture

Présentation du système

Points abordés

  • Créez des agents LLM avec Google ADK : Agent, instructions système et outils intégrés.
  • Empaquetez les connaissances réutilisables de l'agent dans des fichiers modulaires avec les compétences ADK (SkillToolset).
  • Générez des images réelles en associant un agent textuel à un modèle d'image via un FunctionTool.
  • Intégrez des API externes sans code de colle personnalisé à l'aide du protocole MCP (Model Context Protocol).
  • Transformez n'importe quel agent en service appelable sur le réseau à l'aide du protocole Agent to Agent (A2A) sur HTTPS.
  • Orchestrez des agents distribués avec RemoteA2aAgent et AgentTool.
  • Empaqueter et déployer des agents indépendants en tant que microservices Cloud Run.
  • Hébergez un orchestrateur avec état sur Agent Runtime.
  • Maintenez les longs workflows multi-agents dans les limites de contexte à l'aide de la compression du contexte.
  • Créez une boucle de contrôle qualité : les critiques de films génèrent des révisions automatiques si nécessaire.

Prérequis

  • Un projet Google Cloud pour lequel la facturation est activée
  • Rôle IAM Propriétaire ou Éditeur
  • Connaissances de base en Python

2. Configurer votre environnement

Pour cet atelier de programmation, nous allons utiliser Cloud Shell.

Qu'est-ce que Cloud Shell ?

Cloud Shell est un environnement Linux sans frais basé sur un navigateur, avec tout ce dont vous avez besoin préinstallé : gcloud, git, Python, Docker et plus encore. Vous n'avez rien à installer en local.

Pour ouvrir Cloud Shell, cliquez sur l'icône de terminal dans la barre d'outils en haut à droite de la console GCP :

Ouvrez Cloud Shell à partir de la barre d'outils de la console GCP.

Lorsque vous ouvrez Cloud Shell pour la première fois, vous êtes invité à valider votre compte. Cliquez sur Valider :

Boîte de dialogue "Valider votre compte"

Cliquez ensuite sur Autoriser pour permettre à Cloud Shell d'effectuer des appels d'API Google Cloud :

Boîte de dialogue "Autoriser Cloud Shell"

Cloud Shell est maintenant prêt. Un message de bienvenue s'affiche dans le terminal : Terminal Cloud Shell prêt

S'authentifier et configurer votre projet

Cloud Shell est déjà authentifié avec votre compte Google. Confirmez votre compte actif et trouvez votre ID de projet :

gcloud config list

Vous pouvez également voir votre ID de projet dans le tableau de bord de la console GCP, dans le panneau latéral de gauche. Copiez-le, car vous en aurez besoin dans la commande suivante :

Recherchez votre ID de projet dans la console GCP et définissez-le dans Cloud Shell.

Définissez maintenant votre projet :

export PROJECT_ID=$(gcloud config get-value project)
export REGION="us-central1"        # Cloud Run deployment region
echo "Project: $PROJECT_ID"

Résultat attendu :

Project: my-project-123

Activer les API requises

gcloud services enable \
    aiplatform.googleapis.com \
    apphub.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    generativelanguage.googleapis.com \
    iam.googleapis.com \
    cloudresourcemanager.googleapis.com \
    storage.googleapis.com \
    secretmanager.googleapis.com

Cela prend environ deux minutes. Operation finished successfully s'affiche une fois l'opération terminée.

Configurer les identifiants par défaut de l'application (ADC)

Les agents appelleront Gemini Enterprise Agent Platform à l'aide de la bibliothèque Google Auth, qui nécessite des identifiants par défaut de l'application, distincts de l'authentification gcloud CLI.

Exécutez cette commande une fois :

gcloud auth application-default login

Un onglet de navigateur s'ouvre et vous invite à confirmer. Cliquez sur Autoriser. Cette page vous indique les informations suivantes :

Credentials saved to file: ~/.config/gcloud/application_default_credentials.json

Cloner le dépôt de démarrage

Cet atelier de programmation utilise un dépôt de démarrage, c'est-à-dire un projet squelette avec toute l'infrastructure en place (Dockerfiles, pyproject.toml, scripts de déploiement), mais avec la logique de l'agent à écrire.

git clone https://github.com/Saoussen-CH/mas-a2a-gcp.git ~/ai-creative-studio
cd ~/ai-creative-studio/workshop/starter

Chaque agent.py contient des espaces réservés # TODO où vous écrirez la logique de l'agent. Les scripts Dockerfile, pyproject.toml et de déploiement sont déjà complets.

Configurer les variables d'environnement

Copiez l'exemple fourni et insérez votre ID de projet en une seule étape :

cp .env.example .env
sed -i "s|GOOGLE_CLOUD_PROJECT=your-project-id|GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)|" .env

Créez ensuite le bucket GCS dans lequel le Designer stockera les images générées, puis remplacez .env par son nom :

export PROJECT_ID=$(gcloud config get-value project)
export BUCKET_NAME="${PROJECT_ID}-campaign-images"

gcloud storage buckets create gs://${BUCKET_NAME} \
    --location=us-central1 \
    --project=${PROJECT_ID}

sed -i "s|GCS_IMAGES_BUCKET=your-project-id-campaign-images|GCS_IMAGES_BUCKET=${BUCKET_NAME}|" .env

Configurez ensuite la prise en charge des URL d'image signées. Le directeur de la création génère des liens HTTPS cliquables pour chaque image dans le récapitulatif final de la campagne. Pour cela, un compte de service est nécessaire pour signer les URL. Exécutez les commandes suivantes pour le configurer :

export PROJECT_NUMBER=$(gcloud projects describe $(gcloud config get-value project) --format="value(projectNumber)")
export SA_EMAIL="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export AGENT_RUNTIME_SA="service-${PROJECT_NUMBER}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"

# Allow your user account to sign URLs locally (adk web)
gcloud iam service-accounts add-iam-policy-binding ${SA_EMAIL} \
  --member="user:$(gcloud config get-value account)" \
  --role="roles/iam.serviceAccountTokenCreator"

# Allow Agent Runtime to sign URLs when deployed
gcloud projects add-iam-policy-binding $(gcloud config get-value project) \
  --member="serviceAccount:${AGENT_RUNTIME_SA}" \
  --role="roles/iam.serviceAccountTokenCreator"

# Save SA email and project number to .env
grep -q "^SIGNING_SERVICE_ACCOUNT" .env \
  && sed -i "s|^SIGNING_SERVICE_ACCOUNT=.*|SIGNING_SERVICE_ACCOUNT=${SA_EMAIL}|" .env \
  || echo "SIGNING_SERVICE_ACCOUNT=${SA_EMAIL}" >> .env

grep -q "^GOOGLE_CLOUD_PROJECT_NUMBER" .env \
  && sed -i "s|^GOOGLE_CLOUD_PROJECT_NUMBER=.*|GOOGLE_CLOUD_PROJECT_NUMBER=${PROJECT_NUMBER}|" .env \
  || echo "GOOGLE_CLOUD_PROJECT_NUMBER=${PROJECT_NUMBER}" >> .env

Ouvrez .env dans l'éditeur pour examiner tous les paramètres :

cloudshell edit .env

.env s'ouvre sous forme d'onglet dans l'éditeur Cloud Shell. Si le panneau de l'éditeur n'est pas visible, cliquez sur le bouton Ouvrir l'éditeur dans la barre d'outils :

Cliquez sur "Ouvrir l'éditeur" dans la barre d'outils Cloud Shell.

Éditeur Cloud Shell avec l'arborescence des fichiers du projet

Vérifiez que le projet a été correctement défini :

grep GOOGLE_CLOUD_PROJECT .env

Installer des dépendances

Nous utilisons uv, un gestionnaire de packages Python moderne et rapide qui gère les environnements virtuels et les installe dans un seul outil. Il est environ 10 à 100 fois plus rapide que pip et constitue la méthode recommandée pour gérer les projets Python.

uv est déjà installé dans Cloud Shell. Tous les agents partagent les mêmes dépendances de base. Vous n'avez donc besoin de les installer qu'une seule fois pour qu'elles fonctionnent pour chaque agent de cet atelier de programmation :

uv sync

La commande uv sync lit pyproject.toml et crée un répertoire .venv/ avec toutes les dépendances. Chaque spécialiste possède également son propre pyproject.toml, qui est utilisé exclusivement par les compilations Docker. L'installation partagée ci-dessus couvre tout ce dont vous avez besoin pour les tests locaux.

3. Comprendre Google ADK

Avant d'écrire du code, découvrons l'Agent Development Kit (ADK), le framework que vous utiliserez pour créer chaque agent dans cet atelier de programmation.

Qu'est-ce que l'ADK ?

Agent Development Kit (ADK) est un framework flexible et modulaire permettant de développer et de déployer des agents IA. Bien qu'optimisé pour Gemini et l'écosystème Google, l'ADK est indépendant des modèles et des déploiements, et est conçu pour être compatible avec d'autres frameworks. ADK a été conçu pour que le développement d'agents ressemble davantage au développement logiciel. Il permet aux développeurs de créer, de déployer et d'orchestrer plus facilement des architectures agentiques allant de tâches simples à des workflows complexes.

ADK gère les parties complexes (appel d'outils, conversation multitours, gestion du contexte, streaming), ce qui vous permet de vous concentrer sur la logique de l'agent.

Composants de base d'un agent ADK

Chaque agent est composé de quatre éléments de base :

Bloquer

Rôle

Modèle

LLM qui raisonne sur les objectifs, détermine un plan et génère des réponses

Outils

Fonctions qui récupèrent des données ou effectuent des actions en appelant des API ou des services

Orchestration

Maintient la mémoire et l'état au fil des tours, achemine les appels d'outils, renvoie les résultats au modèle

Runtime (durée d'exécution)

Exécute le système lorsqu'il est appelé, en local via adk web ou en tant que service déployé

Définition de l'agent

Chacun des cinq agents de cet atelier de programmation est défini de la même manière :

from google.adk.agents import Agent
from google.adk.tools.google_search_tool import google_search

root_agent = Agent(
    name="brand_strategist",                              # unique identifier
    model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"), # the LLM powering this agent
    instruction=SYSTEM_INSTRUCTION,                       # the agent's persona, constraints, and output format
    description="Brand strategist for market research, trend analysis, and competitive insights",
    tools=[google_search],                                # functions the LLM can call
)

Champ

Objectif

name

ID unique : utilisé par les orchestrateurs pour acheminer les appels

model

Le modèle Gemini qui alimente cet agent

instruction

Invite système : définit le rôle, les contraintes et le format de sortie de l'agent

description

Résumé en une ligne : l'orchestrateur le lit pour décider quel spécialiste appeler.

tools

Fonctions que le LLM peut appeler (intégrées comme google_search ou fonctions Python personnalisées)

Comment ADK exécute un agent

User message
     
     
  Agent (LLM)   reads instruction + conversation history
     
     ├─► needs more info?  calls a tool  gets result  continues reasoning
     
     └─► done reasoning  returns final text response

Le LLM décide de manière autonome s'il doit appeler un outil, lequel et avec quels arguments. Vous écrivez l'instruction, et ADK s'occupe du reste.

4. Créer et tester l'agent Brand Strategist

Commençons par le premier agent : le stratège de marque. Cet agent de recherche uniquement permet de rechercher des insights sur l'audience cible, d'analyser la concurrence et de trouver des sujets tendance à l'aide de la recherche Google.

Ouvrez le fichier squelette de l'agent dans l'éditeur Cloud Shell :

cloudshell edit agents/brand_strategist/agent.py

Deux sections # TODO s'affichent. Vous devez les remplir.

TODO 1 : Écrire l'instruction système

Vous allez d'abord écrire l'instruction système pour l'agent. L'instruction système est une chaîne qui définit le rôle, les contraintes et le format de sortie de l'agent.

SYSTEM_INSTRUCTION = f"""You are a Brand Strategist specializing in market research and trend analysis.

IMPORTANT: Today's date is {datetime.date.today().strftime("%B %d, %Y")}.
When conducting research, focus on current trends from {datetime.date.today().year}.
Use search queries like "[topic] trends {datetime.date.today().year}" for recent insights.

IMPORTANT: Your role is RESEARCH ONLY. You do NOT create campaign content, captions, or designs.
After providing research insights, your work is complete.

Your expertise:
- Identifying target audience insights and behaviors
- Analyzing competitor strategies
- Researching current social media trends
- Understanding platform algorithms and best practices

You have access to:
- google_search: Search the web for competitors, trends, and market insights

When given a campaign brief:
1. Use google_search to research the target audience's current interests
2. Search for and analyze 2-3 competitor brands
3. Identify 3-5 trending topics related to the product category
4. Provide high-level strategic insights - NOT specific campaign content

DO NOT create captions, copy, designs, or any campaign content.

Format your output as:
**Audience Insights:**
[Key behaviors and preferences based on research]

**Competitive Analysis:**
[What 2-3 competitors are doing - strengths and weaknesses]

**Trending Topics:**
[3-5 relevant trends to consider]

**Key Strategic Insights:**
[High-level themes and positioning opportunities]
"""

TODO 2 : Créer le root_agent

Ensuite, remplacez le root_agent incomplet par :

root_agent = Agent(
    name="brand_strategist",
    model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
    instruction=SYSTEM_INSTRUCTION,
    description="Brand strategist for market research, trend analysis, and competitive insights",
    tools=[google_search],
)

Tester localement avec l'UI Web d'ADK

Testons maintenant l'agent à l'aide de l'UI Web d'ADK, une interface de chat intégrée permettant de tester les agents avant de les déployer dans le cloud.

uv run adk web agents --allow_origins='*'

Cette page vous indique les informations suivantes :

INFO: Started server process
INFO: Uvicorn running on http://localhost:8000

Le serveur s'exécute désormais dans Cloud Shell :

Pour l'ouvrir dans votre navigateur, utilisez Aperçu sur le Web :

  1. Consultez la barre d'outils Cloud Shell en haut de la page.
  2. Cliquez sur l'icône Aperçu sur le Web (qui ressemble à une boîte avec une flèche vers le haut, en haut à droite de la barre d'outils Cloud Shell).
  3. Cliquez sur Modifier le port, saisissez 8000, puis cliquez sur Modifier et prévisualiser.

L'UI Web d'ADK s'ouvre dans un nouvel onglet du navigateur. Cliquez sur le menu déroulant Sélectionner un agent en haut à gauche. La liste de tous vos agents s'affiche :

Sélectionnez brand_strategist pour commencer le test :

Essayer ces requêtes de test

Dans la zone de chat de l'UI Web ADK, essayez :

  • Research the eco-friendly water bottle market for health-conscious millennials
  • What are the top Instagram trends in the wellness space in 2025?

L'agent doit appeler la recherche Google et renvoyer une recherche structurée avec des sections "Insights sur l'audience", "Analyse de la concurrence" et "Tendances".

5. Créer la compétence Copywriter – ADK Skills

Rôle : transforme des études sur les marques en légendes Instagram. Le rédacteur crée trois variantes de légende couvrant différents tons (inspirant, éducatif, communautaire), chacune avec des hashtags et un CTA.

Concept : compétences ADK

Une approche naïve consisterait à intégrer toutes les connaissances de la plate-forme (limites de caractères, niveaux de hashtags, formules de légende, exemples de voix de marque) directement dans le prompt système. Cela fonctionne, mais chaque requête est gonflée avec du contenu dont l'agent n'a besoin qu'occasionnellement.

Les compétences ADK (SkillToolset, introduites dans ADK 1.25.0) vous permettent de regrouper ces connaissances dans des fichiers modulaires avec trois niveaux de chargement :

  • L1 – Frontmatter (name + description dans SKILL.md) : toujours disponible, utilisé pour la découverte des compétences
  • L2-instructions (corps de SKILL.md) : chargées lorsque l'agent déclenche la compétence
  • Ressources de niveau 3 (fichiers references/ et assets/) : chargées uniquement lorsque l'agent les lit explicitement

L'instruction système est réduite à une brève déclaration de rôle, plus "charge la compétence avant d'écrire". Les informations sur la plate-forme ne sont incluses dans la fenêtre de contexte que lorsque l'agent en a réellement besoin.

La compétence Copywriter se trouve dans agents/copywriter/skills/instagram-copywriting/ :

skills/
  instagram-copywriting/
    SKILL.md                        L1 frontmatter (discovery) + L2 instructions (loaded on trigger)
    references/
      platform-guide.md             L3: character limits, hashtag tiers, algorithm signals
      caption-formulas.md           L3: hook formulas, CTA patterns, full caption structures
    assets/
      brand-voice-examples.md       L3: annotated real-world caption examples

Ouvrez le fichier directement dans l'éditeur Cloud Shell :

cloudshell edit agents/copywriter/agent.py

TODO 1 : Importer load_skill_from_dir et skill_toolset

Recherchez le commentaire # TODO 1: Import load_skill_from_dir and skill_toolset et ajoutez les deux importations :

from google.adk.skills import load_skill_from_dir
from google.adk.tools import skill_toolset

TODO 2 : Charger la compétence et créer un SkillToolset

Recherchez les deux commentaires sous les importations :

# TODO 2: Load the instagram-copywriting skill from the skills/ directory
# TODO 2: Create a SkillToolset with the loaded skill

Remplacez-les par :

_instagram_skill = load_skill_from_dir(
    pathlib.Path(__file__).parent / "skills" / "instagram-copywriting"
)
_copywriting_skills = skill_toolset.SkillToolset(skills=[_instagram_skill])

load_skill_from_dir lit SKILL.md ainsi que tous les fichiers de references/ et assets/. SkillToolset l'encapsule dans le format accepté par les agents ADK : un ensemble d'outils, et non une compétence brute.

TODO 3 : Enregistrer l'ensemble d'outils auprès de l'agent

Recherchez tools=[], # TODO 3: Add the SkillToolset here et remplacez-le par :

tools=[_copywriting_skills],

Ouvrez le fichier de compétence pour voir comment il est structuré :

cloudshell edit agents/copywriter/skills/instagram-copywriting/SKILL.md

Laissez l'UI Web d'ADK s'exécuter. Utilisez le menu déroulant de l'agent pour passer à copywriter sans redémarrer le serveur.

Si ce n'est pas le cas, redémarrez-le :

uv run adk web agents --allow_origins='*'

À vous de jouer : définissez le menu déroulant sur copywriter et envoyez :

You are writing captions for EcoFlow Smart Water Bottle targeting health-conscious millennials aged 25-35.
Audience insight: they prioritize sustainability, track health metrics, and share lifestyle content.
Competitor insight: Hydro Flask dominates with lifestyle branding; S'well leads on premium aesthetics.
Write 3 Instagram captions - one inspirational, one educational, one community-focused. Include 5 hashtags each and a CTA.

6. Build the Designer - Multimodal Image Generation

Laissez l'UI Web d'ADK s'exécuter. Utilisez le menu déroulant des agents pour changer d'agent sans redémarrer le serveur.

Rôle : crée des concepts visuels pour chaque légende et génère les images à l'aide de la fonctionnalité de génération d'images native de Gemini. Le concepteur génère exactement un concept visuel par légende, avec une requête détaillée, un style, une palette de couleurs, une ambiance et un format Instagram. Il appelle ensuite immédiatement l'outil generate_image pour produire l'image et l'importer dans GCS.

Concept : Associer un agent de texte à un modèle d'image à l'aide d'un outil

Le Designer s'exécute sur gemini-3-flash-preview (l'ensemble de modèles de texte défini via GEMINI_MODEL dans .env), mais la génération d'images nécessite un modèle dédié (gemini-3.1-flash-image-preview). Ce modèle d'image ne prend pas en charge l'appel de fonction. Il ne peut donc pas être utilisé directement en tant qu'agent ADK. Au lieu de cela, il est encapsulé dans une fonction Python simple et enregistré en tant que FunctionTool.

Il s'agit du modèle pour tout modèle ou API que le LLM ne peut pas appeler directement : encapsulez-le dans un outil, laissez l'agent orchestrer quand l'appeler et obtenez un résultat structuré.

Designer agent (text model)
        
          decides visual concept, writes image prompt
        
  generate_image tool
        
          calls gemini-3.1-flash-image-preview
          uploads result to GCS
        
  {"status": "success", "gcs_uri": "gs://..."}
        
          returned to agent, included in response
        
  Critic (receives gcs_uri, passes to Vertex AI for multimodal review)

Ouvrez le fichier directement dans l'éditeur Cloud Shell :

cloudshell edit agents/designer/image_gen_tool.py

La signature de la fonction, la configuration de l'environnement et l'injection du format sont fournies. Exécutez les trois TODO dans l'ordre :

TODO 1 : Appeler le modèle d'image Gemini

Recherchez le commentaire # TODO 1 et remplacez-le par :

        client = genai.Client(vertexai=True, project=project_id, location=location)

        response = client.models.generate_content(
            model=image_model,
            contents=prompt_with_aspect,
            config=types.GenerateContentConfig(
                response_modalities=["IMAGE", "TEXT"],
                http_options=types.HttpOptions(
                    retry_options=types.HttpRetryOptions(
                        attempts=5, exp_base=2, initial_delay=30,
                        http_status_codes=[429, 500, 503, 504],
                    ),
                    timeout=180_000,
                ),
            ),
        )

TODO 2 : extraire les octets d'image de la réponse

Recherchez le commentaire # TODO 2 et remplacez-le par :

        image_bytes = None
        mime_type = "image/png"
        for part in response.candidates[0].content.parts:
            if part.inline_data is not None:
                image_bytes = part.inline_data.data
                mime_type = part.inline_data.mime_type or "image/png"
                break

        if not image_bytes:
            return {"status": "error", "error": "Gemini returned no image data"}

TODO 3 : Importer dans GCS et renvoyer l'URI

Recherchez le commentaire # TODO 3 et remplacez-le par :

        ext = "jpg" if "jpeg" in mime_type else "png"
        from google.cloud import storage
        gcs_client = storage.Client(project=project_id)
        bucket = gcs_client.bucket(bucket_name)
        blob_name = f"campaign-images/{concept_name}-{uuid.uuid4().hex[:8]}.{ext}"
        blob = bucket.blob(blob_name)
        blob.upload_from_file(io.BytesIO(image_bytes), content_type=mime_type)
        gcs_uri = f"gs://{bucket_name}/{blob_name}"

À vous de jouer : définissez le menu déroulant sur designer et envoyez :

Create a visual concept and generate the image for an EcoFlow Smart Water Bottle Instagram post targeting health-conscious millennials.
Style: clean, modern, lifestyle-focused. Include a detailed prompt with color palette, mood, and format (1080x1080 or 1080x1350).

7. Créer le Critic : sortie structurée

Rôle : vérifie la qualité des textes et des visuels avant de les transmettre au responsable du projet. Critic évalue les deux livrables et renvoie APPROVED ou NEEDS_REVISION avec des suggestions spécifiques. Lorsque des valeurs gcs_uri sont présentes dans l'entrée, l'outil review_image est appelé pour inspecter visuellement chaque image générée avant de l'évaluer.

Concept : quand utiliser un modèle Pydantic pour la sortie Gemini

La règle concerne qui consomme la sortie :

  • Le code Python le consomme : utilisez response_schema + Pydantic. Le code ne peut pas gérer l'ambiguïté. Vous avez donc besoin d'une structure garantie pour extraire les champs de manière fiable.
  • Un LLM le consomme : le format texte et les instructions système suffisent. Les LLM comprennent les règles de mise en forme et tolèrent les variations.

Dans review_image, le code Python a besoin de score, approval_status, what_works, issues et suggestions comme valeurs saisies. La transmission de response_schema=_GeminiReview contraint Gemini au niveau d'API à renvoyer un JSON valide. model_validate_json() l'analyse en un objet typé que votre code peut utiliser de manière fiable.

class _GeminiReview(BaseModel):
    score: int = Field(ge=1, le=10)
    approval_status: Literal["APPROVED", "NEEDS_REVISION"]
    what_works: str
    issues: str
    suggestions: str

Ouvrez le fichier directement dans l'éditeur Cloud Shell :

cloudshell edit agents/critic/image_review_tool.py

Les modèles Pydantic et l'invite sont fournis. Exécutez les trois TODO dans l'ordre :

TODO 1 : Créez une partie image à partir de l'URI GCS

Recherchez le commentaire # TODO 1 et remplacez-le par :

        image_part = types.Part.from_uri(file_uri=gcs_uri, mime_type=mime_type)

TODO 2 : Appeler Gemini avec un schéma de réponse structuré

Recherchez le commentaire # TODO 2 et remplacez-le par :

        response = client.models.generate_content(
            model=model,
            contents=[image_part, prompt],
            config=types.GenerateContentConfig(
                response_schema=_GeminiReview,
                response_mime_type="application/json",
            ),
        )

TODO 3 : Analyser la réponse et renvoyer le résultat

Recherchez le commentaire # TODO 3 et remplacez-le par :

        review = _GeminiReview.model_validate_json(response.text)
        return ImageReviewResult(status="success", concept_name=concept_name, **review.model_dump())

À vous de jouer : définissez le menu déroulant sur critic et envoyez :

Review this Instagram caption for an eco-friendly water bottle brand targeting millennials:
"Hydrate smarter, live greener. 💧 Our EcoFlow bottle tracks your intake, keeps your drink cold for 24h, and never touches single-use plastic. Because what you drink from matters as much as what you drink. #EcoFlow #HydrationGoals #SustainableLiving #ZeroWaste #HealthyHabits - Shop link in bio."
Score it and indicate APPROVED or NEEDS_REVISION with specific feedback.

Vérifiez que la réponse contient **POSTS REVIEW:**, Status: APPROVED (ou NEEDS_REVISION) et **OVERALL ASSESSMENT:**. Si ces sections sont présentes, le Critic est prêt à être intégré à l'orchestrateur.

Une fois que vous avez terminé de tester les trois agents, appuyez sur Ctrl+C pour arrêter le serveur.

8. Créer l'agent Project Manager avec MCP

Le responsable du projet présente un nouveau concept : le MCP (Model Context Protocol).

Ouvrez le fichier :

cloudshell edit agents/project_manager/agent.py

Ce fichier est plus complexe. Il comporte une fonction create_project_manager_agent() avec deux branches : l'une sans Notion (chronologies en texte brut) et l'autre avec l'ensemble d'outils Notion MCP. Vous devez remplir les deux.

Problème résolu par le MCP

Votre agent doit appeler un service externe, par exemple pour créer une page dans Notion. Vous pouvez écrire du code Python qui appelle directement l'API REST Notion. Mais ensuite :

  • Chaque développeur écrit un wrapper différent
  • Vous devez gérer le code d'intégration personnalisé.
  • Le LLM ne sait pas que l'API existe, sauf si vous décrivez manuellement chaque point de terminaison.

Le protocole MCP résout ce problème en définissant une manière standard pour les services externes d'exposer leurs capacités en tant qu'outils qu'un LLM peut découvrir et appeler automatiquement.

Qu'est-ce que MCP ?

MCP (Model Context Protocol) est une norme ouverte (publiée par Anthropic) permettant de connecter des agents IA à des outils et des sources de données externes. Il fonctionne comme un adaptateur universel.

Un serveur MCP est un petit programme qui :

  1. Encapsule une API externe (Notion, GitHub, bases de données, systèmes de fichiers, etc.)
  2. Expose cette API sous forme de liste d'outils typés et documentés.
  3. Communique avec l'agent via un protocole simple (stdio ou HTTP)

L'agent se connecte au serveur MCP, détecte automatiquement les outils disponibles et peut les appeler comme n'importe quel autre outil. Le LLM voit API-post-page(...) comme une fonction appelable.

Quelle est la différence entre A2A et MCP ?

Il s'agit d'un point de confusion courant. Voici la principale différence :

A2A

MCP

Éléments connectés

Agent ↔ Agent

Agent ↔ Outil/service externe

L'autre côté est

Autre agent LLM

Wrapper d'API (sans LLM)

Exemple

Le directeur de la création appelle le responsable de la stratégie de marque

Le gestionnaire de projet appelle l'API Notion

Protocole

JSON-RPC sur HTTPS

Flux stdio ou HTTP

Défini par

Google

Anthropic

Raisonnez de la manière suivante :

  • A2A : façon dont les agents communiquent entre eux
  • MCP : façon dont les agents communiquent avec les outils et services

Dans ce projet, les deux sont utilisés ensemble :

Creative Director
    
      (A2A)  Brand Strategist ─── (google_search tool built into ADK)
      (A2A)  Copywriter
      (A2A)  Designer
      (A2A)  Critic
      (A2A)  Project Manager
                   
                     (MCP)  notion-mcp-server ──► Notion REST API

Fonctionnement de MCP dans ce projet

Lorsque l'agent s'exécute, ADK lance notion-mcp-server en tant que processus enfant. Ce processus expose directement ces outils au LLM :

Outil

Description

API-retrieve-a-database

Récupère le schéma (noms de propriétés, types, valeurs valides)

API-post-database-query

Interroge les pages existantes

API-post-page

Crée une page

API-patch-page

Met à jour une page existante

Le LLM les appelle comme n'importe quelle autre fonction. Il ne sait pas qu'elles passent par MCP pour accéder à l'API REST Notion en coulisses.

Pourquoi utiliser stdio ? Pourquoi ne pas utiliser simplement HTTP ?

Le serveur MCP s'exécute en tant que processus enfant de l'agent et communique via stdin/stdout. Ainsi :

  • Aucun port réseau supplémentaire n'est nécessaire.
  • Le cycle de vie est géré par l'agent (démarré à la demande, arrêté à la sortie)
  • Tout est fourni dans une seule image Docker. Vous n'avez pas besoin de déployer un service distinct.

(Facultatif) Activer l'intégration de Notion

Vous pouvez ignorer toute cette section. L'agent Gestionnaire de projet produit toujours un calendrier de campagne complet au format texte, avec ou sans Notion. Si vous ignorez cette configuration, l'agent repasse en mode en mémoire et affiche le calendrier en texte brut dans le chat. Rien ne sera cassé, mais les tâches n'apparaîtront pas dans une base de données Notion. Passez directement à la TÂCHE 1 si vous le souhaitez.

Si vous disposez d'un compte Notion et que vous souhaitez voir l'intégration MCP en action, effectuez la configuration ci-dessous dès maintenant. Les tâches à faire qui suivent font référence aux ID de base de données Notion. C'est là que vous les trouverez.

Étape 1 : Créer la base de données Notion à partir d'un modèle

Nous utilisons le modèle officiel Projets et tâches Notion comme base de données. Nous avons délibérément choisi ce modèle pour illustrer un paramètre complexe et réel. Il comporte plusieurs types de propriétés (état, plages de dates, relations, sélections) avec des noms non évidents. Il s'agit d'un excellent test de la découverte dynamique de schéma de MCP : l'agent doit déterminer les noms de propriétés exacts au moment de l'exécution plutôt que de les avoir codés en dur.

Cliquez sur le lien ci-dessous pour ajouter le modèle à votre espace de travail Notion :

→ Ajouter le modèle "Projets et tâches" à Notion

Modèle Notion Projects & Tasks sur la Marketplace

Une fois ajoutées, vous disposerez de deux bases de données associées : Projets et Tâches. Le modèle est fourni avec des exemples d'entrées. Supprimez-les tous avant de continuer afin que l'agent commence avec un espace de travail propre (tout sélectionner → Supprimer).

Étape 2 - Créez une intégration Notion

Créez l'intégration :

  1. Accédez à notion.so/my-integrations.
  2. Cliquez sur New Integration (Nouvelle intégration), puis nommez-la AI Creative Studio.
  3. Associer le projet à votre espace de travail
  4. Cliquez sur Configurer les paramètres → assurez-vous que les fonctionnalités Lire le contenu, Mettre à jour le contenu et Insérer du contenu sont toutes cochées.

Paramètres d'intégration de Notion : nommez-le "AI Creative Studio" et copiez le jeton.

  1. Copiez le jeton d'intégration interne (ntn_...) et collez-le dans votre fichier .env :
NOTION_TOKEN=ntn_your-token-here

Associez l'intégration à vos bases de données :

  1. Ouvrez la page du modèle que vous venez de dupliquer, puis cliquez sur la base de données Projets.
  2. Cliquez sur le menu ... (en haut à droite) → Connexions → Ajouter une connexion → sélectionnez AI Creative Studio.

Cliquez sur "Connexions" dans le menu de la base de données pour partager avec votre intégration.

AI Creative Studio apparaît comme une connexion active.

  1. Procédez de même pour la base de données Tasks (Tâches).

Obtenez les ID de base de données :

  1. Cliquez sur le lien de la base de données Projets pour l'ouvrir. Il s'ouvre sur sa propre page avec une URL du type suivant :
https://www.notion.so/9887b6a94f7f83f68f8581e038d1aaa4?v=2c37b6a94f7f838685f1086e312c7278

Ouvrir la base de données "Projets" depuis la page du modèle

L'ID de la base de données correspond au premier UUID de l'URL, c'est-à-dire tout ce qui précède ?v= :

https://www.notion.so/{DATABASE_ID}?v=...
                       ^^^^^^^^^^^^^^^^
                       9887b6a94f7f83f68f8581e038d1aaa4  ← this is your DATABASE_ID
  1. Faites de même pour le lien vers la base de données Tasks (Tâches) afin d'obtenir son ID.
  2. Ajoutez les trois valeurs à votre .env :
NOTION_TOKEN=ntn_your-token-here
NOTION_PROJECT_DATABASE_ID=9887b6a94f7f83f68f8581e038d1aaa4   # <-- your Projects DB ID
NOTION_TASKS_DATABASE_ID=your-tasks-db-id                      # <-- your Tasks DB ID

Étape 3 : Installez le serveur Notion MCP

Le gestionnaire de projet se connecte à Notion via le package Node.js @notionhq/notion-mcp-server officiel. Installez-le globalement :

npm install -g @notionhq/notion-mcp-server@1.9.1

Vérifiez l'installation :

npm list -g @notionhq/notion-mcp-server

Résultat attendu :

└── @notionhq/notion-mcp-server@1.9.1

notion-mcp-server: command not found

? Assurez-vous que Node.js est installé (node --version) et que votre fichier binaire npm global se trouve dans votre PATH (export PATH=$PATH:$(npm bin -g)).

Étape 4 : Vérifiez votre fichier .env

Ouvrez .env et vérifiez que les trois valeurs Notion sont définies (vous les avez ajoutées à l'étape 2) :

cloudshell edit .env
NOTION_TOKEN=ntn_...                           # integration token
NOTION_PROJECT_DATABASE_ID=...                 # Projects database ID
NOTION_TASKS_DATABASE_ID=...                   # Tasks database ID

L'agent Project Manager détecte automatiquement ces variables au démarrage et active l'ensemble d'outils Notion MCP.

Fonctionnement de la découverte de schéma

Le gestionnaire de projet utilise la découverte dynamique de schéma. Il ne code jamais en dur les noms de propriétés Notion :

Step 1: Call API-retrieve-a-database to discover exact property names
Step 2: Read the "properties" object in the response
Step 3: Use ONLY discovered property names (case-sensitive) in API calls
Step 4: For select/status fields, use only values from the options array

Cela signifie que l'agent s'adapte automatiquement à n'importe quelle structure de base de données Notion. Renommez vos propriétés en français, en arabe ou dans n'importe quelle autre langue, et l'agent fonctionnera toujours.

TODO 1 : Écrire l'instruction système

Le starter calcule déjà notion_section, qui est une chaîne vide lorsque Notion n'est pas configuré, ou un bloc contenant les ID de base de données ainsi que des conseils complets sur l'outil lorsqu'il l'est. Cela permet de garder les instructions Notion entièrement en dehors de l'invite de l'agent sans Notion. Le LLM ne voit jamais les règles pour les outils dont il ne dispose pas.

Votre travail consiste à remplacer l'espace réservé return par une véritable instruction système utilisant {notion_section} :

    return f"""You are a Project Manager specializing in creative campaign execution.

Today's date is {datetime.date.today().strftime("%B %d, %Y")}.
Use this as the starting point for all timelines.

Your goal: create a complete project plan for the campaign.
{notion_section}
**Project Timeline:**
Phase 1: Strategy & Research | [date]  [date] | [key activities]
Phase 2: Content Creation    | [date]  [date] | [key activities]
Phase 3: Review & Revision   | [date]  [date] | [key activities]
Phase 4: Launch & Monitoring | [date]  [date] | [key activities]

**Task List:**
| Task | Owner | Deadline | Status |
[list each task with realistic deadlines from today; set Owner to TBD]

**Budget Breakdown:**
[by category with approximate allocations]

**Milestones:**
[3-5 key checkpoints with dates]

**Notion Status:**
[What happened - e.g. "Project created (ID: xxx), 8 tasks linked" or "Notion not configured - text timeline only"]
"""

TODO 2 – Agent sans Notion

Dans create_project_manager_agent(), dans la branche if not notion_token, remplacez l'agent incomplet par :

        return Agent(
            name="project_manager",
            model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
            generate_content_config=GENERATE_CONTENT_CONFIG,
            instruction=get_system_instruction(),
            description="Project manager that creates campaign timelines and task breakdowns",
        )

TODO 3 : Agent avec Notion MCP

Remarque : Le fichier de démarrage contient déjà un rappel handle_notion_error préécrit au-dessus de create_project_manager_agent(). Il intercepte les erreurs d'API Notion (400/404) et remplace les charges utiles d'erreur brutes par des messages clairs et exploitables afin que le LLM puisse s'auto-corriger. Il vous suffit de le câbler via after_tool_callback.

Commencez par lire les deux ID de base de données en haut de create_project_manager_agent() :

    notion_token           = os.getenv("NOTION_TOKEN")
    notion_project_db_id   = os.getenv("NOTION_PROJECT_DATABASE_ID")
    notion_tasks_db_id     = os.getenv("NOTION_TASKS_DATABASE_ID")

Ensuite, dans la branche else, créez l'ensemble d'outils MCP et l'agent :

        from google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams
        from mcp import StdioServerParameters

        server_params = StdioServerParameters(
            command="notion-mcp-server",
            env={
                "NOTION_TOKEN": notion_token,
                "PATH": os.environ.get("PATH", ""),
            }
        )
        notion_toolset = McpToolset(
            connection_params=StdioConnectionParams(
                server_params=server_params,
                timeout=30.0
            )
        )

        return Agent(
            name="project_manager",
            model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
            generate_content_config=GENERATE_CONTENT_CONFIG,
            after_tool_callback=handle_notion_error,
            instruction=get_system_instruction(
                project_database_id=notion_project_db_id,
                tasks_database_id=notion_tasks_db_id,
            ),
            description="Project manager with Notion integration for task tracking",
            tools=[notion_toolset],
        )

Bonne pratique : N'échouez jamais de manière définitive sur les intégrations facultatives. La chronologie textuelle est toujours le principal livrable. Notion est un complément.

Tester le gestionnaire de projet localement avec ADK Web

uv run adk web agents --allow_origins='*'

Ouvrez l'aperçu sur le Web sur le port 8000. Utilisez le menu déroulant de l'agent pour sélectionner project_manager, puis essayez :

Create a project plan for a GreenBrew organic coffee brand Instagram campaign.
Budget: $2,500. Launch in 3 weeks. Target audience: eco-conscious millennials aged 22-30.
Include phases, tasks with deadlines from today, and milestones.

Vous devriez voir un calendrier textuel structuré avec des phases, une liste de tâches et des jalons. Si les identifiants Notion sont définis dans .env, l'agent créera également des entrées dans votre espace de travail Notion.

9. Comprendre le protocole A2A

Nous utiliserons le protocole Agent-to-Agent (A2A) pour connecter les différents agents de notre système. Découvrons comment cela fonctionne.

Problème résolu par A2A

Imaginons que vous ayez un agent Brand Strategist créé avec ADK et un agent Copywriter créé avec LangGraph. Comment l'un appelle-t-il l'autre ? Ils parlent des langues internes différentes. Vous devriez écrire du code de colle personnalisé à chaque fois.

A2A résout ce problème en définissant un langage universel que tout agent peut parler, quel que soit le framework. Il s'agit du HTTP du monde des agents : une norme sur laquelle tout le monde s'accorde pour que chacun puisse parler à n'importe qui.

Qu'est-ce que l'A2A ?

Agent2Agent (A2A) est une norme ouverte pour la communication entre agents publiée par Google. Il définit les éléments suivants :

  1. Description de l'agent : carte d'agent à l'adresse /.well-known/agent.json
  2. Comment un autre agent l'appelle-t-il ? JSON-RPC sur HTTPS
  3. Comment les résultats sont-ils renvoyés ? (en flux continu ou en une seule réponse)

Ce qui rend A2A flexible :

  • Indépendance de la langue : les agents Python peuvent communiquer avec les agents TypeScript.
  • Indépendance du framework : les agents ADK peuvent communiquer avec les agents LangGraph ou CrewAI
  • Indépendance de l'infrastructure : les agents locaux peuvent communiquer avec les agents cloud.

Fonctionnement : étape par étape

Creative Director                  Brand Strategist
      │                                  │
      │  1. GET /.well-known/agent.json  │
      │ ────────────────────────────────►│
      │  ◄──── agent card (name, url,    │
      │         skills, capabilities) ───│
      │                                  │
      │  2. POST /                       │
      │     {"method": "tasks/send",     │
      │      "params": {"message": ...}} │
      │ ────────────────────────────────►│
      │                                  │  LLM does
      │                                  │  the work...
      │  3. streaming response chunks    │
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│

Étape 1 : Découverte : l'orchestrateur récupère la carte de l'agent une fois pour connaître son nom, son URL et ses capacités.

Étape 2 : Invocation : l'orchestrateur envoie une tâche via JSON-RPC POST. Le corps contient le message (la requête pour le spécialiste).

Étape 3 : Réponse : le spécialiste renvoie sa réponse par blocs, comme un appel LLM classique.

Carte de l'agent

Chaque agent publie une auto-description sur /.well-known/agent.json. C'est comme une carte de visite : elle indique au monde ce que l'agent peut faire et où le contacter :

{
  "name": "brand_strategist",
  "description": "Market research and competitive analysis",
  "url": "https://brand-strategist-xyz.run.app",
  "capabilities": { "streaming": true },
  "skills": [
    {
      "id": "market_research",
      "description": "Research target audiences, competitors, and trends"
    }
  ]
}

L'orchestrateur lit cette fiche pour créer son objet RemoteA2aAgent. Aucune connaissance codée en dur des éléments internes du spécialiste n'est nécessaire.

Exposer un agent via A2A dans ADK

to_a2a() encapsule n'importe quel agent ADK dans une application FastAPI compatible avec A2A. Une seule ligne :

from google.adk.a2a.utils.agent_to_a2a import to_a2a

# root_agent = your normal ADK Agent(...)
a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

Cela crée automatiquement :

  • /.well-known/agent.json : carte d'agent
  • / : point de terminaison JSON-RPC (toutes les requêtes de tâches A2A sont envoyées au chemin racine)

10. Exposer les agents en tant que services A2A

Pour exposer les agents en tant que services A2A, vous pouvez utiliser la fonction utilitaire to_a2a() d'ADK.

Fonctionnement de to_a2a()

from google.adk.a2a.utils.agent_to_a2a import to_a2a

a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

to_a2a() encapsule votre agent ADK dans une application FastAPI qui expose automatiquement les éléments suivants :

  • /.well-known/agent.json : carte de l'agent (nom, description, capacités)
  • /a2a/{agent_name} : point de terminaison JSON-RPC pour recevoir les tâches

Le code squelette de chaque agent inclut déjà un bloc __main__ qui encapsule l'agent dans un serveur A2A à l'aide de to_a2a(). Vous n'avez pas besoin d'écrire ce code, il est fourni.

Comprendre la configuration à double URL

Lorsque vous exécutez python agent.py, le bloc __main__ utilise deux configurations d'URL distinctes :

# Where the server actually listens (network interface):
HOST = "0.0.0.0"
PORT = 8082  # Brand Strategist (others use 80838086 locally)

# What gets advertised in the agent card (the address other agents use to reach it):
PUBLIC_HOST = os.getenv("PUBLIC_HOST", "localhost")
PUBLIC_PORT = int(os.getenv("PUBLIC_PORT", str(PORT)))
PROTOCOL    = os.getenv("PROTOCOL", "http")

a2a_app = to_a2a(root_agent, host=PUBLIC_HOST, port=PUBLIC_PORT, protocol=PROTOCOL)
uvicorn.run(a2a_app, host=HOST, port=PORT)

Environnement

HOST:PORT (écoutes)

PUBLIC_HOST:PUBLIC_PORT (annoncé dans la fiche de l'agent)

Local

0.0.0.0:8082

http://localhost:8082

Cloud Run

0.0.0.0:8080

https://brand-strategist-xyz.run.app:443

Localement, les deux pointent vers la même machine. Sur Cloud Run, le conteneur écoute en interne sur 8080, mais la fiche de l'agent doit indiquer l'URL HTTPS publique. Sinon, le Creative Director ne peut pas contacter le spécialiste depuis l'extérieur du conteneur.

Démarrez les cinq serveurs A2A spécialisés.

Exécutons les cinq spécialistes en tant que serveurs A2A simultanément, puis testons le Creative Director en local en les pointant.

Ouvrez cinq terminaux Cloud Shell distincts (cliquez sur l'icône + dans la barre d'onglets du terminal) et exécutez un agent par terminal.

uv run active automatiquement .venv. Aucune source manuelle n'est nécessaire dans chaque terminal.

Terminal 1 : Brand Strategist (port 8082)

cd ~/ai-creative-studio/workshop/starter
PORT=8082 uv run agents/brand_strategist/agent.py

Terminal 2 : rédacteur (port 8083)

cd ~/ai-creative-studio/workshop/starter
PORT=8083 uv run agents/copywriter/agent.py

Terminal 3 – Designer (port 8084) :

cd ~/ai-creative-studio/workshop/starter
PORT=8084 uv run agents/designer/agent.py

Terminal 4 – Critic (port 8085) :

cd ~/ai-creative-studio/workshop/starter
PORT=8085 uv run agents/critic/agent.py

Terminal 5 : gestionnaire de projet (port 8086)

cd ~/ai-creative-studio/workshop/starter
PORT=8086 uv run agents/project_manager/agent.py

Définir les URL localhost dans .env

Dans Terminal 6, mettez à jour .env avec les URL de l'agent local afin que le Creative Director puisse les trouver :

cd ~/ai-creative-studio/workshop/starter

sed -i \
  -e 's|STRATEGIST_AGENT_URL=.*|STRATEGIST_AGENT_URL=http://localhost:8082|' \
  -e 's|COPYWRITER_AGENT_URL=.*|COPYWRITER_AGENT_URL=http://localhost:8083|' \
  -e 's|DESIGNER_AGENT_URL=.*|DESIGNER_AGENT_URL=http://localhost:8084|' \
  -e 's|CRITIC_AGENT_URL=.*|CRITIC_AGENT_URL=http://localhost:8085|' \
  -e 's|PM_AGENT_URL=.*|PM_AGENT_URL=http://localhost:8086|' \
  .env

Inspecter les agents avec l'outil d'inspection A2A

A2A Inspector est un outil de développement Open Source qui utilise le protocole A2A de manière native. Il vous permet de vous connecter directement à n'importe quel agent A2A en cours d'exécution, de lire sa carte d'agent et d'envoyer des tâches, le tout sans écrire de code client.

Voici ce que vous verrez :

  • Fiche d'agent : métadonnées structurées que votre agent annonce (nom, description, modes d'entrée/sortie compatibles et URL du point de terminaison). Voici ce que lit le directeur de création lorsqu'il découvre un spécialiste.
  • Interface de chat : envoyez n'importe quel message à l'agent via A2A et consultez la réponse brute. Vous pouvez tester les requêtes de manière isolée avant de connecter les agents entre eux.
  • Validation du protocole : l'inspecteur vérifie que la fiche de l'agent est conforme à la spécification A2A, en signalant les champs manquants ou les réponses mal formées dès le début.

Pourquoi est-ce important ? Lorsque vous déployez sur Cloud Run ultérieurement, le Creative Director découvre chaque spécialiste en récupérant sa fiche d'agent depuis /.well-known/agent.json. Si cette fiche est incorrecte (URL incorrecte, fonctionnalités manquantes, etc.), l'orchestrateur échoue sans message d'erreur. L'inspecteur vous permet de détecter ces problèmes en local avant tout déploiement dans le cloud.

Fiche de l&#39;agent Brand Strategist

La fiche de l'agent affiche l'identité et les capacités du spécialiste exactement comme les autres agents les voient.

Informations relatives à la carte de l&#39;agent

Installer et démarrer l'outil d'inspection

cd ~/ai-creative-studio/workshop
./setup_inspector.sh

La mise à jour .env est une commande ponctuelle. Utilisez le Terminal 6 pour démarrer l'inspecteur :

cd ~/a2a-inspector
bash scripts/run.sh

Pour ouvrir l'interface utilisateur de l'inspecteur, utilisez Aperçu sur le Web → Modifier le port → saisissez 5001.

Contacter le Brand Strategist

Saisissez http://localhost:8082 dans le champ d'URL de l'inspecteur, puis cliquez sur Connect (Connecter). L'inspecteur récupère la fiche de l'agent et affiche les métadonnées du spécialiste.

A2A Inspector connecté à Brand Strategist

Informations fournies par la fiche de l'agent

La fiche d'agent est plus qu'une métadonnée. Il s'agit du contrat de capacité complet que l'agent annonce au réseau. Connectez-vous au Gestionnaire de projet (http://localhost:8086) pour voir l'exemple le plus complet :

{
  "name": "project_manager",
  "description": "Project manager with Notion integration for task tracking",
  "protocolVersion": "0.3.0",
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "project_manager",
      "name": "model",
      "tags": ["llm"],
      "description": "... full system instruction including today's date and Notion database IDs ..."
    },
    {
      "id": "project_manager-API-post-page",
      "name": "API-post-page",
      "tags": ["llm", "tools"],
      "description": "Notion | Create a page"
    },
    {
      "id": "project_manager-API-retrieve-a-database",
      "name": "API-retrieve-a-database",
      "tags": ["llm", "tools"],
      "description": "Notion | Retrieve a database"
    }
  ]
}

Trois éléments se distinguent :

1. Les outils MCP deviennent des compétences A2A : chaque outil Notion auquel le chef de projet a accès (API-post-page, API-retrieve-a-database, etc.) est listé comme compétence distincte dans la fiche de l'agent. Tout autre agent du réseau peut découvrir exactement les outils que cet agent peut utiliser, sans lire de code.

2. L'instruction système est intégrée : la première compétence description contient l'instruction système complète, y compris la date du jour et les ID de base de données Notion. C'est ainsi que le directeur artistique sait quoi transmettre lorsqu'il appelle le responsable du projet.

3. L'URL est le point de terminaison en direct : le champ url est exactement ce que RemoteA2aAgent utilise lorsque le directeur de création appelle ce spécialiste. Si l'URL de la fiche est incorrecte, l'orchestrateur ne peut pas joindre l'agent.

C'est pourquoi l'inspecteur est un outil de débogage puissant : un coup d'œil à la fiche de l'agent vous indique si l'agent est en cours d'exécution, quels outils il possède et si le point de terminaison est correct.

Envoyer un message de test

Une fois connecté, saisissez une requête dans le panneau de chat et envoyez-la. L'inspecteur l'envoie en tant que tâche A2A et renvoie la réponse en flux continu, de la même manière que le directeur artistique appellera cet agent en production.

Discuter avec un Brand Strategist via l&#39;outil d&#39;inspection A2A

Pointez l'inspecteur sur n'importe quel port local (8082 à 8086) pour tester chaque spécialiste individuellement.

11. Créer l'orchestrateur Creative Director

Le directeur artistique est le chef d'orchestre. Il lit les URL spécialisées à partir des variables d'environnement, enveloppe chacune d'elles en tant que RemoteA2aAgent et les expose en tant que AgentTool que le LLM peut appeler.

Assurez-vous que les cinq agents spécialisés sont toujours en cours d'exécution (terminaux 1 à 5 de l'étape 10).

Dans Terminal 6 (le terminal de l'inspecteur A2A), arrêtez l'inspecteur avec Ctrl+C.

Ouvrez le fichier :

cd ~/ai-creative-studio/workshop/starter
cloudshell edit agents/creative_director/agent.py

Ce fichier comporte trois tâches à faire. Parcourez-les dans l'ordre.

À FAIRE 1 : Examiner l'instruction système déjà rédigée

L'instruction système se trouve dans prompt.py dans le même répertoire. Elle est importée automatiquement :

from .prompt import SYSTEM_INSTRUCTION_TEMPLATE

Ouvrez prompt.py pour le lire avant de continuer :

cloudshell edit agents/creative_director/prompt.py

Il est important de le comprendre, car il contrôle l'ensemble du comportement d'orchestration.

Pourquoi l'invite de l'orchestrateur contrôle-t-elle tout ?

Ouvrez prompt.py à côté de cette section, car les exemples ci-dessous font référence à des parties spécifiques de ce fichier.

Le prompt dans prompt.py n'est pas qu'une documentation, il s'agit du plan de contrôle de l'ensemble du système. Une invite d'orchestrateur mal structurée entraîne les problèmes suivants : agents appelés dans le désordre, contenu généré par l'orchestrateur au lieu des spécialistes, workflows qui se poursuivent après des échecs et contexte supprimé silencieusement entre les agents. Ces neuf éléments permettent d'éviter les échecs les plus courants :

Élément 0 : Planifiez d'abord, puis exécutez

Il s'agit de l'élément le plus important. Avant d'appeler un spécialiste, l'orchestrateur est invité à générer un plan numéroté :

I'll create your campaign by coordinating the specialist agents in sequence:
1. Brand Strategist - develop positioning and audience insights
2. Copywriter - write captions using those insights
3. Visual Designer - create image prompts aligned with the copy
4. Critic - review and score the full package
5. Project Manager - build the timeline and task breakdown

Sans cette étape, le LLM passe directement aux appels d'outils et perd le fil du workflow, en particulier après avoir reçu une longue réponse d'un spécialiste. En définissant d'abord le plan, l'orchestrateur est ancré : il sait à quelle étape il se trouve, ce qui suit et à quoi ressemble une exécution complète. Si vous ne le faites pas, l'orchestrateur risque de s'arrêter au milieu du workflow ou de répéter des étapes.

Élément 1 : définition explicite du rôle

❌ "You are a helpful creative assistant."
✅ "You orchestrate specialists. You do NOT write captions, designs, or timelines yourself."

Sans cette interdiction explicite, le LLM peut parfois ignorer l'appel à des spécialistes et générer du contenu directement, car c'est plus rapide et il "sait" comment faire. L'instruction doit rendre cela incorrect.

Élément 2 : Syntaxe d'appel d'outil avec des modèles incorrects

Il ne suffit pas de montrer uniquement la syntaxe correcte. Le LLM peut générer des appels qui semblent plausibles, mais qui échouent en mode silencieux. La requête liste explicitement le modèle correct et ceux qui ne doivent jamais être utilisés :

✅ copywriter(request="...")          ← correct
❌ print(copywriter(...))             ← breaks silently
❌ default_api.copywriter(...)        ← breaks silently
❌ copywriter.run(...)                ← breaks silently
❌ agents.copywriter(...)             ← breaks silently

Lister explicitement les mauvais schémas a permis de réduire d'environ 95% les appels d'outils mal formés en production.

Élément 3 : Exécution séquentielle détaillée étape par étape

a) Call the tool
b) Wait for tool_output
c) Verify the output is not an error
d) Confirm to the user: "✓ Brand Strategist complete"
e) Then move to the next agent

Sans les étapes (b) et (c), le LLM appellera parfois deux agents simultanément ou supposera que l'opération a réussi et passera à la suite avant de recevoir la réponse.

Élément 4 : Directives d'erreur : STOP, signaler, ne pas continuer

Dans les premières versions, l'orchestrateur recevait une erreur d'un spécialiste, fabriquait une sortie plausible pour celle-ci et passait à l'agent suivant. L'utilisateur a obtenu une campagne complète, mais basée sur des informations hallucinées. La solution est explicite : ARRÊTEZ-VOUS immédiatement. Signalez l'erreur exacte. Ne jamais continuer.

Élément 5 : règles de transmission du contexte

Les agents à distance n'ont aucun historique de conversation. Lorsque l'orchestrateur appelle le rédacteur via A2A, celui-ci ne voit que le message de cette requête unique. Il n'a aucune idée de ce qu'a dit le responsable de la stratégie de marque. L'orchestrateur doit regrouper explicitement les sorties précédentes dans chaque appel suivant :

copywriter(request="Create 3 posts for EcoFlow water bottle targeting millennials.
Use these insights from the Brand Strategist: [paste full strategist output here].
Create engaging captions with hashtags.")

L'instruction l'indique explicitement : "Les agents distants n'ont PAS de mémoire partagée. Vous devez transmettre explicitement les sorties précédentes." Sans cela, chaque agent travaille à l'aveugle.

Élément 6 : Classification des demandes (simples ou complexes)

Toutes les demandes ne nécessitent pas l'intervention des cinq agents. La requête demande à l'orchestrateur de classer la requête avant de planifier :

SIMPLE  → one agent needed
  "Research the eco-friendly water bottle market" → brand_strategist only
  "Write 3 Instagram captions"                    → copywriter only

COMPLEX → all agents sequentially
  "Create a complete campaign with timeline"      → all 5 agents

Sans cette classification, l'orchestrateur exécuterait les cinq agents pour chaque requête, y compris "donne-moi juste trois idées de posts", ce qui ajouterait une latence et des coûts inutiles.

Élément 7 : Règles de communication : afficher les résultats complets, sans filtrage

La requête indique explicitement que l'orchestrateur ne doit pas résumer ni modifier ce que renvoient les spécialistes :

- DO NOT summarize unless the output exceeds 2000 words
- DO NOT filter or edit agent responses
- Show the user exactly what each specialist produced
- NEVER say results are ready unless you received them in tool_output

Sans cela, l'orchestrateur réécrit les résultats des spécialistes avec ses propres mots, ce qui entraîne une perte de détails, des erreurs et l'inutilité des spécialistes.

Élément 8 : Ne jamais arrêter un workflow avant son terme

Mode d'échec subtil, mais critique : l'orchestrateur annonce un plan en cinq étapes, en effectue trois, puis présente les résultats comme si tout était terminé. L'invite empêche cela grâce à une checklist explicite qui doit être validée avant que l'orchestrateur puisse terminer :

✓ Did I announce a plan with N agents?
✓ Have I called ALL N agents from my plan?
✓ Did each agent respond successfully?
✓ Am I presenting complete results from ALL agents?

If any answer is NO → continue executing the remaining agents.

Cela empêche l'orchestrateur de traiter une exécution partielle comme terminée.

La boucle de contrôle qualité

Le workflow de révision est la partie la plus complexe de prompt.py. Ouvrez la section ## REVISION WORKFLOW et suivez les instructions.

Fonctionnement

Une fois que le critique a répondu, le directeur artistique ne se contente pas de transmettre aveuglément la réponse au chef de projet. Il lit la sortie du Critic et crée des branches :

Critic output
      │
      ├── "All Approved: YES"
      │         └──► proceed to Project Manager
      │
      └── "Status: NEEDS_REVISION"
                │
                ├── posts fail   → call copywriter again with feedback
                ├── visuals fail → call designer again with feedback
                └── both fail    → call copywriter, then designer
                          │
                          └──► revised output → Project Manager
                               (1 revision max per deliverable)

Il s'agit d'un modèle basé sur les LLM, et non sur le code.

L'atelier de programmation mentionné précédemment indique que l'orchestrateur "analyse" la réponse du Critic. Aucun code Python n'effectue cette analyse (pas d'expression régulière, pas de correspondance de chaîne). Le directeur artistique est un LLM qui lit sa propre instruction. Cette instruction indique :

Look for "Status: NEEDS_REVISION" in the critic's response.
Posts need revision  → call copywriter
Visuals need revision → call designer

Le LLM lit ces chaînes exactes dans la sortie du critique et suit la branche. C'est pourquoi le format "Critique" est non négociable : si le critique écrit "nécessite quelques améliorations" au lieu de NEEDS_REVISION, le LLM ne trouve aucune correspondance dans ses instructions et ignore silencieusement l'étape de révision.

Comment le contexte est transmis lors d'un appel de révision

L'appel de révision suit la même règle de transmission de contexte que l'élément 5 : l'orchestrateur doit tout inclure explicitement, car le rédacteur n'a aucun souvenir de sa première version :

"I need you to revise the Instagram posts based on critic feedback.

ORIGINAL BRIEF:
[the original user request]

YOUR FIRST VERSION:
[the posts the copywriter created]

CRITIC FEEDBACK (Score: 6/10 - NEEDS_REVISION):
[the critic's specific suggestions]

Please revise the posts addressing this feedback while maintaining
the strengths the critic identified."

Sans la section "VOTRE PREMIÈRE VERSION", le rédacteur commencerait à écrire à partir de zéro au lieu d'améliorer ce qu'il a déjà produit.

La limite d'une révision et son importance

Après une série de révisions, l'orchestrateur passe au gestionnaire de projet, quel que soit le score. L'instruction suit mentalement ce qui suit :

After calling copywriter for revision once:
→ mark "copywriter_revised = true" in context
→ even if the critic still suggests changes, proceed to PM

Sans cette limite, la boucle pourrait s'exécuter indéfiniment : le critique signale un problème → le rédacteur le corrige → le critique le signale à nouveau → le rédacteur le corrige à nouveau. Chaque partie coûte des jetons et du temps. Une seule révision suffit à améliorer la qualité sans risque de cycle incontrôlable.

Ce qui est transmis au gestionnaire de projet

Le responsable du projet reçoit toujours les versions finales approuvées, et non les versions originales. Si des révisions ont été apportées, l'outil d'orchestration transmet la copie et les éléments visuels révisés. Si tout a été approuvé lors de la première vérification, il passe directement ces étapes. Le responsable de projet ne voit jamais les brouillons refusés.

TODO 2 : Enregistrez chaque spécialiste en tant que RemoteA2aAgent + AgentTool

Recherchez le commentaire # TODO 2: For each specialist URL... et remplacez-le par :

    if strategist_url:
        available_agents_list.append(
            "- **brand_strategist**: Market research, competitor analysis, trend identification"
        )
        strategist_agent = RemoteA2aAgent(
            name="brand_strategist",
            description="Researches markets, competitors, and trends using Google Search",
            agent_card=f"{strategist_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=strategist_agent))

    if copywriter_url:
        available_agents_list.append(
            "- **copywriter**: Instagram captions, hashtags, and CTAs"
        )
        copywriter_agent = RemoteA2aAgent(
            name="copywriter",
            description="Creates Instagram captions with hashtags and CTAs",
            agent_card=f"{copywriter_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=copywriter_agent))

    if designer_url:
        available_agents_list.append(
            "- **designer**: Visual concepts and real images generated via Gemini (GCS URIs returned)"
        )
        designer_agent = RemoteA2aAgent(
            name="designer",
            description="Creates visual concepts and generates real images via Gemini, stored in GCS",
            agent_card=f"{designer_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=designer_agent))

    if critic_url:
        available_agents_list.append(
            "- **critic**: Quality review with APPROVED/NEEDS_REVISION scoring"
        )
        critic_agent = RemoteA2aAgent(
            name="critic",
            description="Reviews campaign materials and returns structured quality feedback",
            agent_card=f"{critic_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=critic_agent))

    if pm_url:
        available_agents_list.append(
            "- **project_manager**: Project timelines, task breakdowns, Notion integration"
        )
        pm_agent = RemoteA2aAgent(
            name="project_manager",
            description="Creates project timelines and task breakdowns, optionally in Notion",
            agent_card=f"{pm_url}/.well-known/agent.json",
        )
        agent_tools.append(AgentTool(agent=pm_agent))

TODO 3 : Encapsuler dans une application avec compaction du contexte

Pourquoi la compaction est-elle nécessaire ?

Chaque message d'une conversation (requête de l'utilisateur, appel d'outil et réponse d'outil) est ajouté à la fenêtre de contexte que le LLM lit au tour suivant. Dans un workflow à cinq agents, cela s'accumule rapidement :

Turn 1:  user prompt                           ~200 tokens
Turn 2:  orchestrator plan                     ~300 tokens
Turn 3:  brand_strategist tool_call            ~150 tokens
Turn 4:  brand_strategist tool_output          ~1,500 tokens   full research report
Turn 5:  copywriter tool_call                  ~300 tokens     must include strategist output
Turn 6:  copywriter tool_output                ~2,000 tokens   3 captions
Turn 7:  designer tool_call                    ~500 tokens
Turn 8:  designer tool_output                  ~1,500 tokens
...

Pour l'agent 4 (Critique), la fenêtre de contexte contient la sortie complète des trois agents précédents, ce qui représente souvent entre 8 000 et 12 000 jetons uniquement dans les réponses des outils. Même avec la grande fenêtre de contexte de Gemini 2.5 Pro, la qualité du raisonnement de l'orchestrateur se dégrade à mesure qu'il doit traiter un historique de plus en plus long. Sans compression, les workflows longs atteignent des limites pratiques autour de l'agent 4.

Quels sont les effets de la compaction ?

Au lieu de conserver chaque événement dans son intégralité, l'ADK appelle périodiquement un LLM pour résumer les événements plus anciens dans une représentation compacte. Seuls le résumé des événements passés et la sortie complète de l'agent le plus récent sont conservés dans le contexte.

Without compaction:
  [full strategist output] + [full copywriter output] + [full designer output] + → Critic

With compaction (interval=3, overlap=1):
  [summary of strategist + copywriter] + [full designer output] + → Critic

Le résumé conserve les faits essentiels (insights clés, sous-titres approuvés, concepts visuels), tout en supprimant la mise en forme détaillée, le contexte répété transmis à chaque agent et le raisonnement intermédiaire. Le Critique dispose toujours de tout ce dont il a besoin pour évaluer le code. Il lit simplement un résumé au lieu de trois rapports complets.

Le code

Recherchez le commentaire # TODO 3: Wrap the agent in an App... et remplacez l'espace réservé App(...) par :

    from google.adk.apps import App
    from google.adk.apps.app import EventsCompactionConfig
    from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
    from google.adk.models import Gemini

    compaction_config = EventsCompactionConfig(
        summarizer=LlmEventSummarizer(llm=Gemini(model_id=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"))),
        compaction_interval=3,   # Summarize after every 3 agent completions
        overlap_size=1,          # Keep the most recent agent's output in full
    )

    app = App(
        name="creative_director",
        root_agent=agent,
        events_compaction_config=compaction_config,
        plugins=[LoggingPlugin()],
    )
    return agent, app

compaction_interval=3 : la compaction se déclenche après chaque troisième achèvement de l'agent. Pour un pipeline à cinq agents, cela signifie qu'il se déclenche une fois (après les agents 1 à 3), puis que l'agent critique et le PM voient un résumé des agents 1 à 3, ainsi que la sortie complète de l'agent précédent.

overlap_size=1 : la sortie complète la plus récente de l'agent est toujours conservée telle quelle, jamais résumée. C'est important, car le Critique a besoin de l'intégralité du résultat du Concepteur, y compris les valeurs gcs_uri, pour charger et examiner les images réelles. Un résumé perdrait ces URI.

Déroulement d'une campagne complète :

Agent 1 (Strategist)  → full context
Agent 2 (Copywriter)  → full context
Agent 3 (Designer)    → full context
                        ↓ compaction fires: summarizes agents 1-2, keeps 3 in full
Agent 4 (Critic)      → sees [summary of 1-2] + [full output of 3]
Agent 5 (PM)          → sees [summary of 1-3] + [full output of 4]

Comprendre RemoteA2aAgent et AgentTool

RemoteA2aAgent("brand_strategist", agent_card=url)
     
       wraps the remote service so ADK can call it
     
AgentTool(agent=strategist_agent)
     
       exposes it as a callable tool to the LLM
     
Agent(tools=[...])
     
       LLM calls tool("brand_strategist", message=...) when needed
     
brand-strategist-xxxx.run.app   actual HTTP A2A call happens here

Le LLM décide quand appeler chaque outil en fonction des instructions système et de la demande de l'utilisateur. L'outil d'orchestration n'appelle jamais les agents directement dans le code. Tout est basé sur le raisonnement du LLM.

Tester Creative Director en local

uv run adk web agents --allow_origins='*'

Ouvrez l'aperçu sur le Web sur le port 8000. Utilisez le menu déroulant de l'agent pour sélectionner creative_director, puis essayez :

Research the eco-friendly water bottle market for health-conscious millennials

Vous verrez que le directeur artistique transmettra cette demande uniquement au responsable de la stratégie de marque, et que vous recevrez une réponse de ce dernier.

Pour la campagne complète, essayez ce qui suit :

Create a complete Instagram campaign for SolarPack portable solar charger targeting
outdoor enthusiasts and digital nomads aged 22-35.
Budget $2,000, launch in 2 weeks.

Vous verrez le directeur de la création coordonner les cinq spécialistes dans l'ordre, chaque résultat d'agent étant transmis au suivant.

Démonstration : exécution d&#39;une campagne de bout en bout

Arrêtez le Creative Director (Ctrl+C) avant de continuer, car l'inspecteur A2A utilise également le port 8000.

Arrêtez les cinq serveurs spécialisés (Ctrl+C dans chaque terminal) une fois les tests locaux terminés.

12. Déployer et tester les agents spécialisés

Nous sommes maintenant prêts à déployer nos agents sur Google Cloud. Cloud Run est un excellent service pour déployer des agents. Elle est sans serveur, évolutive et facile à utiliser. Chaque agent spécialisé est déployé en tant que service Cloud Run indépendant.

Configuration du déploiement

Le Dockerfile de chaque spécialiste suit ce modèle :

FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc curl

# Fast dependency install with uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY pyproject.toml .
RUN uv sync --no-install-project --no-dev

COPY . .
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

ENV PYTHONUNBUFFERED=1 PORT=8080 HOST=0.0.0.0
EXPOSE 8080
CMD ["uv", "run", "python", "agent.py"]

Déployer les cinq spécialistes de manière séquentielle

cd ~/ai-creative-studio/workshop/starter
source .env

uv run deploy/deploy_all_specialists.py

Ce script déploie les cinq agents un par un (environ 10 à 12 minutes au total). Le déploiement séquentiel évite le quota d'interrogation Cloud Build (60 requêtes/minute). Une fois l'opération terminée, elle réécrit l'URL Cloud Run de chaque agent dans .env.

Une fois le Designer déployé, le script accorde automatiquement à son compte de service Cloud Run roles/storage.objectCreator sur votre bucket GCS afin qu'il puisse importer les images générées.

Si vous avez configuré les identifiants Notion dans .env, le script les stocke également de manière sécurisée dans Secret Manager (sous notion-token, notion-project-db-id, notion-tasks-db-id) et les injecte dans le service Project Manager via --set-secrets plutôt que dans des variables d'environnement simples. Cela signifie que le jeton n'apparaît jamais dans l'onglet "Environnement" de Cloud Run ni dans l'historique des commandes gcloud.

Vérifier les déploiements

Une fois le déploiement terminé, le script réécrit automatiquement les URL Cloud Run dans .env, en remplaçant les URL localhost de l'étape précédente :

source .env

echo "Deployed URLs:"
echo "  Brand Strategist: $STRATEGIST_AGENT_URL"
echo "  Copywriter:       $COPYWRITER_AGENT_URL"
echo "  Designer:         $DESIGNER_AGENT_URL"
echo "  Critic:           $CRITIC_AGENT_URL"
echo "  Project Manager:  $PM_AGENT_URL"

Le directeur artistique utilisera automatiquement ces URL Cloud Run lors du déploiement dans Agent Runtime à l'étape suivante.

Valider les cartes d'agent

Chaque agent déployé expose une fiche d'agent à l'adresse /.well-known/agent.json. Récupérez-les pour vérifier que tout est en ligne :

source .env

for agent_url in $STRATEGIST_AGENT_URL $COPYWRITER_AGENT_URL $DESIGNER_AGENT_URL $CRITIC_AGENT_URL $PM_AGENT_URL; do
    echo "=== Agent Card: $agent_url ==="
    curl -s "${agent_url}/.well-known/agent.json" | python3 -m json.tool | grep -E '"name"|"url"|"description"'
    echo ""
done

Résultat attendu pour chaque agent :

"name": "brand_strategist",
"url": "https://brand-strategist-xxxx.run.app",
"description": "Brand strategist for market research and competitive insights"

Tester avec A2A Inspector (Cloud Run)

L'outil A2A Inspector est déjà installé depuis l'étape 10. Démarrez-le :

cd ~/a2a-inspector
bash scripts/run.sh

Ouvrez Aperçu sur le Web → Modifier le port → 5001. Saisissez votre URL Cloud Run dans le champ de connexion :

https://brand-strategist-xxxx.us-central1.run.app

Cliquez sur Connecter. Aucun jeton d'authentification n'est nécessaire, car les services sont déployés avec --allow-unauthenticated.

L'inspecteur se connecte, valide la fiche de l'agent et vous permet de discuter de manière interactive via A2A.

Inspecter les agents déployés sur Cloud Run

Après le déploiement sur Cloud Run, pointez l'inspecteur sur l'URL HTTPS publique pour vérifier que le déploiement cloud fonctionne :

Inspecteur A2A connecté à l&#39;agent Cloud Run

Le workflow est identique : collez l'URL Cloud Run, connectez-vous et envoyez un message de test. Si la fiche de l'agent se charge et que le chat répond, cela signifie que le spécialiste est correctement déployé et joignable.

13. Déployer le Creative Director sur Agent Runtime

L'orchestrateur est déployé sur Agent Runtime, qui fournit un état de session géré, un scaling automatique et un traçage intégré.

Pourquoi utiliser Agent Runtime pour l'orchestrateur ?

Les cinq spécialistes sont déployés sur Cloud Run, qui est léger et sans état, et chacun d'eux gère une tâche. Le directeur de la création a des exigences différentes :

Exigence

Pourquoi est-ce important ?

État de la session

Un workflow en plusieurs étapes prend plus de 45 secondes. L'environnement d'exécution de l'agent conserve l'état de la conversation entre les appels d'outils de l'orchestrateur. Ainsi, rien n'est perdu au milieu du pipeline.

Charge variable

Parfois une campagne par heure, parfois plusieurs en parallèle. L'environnement d'exécution de l'agent passe à zéro instance lorsqu'il est inactif et effectue un scaling horizontal automatiquement. Vous ne payez pas la capacité inactive.

Observabilité

Cloud Logging, Cloud Monitoring et Cloud Trace sont intégrés. Vous pouvez voir chaque appel A2A, chaque jeton utilisé et chaque pic de latence, sans ajouter d'instrumentation.

Workflows de longue durée

Cloud Run a un délai d'expiration de requête de 3 600 secondes. Agent Runtime est conçu pour les workflows qui peuvent prendre plusieurs minutes, avec des nouvelles tentatives gérées et la persistance de l'état.

Cloud Run est la plate-forme idéale pour les spécialistes sans état. Agent Runtime est la plate-forme idéale pour l'orchestrateur avec état.

Déployer l'orchestrateur

cd ~/ai-creative-studio/workshop/starter
source .env

uv run deploy/deploy_orchestrator.py --action deploy

Cette opération prend environ 5 à 10 minutes. Une fois l'opération terminée, les AGENT_ENGINE_ID et AGENT_ENGINE_RESOURCE_NAME sont enregistrés dans .env.

source .env
echo "Agent Engine ID: $AGENT_ENGINE_ID"
echo "Resource: $AGENT_ENGINE_RESOURCE_NAME"

Fonctionnement du déploiement

client.agent_engines.create() empaquète votre objet App, l'importe avec ses dépendances et le déploie sur une infrastructure gérée. Voici à quoi sert chaque paramètre :

import vertexai
from vertexai import Client, agent_engines

vertexai.init(project=PROJECT_ID, location=LOCATION, staging_bucket=STAGING_BUCKET)

# Wrap the App in an AdkApp adapter - enables tracing in Cloud Trace
adk_app = agent_engines.AdkApp(app=root_app, enable_tracing=True)

# Initialize client and deploy
client = Client(project=PROJECT_ID, location=LOCATION)

agent_engine_resource = client.agent_engines.create(
    agent=adk_app,
    config={
        "staging_bucket": STAGING_BUCKET,   # GCS bucket for packaging artifacts
        "display_name": "Creative Director",
        # Python packages installed in the managed runtime - pin for reproducibility
        "requirements": [
            "google-cloud-aiplatform[agent_engines]>=1.132.0,<2.0.0",
            "google-adk[a2a]==1.31.1",
            "google-genai>=1.70.0",
            "google-cloud-storage>=2.10.0",
            "python-dotenv>=1.0.0",
            "pydantic>=2.0.0",
            "cloudpickle>=3.0.0",
        ],
        # Specialist URLs passed as env vars - the orchestrator reads these at runtime
        "env_vars": {
            "COPYWRITER_AGENT_URL": COPYWRITER_URL,
            "DESIGNER_AGENT_URL":   DESIGNER_URL,
            "STRATEGIST_AGENT_URL": STRATEGIST_URL,
            "CRITIC_AGENT_URL":     CRITIC_URL,
            "PM_AGENT_URL":         PM_URL,
        },
    },
)

resource_name = agent_engine_resource.api_resource.name
agent_engine_id = resource_name.split("/")[-1]

Ce qui se passe en arrière-plan :

1. Agent Engine packages your App + requirements into a container
2. Uploads it to the staging bucket in your project
3. Deploys to managed compute (you never see or manage the VM)
4. Returns a resource name: projects/.../locations/.../reasoningEngines/<id>
5. That ID is saved to .env as AGENT_ENGINE_ID

Après le déploiement, l'orchestrateur se connecte aux cinq spécialistes Cloud Run via les URL de ses variables d'environnement.

  • Ces variables sont transmises via .env avant l'exécution du script de déploiement.

14. Exécuter une campagne de bout en bout

L'ensemble du système est déployé. Exécutez une campagne complète depuis l'atelier de programmation Agent Runtime.

Ouvrir le bac à sable Agent Runtime

  1. Accédez à https://console.cloud.google.com/agent-platform/runtimes. Vous pouvez également accéder à Agent Runtime depuis Agent Platform > Agents > Déploiements.
  2. Sélectionnez votre environnement d'exécution de l'agent déployé (creative-director).
  3. Cliquez sur Playground dans la barre latérale de gauche.
  4. Cliquez sur Nouvelle session pour ouvrir une nouvelle conversation.

Lancer une campagne complète

Collez ce brief dans le chat et envoyez-le :

Create a complete Instagram campaign for:
- Product: EcoFlow Smart Water Bottle (tracks hydration, keeps drinks cold 24h)
- Target Audience: Health-conscious millennials, 25-35 years old
- Platform: Instagram
- Goal: Brand awareness + drive website traffic
- Brand Voice: Motivational, clean, science-backed
- Budget: $3,000
- Timeline: Launch in 2 weeks

Le directeur de création exécutera les cinq agents dans l'ordre :

  1. Responsable de la stratégie de marque : études de marché, analyse de la concurrence, insights sur l'audience
  2. Rédacteur → 3 posts Instagram avec légendes, hashtags et CTA
  3. Concepteur : concepts visuels et images réelles générés par Gemini (URI GCS) pour chaque post
  4. Critique : examen de la qualité avec les scores APPROUVÉ / À REVOIR
  5. (Révision si nécessaire) → Le rédacteur ou le concepteur est à nouveau contacté pour obtenir des commentaires
  6. Gestionnaire de projet : calendrier sur deux semaines, répartition des tâches, allocation du budget

Démo : Exécution d&#39;une campagne avec l&#39;intégration Notion

Tester le routage mono-agent

Envoyez cette requête plus courte dans une nouvelle session :

Research the luxury skincare market - top brands and trends in 2025

Notez que le directeur de la création transmet la demande uniquement au responsable de la stratégie de marque. Aucun autre agent n'est appelé. La logique de classification des demandes à partir de l'instruction système fonctionne correctement.

Inspecter les traces d'exécution

Toujours dans la console :

  1. Cliquez sur Traces dans la barre latérale de gauche (à côté de Playground).
  2. Sous Vue Trace, sélectionnez la trace de la session que vous venez d'exécuter.
  3. Développez l'arborescence de trace pour afficher chaque appel d'agent, ses entrées/sorties, sa latence et son utilisation de jetons.

Chaque appel A2A à un spécialiste apparaît sous la forme d'un segment distinct. Vous pouvez voir exactement le contexte que le directeur artistique a transmis à chaque agent et ce qu'il a reçu en retour.

Facultatif : Exécuter depuis le terminal

Vous pouvez également exécuter la campagne de manière programmatique à l'aide du script run_campaign.py déjà inclus dans le starter.

cd ~/ai-creative-studio/workshop/starter
uv run run_campaign.py

15. Effectuer un nettoyage

Nettoyez les ressources Google Cloud pour éviter des frais continus.

Exécutez le script de suppression. Il lit votre .env et supprime tout ce qui a été créé lors de cet atelier de programmation :

bash deploy/teardown_gcp.sh

Le script vous indiquera exactement ce qu'il va supprimer et vous demandera une confirmation avant de faire quoi que ce soit :

Ressource

Éléments supprimés

Services Cloud Run

stratège de marque, rédacteur, concepteur, critique, chef de projet

Environnement d'exécution de l'agent

Moteur de raisonnement du directeur de la création + toutes les sessions

Artifact Registry

Dépôt cloud-run-source-deploy + toutes les images Docker

Buckets GCS

{PROJECT_ID}-campaign-images, {PROJECT_ID}-agent-staging, run-sources-{PROJECT_ID}-{REGION}

Secret Manager

notion-token, notion-project-db-id, notion-tasks-db-id (ignoré si non créé)

Vérifier que tout a été supprimé

gcloud run services list --region=us-central1
gcloud storage buckets list --project=$GCP_PROJECT_ID

Résultat attendu : listes vides ou uniquement vos propres ressources préexistantes.

16. Résumé

Félicitations ! Vous avez créé et déployé un système d'IA multi-agent de qualité production sur Google Cloud.

Ce que vous avez créé

Agent

Capacité

Déploiement

Expert en stratégie de marque

Études de marché via la recherche Google

Cloud Run

Rédacteur

Création de légendes Instagram

Cloud Run

Graphiste

Génération d'images avec Gemini et importation dans GCS

Cloud Run

Critique

Contrôle qualité avec notation

Cloud Run

Chef de projet

Chronologie + Notion MCP

Cloud Run

Directeur de la création

Orchestration complète via A2A

Environnement d'exécution de l'agent

Principaux schémas que vous avez appris

  1. ADKAgent : définir un agent LLM avec une instruction et des outils facultatifs
  2. adk web : exécutez et testez n'importe quel agent ADK localement avec une interface utilisateur de chat intégrée.
  3. SkillToolset : regroupez les connaissances réutilisables dans des fichiers modulaires chargés à la demande.
  4. FunctionTool : encapsulez n'importe quelle fonction Python (ou modèle externe) en tant qu'outil d'agent appelable.
  5. to_a2a() : expose tout agent ADK en tant que service HTTPS conforme à A2A
  6. RemoteA2aAgent + AgentTool : orchestrez les agents à distance en tant qu'outils appelables
  7. McpToolset : se connecter à des services externes via des serveurs stdio MCP
  8. EventsCompactionConfig : gérer les limites de jetons dans les longs workflows multi-agents
  9. Sortie structurée du critique : contrôle qualité lisible par machine avec révision automatique
  10. Cloud Run : déployez des agents conteneurisés à grande échelle
  11. Agent Runtime : orchestreurs hôtes avec sessions et traçage gérés

Étapes suivantes

  • Ajouter la modification d'images en plusieurs étapes au Designer à l'aide de la fonctionnalité de modification de gemini-3.1-flash-image-preview
  • Ajouter l'authentification IAM aux services Cloud Run (supprimer --allow-unauthenticated)
  • Remplacez un spécialiste par un agent LangGraph ou CrewAI : A2A est indépendant du framework.
  • Ajoutez l'outil Commentaires des utilisateurs pour permettre aux participants de noter les résultats et de les améliorer.
  • Explorer le traçage de l'environnement d'exécution de l'agent dans la console Cloud

Ressources