Crea un estudio creativo multiagente con la pila de agentes de Google: ADK, A2A y MCP en Cloud Run y Agent Runtime

1. Descripción general

En este codelab, crearás AI Creative Studio, un sistema multiagente distribuido que convierte una sola instrucción en una campaña completa de Instagram.

Escribe una oración. Obtén investigación de público, subtítulos, conceptos visuales, texto revisado para garantizar la calidad y un cronograma completo del proyecto, todo generado por un equipo de agentes de IA que colaboran entre sí.

Los agentes que compilarás

Agente

Rol

Estratega de marca

Busca en la Web estadísticas del público, análisis de la competencia y tendencias para el 2025.

Redactor creativo

Escribe leyendas de Instagram con hashtags y CTA, con una habilidad del ADK que carga las instrucciones de la plataforma y las fórmulas de leyendas a pedido.

Designer

Crea conceptos visuales y genera imágenes reales a través de Gemini, que se almacenan en GCS.

Crítico

Copias y elementos visuales de opiniones: Devuelve APPROVED o NEEDS_REVISION con comentarios específicos

Administrador de proyectos

Crea un cronograma del proyecto y un desglose de tareas, que se pueden sincronizar con Notion a través del MCP.

Director creativo

Coordina a los cinco especialistas en secuencia: le das una instrucción y coordina el resto.

Los 5 agentes se implementan como microservicios independientes de Cloud Run. Se comunican a través del protocolo A2A, un estándar abierto independiente del lenguaje para que cualquier agente pueda llamar a cualquier otro agente, independientemente del framework. El director creativo se ejecuta en Agent Runtime y se conecta con cada especialista de forma remota.

Arquitectura

Descripción general del sistema

Qué aprenderás

  • Crea agentes de LLM con el ADK de Google: Agent, instrucciones del sistema y herramientas integradas.
  • Empaqueta el conocimiento reutilizable del agente en archivos modulares con habilidades del ADK (SkillToolset).
  • Generar imágenes reales conectando un agente de texto a un modelo de imagen a través de un FunctionTool
  • Integra APIs externas sin código de unión personalizado con el Protocolo de contexto del modelo (MCP).
  • Convierte cualquier agente en un servicio al que se pueda llamar desde la red con el protocolo Agent to Agent (A2A) a través de HTTPS.
  • Organiza agentes distribuidos con RemoteA2aAgent y AgentTool.
  • Empaqueta e implementa agentes independientes como microservicios de Cloud Run.
  • Aloja un organizador con estado en Agent Runtime.
  • Mantén los flujos de trabajo multiagente largos dentro de los límites de contexto con la compactación de contexto.
  • Crea un ciclo de control de calidad: Las opiniones de los críticos generan una revisión automática cuando es necesario.

Requisitos

  • Un proyecto de Google Cloud con la facturación habilitada
  • Rol de IAM de propietario o editor
  • Conocimientos básicos de Python

2. Configura tu entorno

En este codelab, usaremos Cloud Shell.

¿Qué es Cloud Shell?

Cloud Shell es un entorno de Linux gratuito basado en el navegador con todo preinstalado: gcloud, git, Python, Docker y mucho más. No es necesario que instales nada de forma local.

Para abrir Cloud Shell, haz clic en el ícono de terminal en la barra de herramientas superior derecha de la consola de GCP:

Abre Cloud Shell desde la barra de herramientas de GCP Console

Cuando abras Cloud Shell por primera vez, se te pedirá que verifiques tu cuenta. Haz clic en Verificar:

Diálogo de verificación de la cuenta

Luego, haz clic en Autorizar para permitir que Cloud Shell realice llamadas a la API de Google Cloud:

Diálogo de autorización de Cloud Shell

Cloud Shell ya está listo. Verás un mensaje de bienvenida en la terminal: Terminal de Cloud Shell lista

Autentica y configura tu proyecto

Cloud Shell ya está autenticado con tu Cuenta de Google. Confirma tu cuenta activa y encuentra el ID del proyecto:

gcloud config list

También puedes ver tu ID del proyecto en el panel de la izquierda del panel de GCP Console. Cópiala, ya que la necesitarás en el siguiente comando:

Busca el ID del proyecto en GCP Console y configúralo en Cloud Shell

Ahora, configura tu proyecto:

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

Resultado esperado:

Project: my-project-123

Habilita las API obligatorias

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

Este proceso tarda unos 2 minutos. Verás Operation finished successfully cuando termines.

Configura las credenciales predeterminadas de la aplicación (ADC)

Los agentes llamarán a Gemini Enterprise Agent Platform con la biblioteca de Google Auth, que requiere credenciales predeterminadas de la aplicación, independientes de la autenticación de la CLI de gcloud.

Ejecuta este comando una vez:

gcloud auth application-default login

Se abrirá una pestaña del navegador en la que se te pedirá que confirmes la acción. Haz clic en Permitir. En esta página verá lo siguiente:

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

Clona el repositorio inicial

En este codelab, se usa un repositorio de inicio, un proyecto esqueleto con toda la infraestructura en su lugar (Dockerfiles, pyproject.toml, secuencias de comandos de implementación), pero con la lógica del agente que debes escribir.

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

Cada agent.py contiene marcadores de posición # TODO en los que escribirás la lógica del agente. Las secuencias de comandos Dockerfile, pyproject.toml y de implementación ya están completas.

Configure las variables de entorno

Copia el ejemplo proporcionado y, en un solo paso, inserta el ID de tu proyecto:

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

Luego, crea el bucket de GCS en el que el diseñador almacenará las imágenes generadas y actualiza .env con su nombre:

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

Luego, configura la compatibilidad con URLs de imágenes firmadas. El director creativo genera vínculos HTTPS en los que se puede hacer clic para cada imagen en el resumen final de la campaña. Esto requiere una cuenta de servicio para firmar las URLs. Ejecuta estos comandos para configurarla:

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

Abre .env en el editor para revisar todos los parámetros de configuración:

cloudshell edit .env

Se abrirá .env como una pestaña en el editor de Cloud Shell. Haz clic en el botón Abrir editor en la barra de herramientas si el panel del editor no está visible:

Haz clic en Abrir editor en la barra de herramientas de Cloud Shell.

Editor de Cloud Shell con el árbol de archivos del proyecto

Confirma que el proyecto se haya configurado correctamente:

grep GOOGLE_CLOUD_PROJECT .env

Instala dependencias

Usamos uv, un administrador de paquetes de Python moderno y rápido que controla los entornos virtuales y se instala en una sola herramienta. Es entre 10 y 100 veces más rápido que pip y es la forma recomendada de administrar proyectos de Python.

Cloud Shell ya tiene instalado uv. Todos los agentes comparten las mismas dependencias principales, por lo que se instalan una sola vez y funcionan para todos los agentes de este codelab:

uv sync

El comando uv sync lee pyproject.toml y crea un directorio .venv/ con todas las dependencias. Cada especialista también tiene su propio pyproject.toml que usan exclusivamente las compilaciones de Docker. La instalación compartida anterior abarca todo lo que necesitas para las pruebas locales.

3. Información sobre el ADK de Google

Antes de escribir código, comprendamos el Kit de desarrollo de agentes (ADK), el framework que usarás para compilar cada agente en este codelab.

¿Qué es el ADK?

El Kit de desarrollo de agentes (ADK) es un framework flexible y modular para desarrollar y, luego, implementar agentes de IA. Si bien el ADK está optimizado para Gemini y el ecosistema de Google, es independiente del modelo y de la implementación, y se creó para ser compatible con otros frameworks. El ADK se diseñó para que el desarrollo de agentes se parezca más al desarrollo de software, de modo que los desarrolladores puedan crear, implementar y organizar con mayor facilidad arquitecturas basadas en agentes que abarcan desde tareas simples hasta flujos de trabajo complejos.

El ADK se encarga de las partes complejas (llamadas a herramientas, conversaciones de varios turnos, administración del contexto, transmisión) para que puedas enfocarte en la lógica del agente.

Componentes básicos de un agente del ADK

Cada agente se compone de cuatro bloques de creación:

Bloquear

Rol

Modelo

El LLM que razona sobre los objetivos, determina un plan y genera respuestas

Herramientas

Funciones que recuperan datos o realizan acciones llamando a APIs o servicios

Organización

Mantiene la memoria y el estado en los distintos turnos, enruta las llamadas a herramientas y pasa los resultados al modelo.

Tiempo de ejecución

Ejecuta el sistema cuando se invoca, ya sea de forma local a través de adk web o como un servicio implementado.

Definición del agente

Cada uno de los 5 agentes de este codelab se define de la misma manera:

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
)

Campo

Objetivo

name

ID único: Los orquestadores lo usan para enrutar llamadas.

model

El modelo de Gemini que respalda a este agente

instruction

Instrucción del sistema: Define el rol, las restricciones y el formato de salida del agente.

description

Resumen de una línea: El organizador lee esto para decidir a qué especialista llamar.

tools

Funciones que el LLM puede invocar (integradas, como google_search, o funciones personalizadas de Python)

Cómo ejecuta un agente el ADK

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

El LLM decide de forma autónoma si llamar a una herramienta, qué herramienta y con qué argumentos. Tú escribes la instrucción y el ADK se encarga del resto.

4. Compila y prueba el agente de Brand Strategist

Comencemos con el primer agente: el estratega de marca. Este es un agente solo de investigación para buscar estadísticas del público objetivo, análisis de la competencia y temas en tendencia con la Búsqueda de Google.

Abre el archivo de agente esqueleto en el editor de Cloud Shell:

cloudshell edit agents/brand_strategist/agent.py

Verás dos secciones de # TODO que deberás completar.

TAREA PENDIENTE 1: Escribe la instrucción del sistema

Primero, escribirás la instrucción del sistema para el agente. La instrucción del sistema es una cadena que define el rol, las restricciones y el formato de salida del agente.

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: Crea el root_agent

A continuación, reemplaza el root_agent incompleto por lo siguiente:

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],
)

Realiza pruebas locales con la IU web del ADK

Ahora probemos el agente con la IU web del ADK, una interfaz de chat integrada para probar agentes antes de implementarlos en la nube.

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

En esta página verá lo siguiente:

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

El servidor ahora se ejecuta dentro de Cloud Shell:

Para abrirla en tu navegador, usa Vista previa en la Web:

  1. Consulta la barra de herramientas de Cloud Shell en la parte superior de la página.
  2. Haz clic en el ícono de Vista previa en la Web (parece una caja con una flecha hacia arriba, en la parte superior derecha de la barra de herramientas de Cloud Shell).
  3. Haz clic en "Cambiar puerto", ingresa 8000 y, luego, haz clic en "Cambiar y obtener vista previa".

Se abrirá una nueva pestaña del navegador con la IU web del ADK. Haz clic en el menú desplegable "Seleccionar un agente" en la esquina superior izquierda. Verás todos tus agentes en la lista:

Elige brand_strategist para comenzar la prueba:

Prueba estos mensajes

En el cuadro de chat de la IU web del ADK, prueba lo siguiente:

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

Deberías ver que el agente llama a la Búsqueda de Google y devuelve una investigación estructurada con las secciones Estadísticas de público, Análisis de la competencia y Temas populares.

5. Crea el redactor publicitario: Habilidades del ADK

Rol: Convertir la investigación de la marca en leyendas de Instagram El redactor publicitario crea 3 variaciones de leyendas que abarcan diferentes tonos (inspirador, educativo y comunitario), cada una con hashtags y un CTA.

Concepto: Habilidades del ADK

Un enfoque ingenuo sería incorporar todo el conocimiento de la plataforma (límites de caracteres, niveles de hashtags, fórmulas de leyendas, ejemplos de voz de la marca) directamente en la instrucción del sistema. Esto funciona, pero infla cada solicitud con contenido que el agente solo necesita de vez en cuando.

Las habilidades del ADK (SkillToolset, introducidas en el ADK 1.25.0) te permiten empaquetar ese conocimiento en archivos modulares con tres niveles de carga:

  • L1 - frontmatter (name + description en SKILL.md): Siempre disponible, se usa para el descubrimiento de habilidades
  • L2 - instructions (cuerpo de SKILL.md): Se carga cuando el agente activa la habilidad.
  • L3: Recursos (archivos references/ y assets/): Se cargan solo cuando el agente los lee de forma explícita.

La instrucción del sistema se reduce a una breve declaración de rol más "carga la habilidad antes de escribir". Los detalles de la plataforma solo ingresan en la ventana de contexto cuando el agente realmente los necesita.

La habilidad de redacción se encuentra en 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

Abre el archivo directamente en el editor de Cloud Shell:

cloudshell edit agents/copywriter/agent.py

TODO 1: Importa load_skill_from_dir y skill_toolset

Busca el comentario # TODO 1: Import load_skill_from_dir and skill_toolset y agrega las dos importaciones:

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

TODO 2: Carga la habilidad y crea un SkillToolset

Busca los dos comentarios debajo de las importaciones:

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

Reemplázalas por lo siguiente:

_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 lee SKILL.md más cualquier archivo en references/ y assets/. SkillToolset lo adapta al formato que aceptan los agentes del ADK: un conjunto de herramientas, no una habilidad sin procesar.

TAREA PENDIENTE 3: Registra el conjunto de herramientas con el agente

Busca tools=[], # TODO 3: Add the SkillToolset here y reemplázalo por lo siguiente:

tools=[_copywriting_skills],

Abre el archivo de la habilidad para ver cómo está estructurado:

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

Mantén en ejecución la IU web del ADK. Usa el menú desplegable del agente para cambiar a copywriter sin reiniciar el servidor.

Si no se está ejecutando, vuelve a iniciarla:

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

Pruébalo: Cambia el menú desplegable a copywriter y envía el mensaje:

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. Compila el Diseñador: Generación de imágenes multimodales

Mantén en ejecución la IU web del ADK. Usa el menú desplegable del agente para cambiar de agente sin reiniciar el servidor.

Rol: Crear conceptos visuales para cada leyenda y generar las imágenes reales con la generación de imágenes nativa de Gemini El diseñador genera exactamente 1 concepto visual por subtítulo, con una instrucción detallada, un estilo, una paleta de colores, un ambiente y un formato de Instagram. Luego, llama de inmediato a la herramienta generate_image para producir la imagen real y subirla a GCS.

Concepto: Cómo conectar un agente de texto con un modelo de imagen a través de una herramienta

El diseñador se ejecuta en gemini-3-flash-preview (el modelo de texto establecido a través de GEMINI_MODEL en .env), pero la generación de imágenes requiere un modelo dedicado (gemini-3.1-flash-image-preview). Ese modelo de imágenes no admite la llamada a funciones, por lo que no se puede usar directamente como agente de ADK. En cambio, se incluye en una función de Python simple y se registra como FunctionTool.

Este es el patrón para cualquier modelo o API a los que el LLM no puede llamar directamente: envuélvelo en una herramienta, deja que el agente coordine cuándo llamarlo y obtén un resultado estructurado.

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)

Abre el archivo directamente en el editor de Cloud Shell:

cloudshell edit agents/designer/image_gen_tool.py

Se proporcionan la firma de la función, la configuración del entorno y la inserción de la relación de aspecto. Realiza los tres TODOs en orden:

TODO 1: Llama al modelo de imagen de Gemini

Busca el comentario # TODO 1 y reemplázalo por lo siguiente:

        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: Extrae los bytes de la imagen de la respuesta

Busca el comentario # TODO 2 y reemplázalo por lo siguiente:

        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: Sube el archivo a GCS y devuelve el URI

Busca el comentario # TODO 3 y reemplázalo por lo siguiente:

        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}"

Pruébalo: Cambia el menú desplegable a designer y envía el mensaje:

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. Crea el crítico: Resultado estructurado

Rol: Garantizar la calidad de las copias y los elementos visuales antes de entregarlos al administrador de proyectos El Critic califica ambos entregables y devuelve APPROVED o NEEDS_REVISION con sugerencias específicas. Cuando hay valores de gcs_uri en la entrada, se llama a la herramienta review_image para inspeccionar visualmente cada imagen generada antes de asignarle una puntuación.

Concepto: Cuándo usar un modelo de Pydantic para el resultado de Gemini

La regla se relaciona con quién consume el resultado:

  • El código de Python lo consume → usa response_schema + Pydantic. El código no puede controlar la ambigüedad, por lo que necesitas una estructura garantizada para extraer campos de manera confiable.
  • Un LLM lo consume → El formato de texto y la instrucción del sistema son suficientes. Los LLM comprenden las reglas de formato y toleran las variaciones.

En review_image, el código de Python necesita score, approval_status, what_works, issues y suggestions como valores escritos. Pasar response_schema=_GeminiReview restringe Gemini a nivel de API para que devuelva JSON válido. model_validate_json() lo analiza y lo convierte en un objeto con tipo que tu código puede usar de manera confiable.

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

Abre el archivo directamente en el editor de Cloud Shell:

cloudshell edit agents/critic/image_review_tool.py

Se proporcionan los modelos Pydantic y la instrucción. Realiza los tres TODOs en orden:

TODO 1: Crea una parte de imagen a partir del URI de GCS

Busca el comentario # TODO 1 y reemplázalo por lo siguiente:

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

TODO 2: Llama a Gemini con un esquema de respuesta estructurada

Busca el comentario # TODO 2 y reemplázalo por lo siguiente:

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

TAREA 3: Analiza la respuesta y devuelve el resultado

Busca el comentario # TODO 3 y reemplázalo por lo siguiente:

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

Pruébalo: Cambia el menú desplegable a critic y envía el mensaje:

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.

Verifica que la respuesta contenga **POSTS REVIEW:**, Status: APPROVED (o NEEDS_REVISION) y **OVERALL ASSESSMENT:**. Si esas secciones están presentes, el Critic está listo para conectarse al orquestador.

Cuando termines de probar los tres agentes, presiona Ctrl+C para detener el servidor.

8. Compila el agente de Project Manager con MCP

El administrador de proyectos presenta un nuevo concepto: MCP (Protocolo de contexto del modelo).

Abre el archivo:

cloudshell edit agents/project_manager/agent.py

Este archivo es más complejo, ya que tiene una función create_project_manager_agent() con dos ramas: una sin Notion (líneas de tiempo solo de texto) y otra con el conjunto de herramientas de MCP de Notion. Completarás ambos.

El problema que resuelve el MCP

Tu agente necesita llamar a un servicio externo, por ejemplo, crear una página en Notion. Podrías escribir código de Python que llame directamente a la API de REST de Notion. Pero, luego, haz lo siguiente:

  • Cada desarrollador escribe un wrapper diferente
  • Debes mantener el código de integración personalizado.
  • El LLM no sabe que existe la API, a menos que describas cada extremo de forma manual.

El MCP resuelve este problema definiendo una forma estándar para que los servicios externos expongan sus capacidades como herramientas que un LLM puede descubrir y llamar automáticamente.

¿Qué es el MCP?

El MCP (Protocolo de contexto del modelo) es un estándar abierto (publicado por Anthropic) para conectar agentes de IA a herramientas y fuentes de datos externas. Funciona como un adaptador universal.

Un servidor de MCP es un programa pequeño que realiza las siguientes acciones:

  1. Encapsula una API externa (Notion, GitHub, bases de datos, sistemas de archivos, etc.).
  2. Expone esa API como una lista de herramientas documentadas y con tipos
  3. Se comunica con el agente a través de un protocolo simple (stdio o HTTP).

El agente se conecta al servidor de MCP, descubre automáticamente las herramientas disponibles y puede llamarlas como cualquier otra herramienta. El LLM ve API-post-page(...) como una función llamable.

A2A vs. MCP: ¿Cuál es la diferencia?

Este es un punto que suele generar confusión. Esta es la distinción clave:

A2A

MCP

Qué se conecta

Agente ↔ Agente

Agente ↔ Herramienta o servicio externo

El otro lado es

Otro agente de LLM

Un wrapper de API (sin LLM)

Ejemplo

El director creativo llama al estratega de marca

El administrador de proyectos llama a la API de Notion

Protocolo

JSON-RPC a través de HTTPS

Flujo de stdio o HTTP

Definido por

Google

Anthropic

Analízalo de la siguiente forma:

  • A2A = Cómo los agentes hablan con otros agentes
  • MCP = Cómo los agentes se comunican con las herramientas y los servicios

En este proyecto, ambos se usan juntos:

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

Cómo funciona el MCP en este proyecto

Cuando se ejecuta el agente, el ADK inicia notion-mcp-server como un proceso secundario. Ese proceso expone estas herramientas directamente al LLM:

Herramienta

Qué hace

API-retrieve-a-database

Recupera el esquema (nombres de propiedades, tipos, valores válidos)

API-post-database-query

Consultas sobre páginas existentes

API-post-page

Crea una página nueva

API-patch-page

Actualiza una página existente

El LLM las llama como cualquier otra función, no sabe que pasan por MCP a la API de REST de Notion de forma interna.

¿Por qué stdio? ¿Por qué no usar solo HTTP?

El servidor de MCP se ejecuta como un proceso secundario del agente y se comunica a través de stdin/stdout. Esto significa lo siguiente:

  • No se necesita un puerto de red adicional
  • El agente administra el ciclo de vida (se inicia a pedido y se detiene al salir).
  • Todo se envía en una imagen de Docker, sin necesidad de implementar un servicio independiente.

(Opcional) Habilita la integración de Notion

Puedes omitir toda esta sección. El agente de Project Manager siempre genera una línea de tiempo completa de la campaña basada en texto, con o sin Notion. Si omites esta configuración, el agente recurrirá al modo en memoria y mostrará la línea de tiempo como texto sin formato en el chat. No se interrumpirá nada, solo que no verás las tareas en una base de datos de Notion. Ve directamente al TODO 1 si quieres omitir.

Si tienes una cuenta de Notion y quieres ver la integración del MCP en acción, completa la configuración que se indica a continuación. Las tareas pendientes que se indican a continuación hacen referencia a los IDs de la base de datos de Notion, que es donde los obtendrás.

Paso 1: Crea la base de datos de Notion a partir de una plantilla

Usamos la plantilla oficial Proyectos y tareas de Notion como nuestra base de datos. Elegimos esta plantilla deliberadamente para demostrar un entorno complejo del mundo real: tiene varios tipos de propiedades (estado, rangos de fechas, relaciones, selecciones) con nombres no evidentes. Esta es una excelente prueba del descubrimiento dinámico de esquemas de MCP: el agente debe determinar los nombres exactos de las propiedades en el tiempo de ejecución en lugar de tenerlos codificados.

Haz clic en el siguiente vínculo para agregar la plantilla a tu espacio de trabajo de Notion:

→ Agrega la plantilla "Proyectos y tareas" a Notion

Plantilla de Proyectos y tareas de Notion en Marketplace

Una vez que los agregues, tendrás dos bases de datos vinculadas: Projects y Tasks. La plantilla incluye entradas de muestra. Bórralas todas antes de continuar para que el agente comience con un espacio de trabajo limpio (selecciona todo → Borrar).

Paso 2: Crea una integración de Notion

Crea la integración:

  1. Ve a notion.so/my-integrations.
  2. Haz clic en New Integration → asígnale el nombre AI Creative Studio
  3. Asócialo a tu espacio de trabajo
  4. Haz clic en Configurar parámetros → asegúrate de que estén marcadas las capacidades Leer contenido, Actualizar contenido y Insertar contenido.

Configuración de la integración de Notion: asígnale el nombre “AI Creative Studio” y copia el token.

  1. Copia el token de integración interno (ntn_...) y pégalo en tu archivo .env:
NOTION_TOKEN=ntn_your-token-here

Conecta la integración a tus bases de datos:

  1. Abre la página de la plantilla que acabas de duplicar y, luego, haz clic en la base de datos Projects.
  2. Haz clic en el menú ... (esquina superior derecha) → ConexionesAgregar una conexión → selecciona AI Creative Studio.

Haz clic en Connections en el menú de la base de datos para compartir con tu integración.

Aparecerá AI Creative Studio como una conexión activa.

  1. Haz lo mismo con la base de datos de Tareas.

Obtén los IDs de la base de datos:

  1. Haz clic en el vínculo de la base de datos Projects para abrirlo. Se abrirá en su propia página con una URL como la siguiente:
https://www.notion.so/9887b6a94f7f83f68f8581e038d1aaa4?v=2c37b6a94f7f838685f1086e312c7278

Cómo abrir la base de datos de Proyectos desde la página de la plantilla

El ID de la base de datos es el primer UUID de la URL, es decir, todo lo que aparece antes del ?v=:

https://www.notion.so/{DATABASE_ID}?v=...
                       ^^^^^^^^^^^^^^^^
                       9887b6a94f7f83f68f8581e038d1aaa4  ← this is your DATABASE_ID
  1. Haz lo mismo con el vínculo de la base de datos Tasks para obtener su ID.
  2. Agrega los tres valores a tu .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

Paso 3: Instala el servidor de MCP de Notion

El Administrador de proyectos se conecta a Notion a través del paquete oficial de @notionhq/notion-mcp-server Node.js. Instálalo de forma global:

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

Verifica la instalación:

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

Resultado esperado:

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

notion-mcp-server: command not found

? Asegúrate de que Node.js esté instalado (node --version) y de que tu binario global de npm esté en tu PATH (export PATH=$PATH:$(npm bin -g)).

Paso 4: Verifica tu archivo .env

Abre .env y confirma que estén establecidos los tres valores de Notion (los agregaste en el paso 2):

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

El agente de Project Manager detecta automáticamente estas variables al inicio y habilita el conjunto de herramientas del MCP de Notion.

Cómo funciona el descubrimiento de esquemas

El administrador de proyectos usa el descubrimiento dinámico de esquemas, por lo que nunca codifica de forma rígida los nombres de las propiedades de 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

Esto significa que el agente se adapta automáticamente a cualquier estructura de base de datos de Notion. Puedes cambiar el nombre de tus propiedades a francés, árabe o cualquier otro idioma, y el agente seguirá funcionando.

TAREA PENDIENTE 1: Escribe la instrucción del sistema

El iniciador ya calcula notion_section, una cadena vacía cuando Notion no está configurado, o un bloque que contiene los IDs de la base de datos y la guía completa de la herramienta cuando sí lo está. Esto mantiene las instrucciones de Notion completamente fuera de la instrucción del agente que no usa Notion. El LLM nunca ve reglas para las herramientas que no tiene.

Tu trabajo es reemplazar el marcador de posición return por una instrucción del sistema real que use {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: Agente sin Notion

Dentro de create_project_manager_agent(), en la rama if not notion_token, reemplaza el agente incompleto por lo siguiente:

        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: Agente con MCP de Notion

Nota: El archivo de inicio ya contiene una devolución de llamada handle_notion_error escrita previamente arriba create_project_manager_agent(). Intercepta los errores de la API de Notion (400/404) y reemplaza las cargas útiles de error sin procesar por mensajes claros y prácticos para que el LLM pueda autocorregirse. Solo debes conectarlo a través de after_tool_callback.

Primero, lee ambos IDs de la base de datos en la parte superior 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")

Luego, en la rama else, crea el conjunto de herramientas del MCP y el agente:

        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],
        )

Práctica recomendada: Nunca falles de forma definitiva en las integraciones opcionales. La línea de tiempo de texto siempre es el resultado principal; Notion es complementario.

Prueba el administrador de proyectos de forma local con la Web del ADK

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

Abre la vista previa en la Web en el puerto 8000. Usa el menú desplegable del agente para seleccionar project_manager y, luego, prueba lo siguiente:

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.

Deberías ver un cronograma de texto estructurado con fases, una lista de tareas y logros. Si se configuran las credenciales de Notion en .env, el agente también creará entradas en tu espacio de trabajo de Notion.

9. Información sobre el protocolo A2A

Usaremos el protocolo Agent-to-Agent (A2A) para conectar los diferentes agentes de nuestro sistema. Veamos cómo funciona.

El problema que resuelve A2A

Imagina que tienes un agente de Brand Strategist creado con el ADK y un agente de Copywriter creado con LangGraph. ¿Cómo se llama una a la otra? Hablan diferentes lenguajes internos. Tendrías que escribir código de unión personalizado cada vez.

A2A resuelve este problema definiendo un lenguaje universal que cualquier agente, independientemente del framework, puede hablar. Es el HTTP del mundo de los agentes: un estándar con el que todos están de acuerdo para que cualquiera pueda hablar con cualquiera.

¿Qué es A2A?

Agent-to-Agent (A2A) es un estándar abierto para la comunicación entre agentes publicado por Google. Define lo siguiente:

  1. Cómo se describe un agente: Tarjeta de agente en /.well-known/agent.json
  2. Cómo lo llama otro agente: JSON-RPC a través de HTTPS
  3. Cómo se devuelven los resultados: transmisión o respuesta única

Qué hace que A2A sea flexible:

  • Independencia del idioma: Los agentes de Python pueden comunicarse con los agentes de TypeScript
  • Independencia del framework: Los agentes del ADK pueden comunicarse con agentes de LangGraph o CrewAI
  • Independencia de la infraestructura: Los agentes locales pueden comunicarse con los agentes de la nube

Cómo funciona (paso a paso)

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    │
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│
      │  ◄───────────────────────────────│

Paso 1: Descubrimiento: El orquestador recupera la tarjeta del agente una vez para conocer su nombre, URL y capacidades.

Paso 2: Invocación: El orquestador envía una tarea a través de JSON-RPC POST. El cuerpo contiene el mensaje (la instrucción para el especialista).

Paso 3: Respuesta: El especialista transmite su respuesta en fragmentos, al igual que una llamada normal al LLM.

La tarjeta de agente

Cada agente publica una autodescripción en /.well-known/agent.json. Es como una tarjeta de presentación: le indica al mundo lo que el agente puede hacer y dónde comunicarse con él:

{
  "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"
    }
  ]
}

El orquestador lee esta tarjeta para compilar su objeto RemoteA2aAgent, sin necesidad de tener conocimientos codificados de forma rígida sobre el funcionamiento interno del especialista.

Cómo exponer un agente a través de A2A en el ADK

to_a2a() une cualquier agente del ADK en una app de FastAPI compatible con A2A. Una línea:

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)

Esto crea automáticamente lo siguiente:

  • /.well-known/agent.json: La tarjeta de agente
  • /: Es el extremo JSON-RPC (todas las solicitudes de tareas de A2A van a la ruta raíz).

10. Exponer agentes como servicios A2A

Para exponer agentes como servicios A2A, puedes usar la función de utilidad to_a2a() del ADK.

Cómo funciona 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() une tu agente del ADK en una aplicación FastAPI que expone automáticamente lo siguiente:

  • /.well-known/agent.json: La tarjeta del agente (nombre, descripción y capacidades)
  • /a2a/{agent_name}: Es el extremo JSON-RPC para recibir tareas.

El código esqueleto de cada agente ya incluye un bloque __main__ que encapsula al agente en un servidor de A2A con to_a2a(). No es necesario que escribas este código, ya que se proporciona.

Información sobre la configuración de URL doble

Cuando ejecutas python agent.py, el bloque __main__ usa dos configuraciones de URL separadas:

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

Entorno

HOST:PORT (reproducciones)

PUBLIC_HOST:PUBLIC_PORT (se anuncia en la tarjeta del agente)

Local

0.0.0.0:8082

http://localhost:8082

Cloud Run

0.0.0.0:8080

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

Localmente, ambos apuntan a la misma máquina. En Cloud Run, el contenedor escucha internamente en 8080, pero la tarjeta del agente debe anunciar la URL pública de HTTPS. De lo contrario, el director creativo no podrá comunicarse con el especialista desde fuera del contenedor.

Inicia los 5 servidores A2A especializados

Ejecutemos los 5 especialistas como servidores A2A de forma simultánea y, luego, probemos al director creativo de forma local apuntando a ellos.

Abre 5 terminales de Cloud Shell separadas (haz clic en el ícono + en la barra de pestañas de la terminal) y ejecuta un agente por terminal.

uv run activa automáticamente el .venv. No se necesita source manual en cada terminal.

Terminal 1: Brand Strategist (puerto 8082):

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

Terminal 2: Redactor publicitario (puerto 8083):

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

Terminal 3: Diseñador (puerto 8084):

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

Terminal 4: Crítico (puerto 8085):

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

Terminal 5: Administrador de proyectos (puerto 8086):

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

Establece URLs de localhost en .env

En Terminal 6, actualiza .env con las URLs del agente local para que el director creativo pueda encontrarlas:

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

Inspecciona agentes con el Inspector de A2A

El Inspector de A2A es una herramienta para desarrolladores de código abierto que utiliza el protocolo A2A de forma nativa. Te permite conectarte directamente a cualquier agente A2A en ejecución, leer su tarjeta de agente y enviar tareas, todo sin escribir código de cliente.

Qué te muestra:

  • Tarjeta del agente: Son los metadatos estructurados que anuncia tu agente: su nombre, descripción, modos de entrada y salida admitidos, y la URL del extremo. Esto es lo que lee el director creativo cuando descubre a un especialista.
  • Interfaz de chat: Envía cualquier mensaje al agente a través de A2A y consulta la respuesta sin procesar. Puedes probar instrucciones de forma aislada antes de conectar los agentes.
  • Validación de protocolo: El inspector verifica que la tarjeta del agente cumpla con la especificación de A2A y muestra los campos faltantes o las respuestas con formato incorrecto de forma anticipada.

Por qué es importante: Cuando realices la implementación en Cloud Run más adelante, el director creativo descubrirá a cada especialista recuperando su tarjeta de agente de /.well-known/agent.json. Si la tarjeta es incorrecta (URL incorrecta o faltan capacidades), el organizador falla de forma silenciosa. El inspector te permite detectar estos problemas de forma local antes de cualquier implementación en la nube.

Tarjeta de agente de Brand Strategist

La tarjeta del agente muestra la identidad y las capacidades del especialista exactamente como las ven los demás agentes.

Detalles de la tarjeta de agente

Instala y ejecuta el inspector

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

La actualización de .env es un comando único. A continuación, usa Terminal 6 para iniciar el inspector:

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

Para abrir la IU del inspector, usa Vista previa en la WebCambiar puerto → escribe 5001.

Conéctate con el Brand Strategist

Ingresa http://localhost:8082 en el campo de URL del inspector y haz clic en Conectar. El inspector recupera la tarjeta del agente y muestra los metadatos del especialista.

El inspector de A2A conectado al Brand Strategist

Qué te indica la tarjeta del agente

La tarjeta de agente es más que metadatos: es el contrato de capacidad completo que el agente anuncia a la red. Conéctate al Administrador de proyectos (http://localhost:8086) para ver el ejemplo más completo:

{
  "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"
    }
  ]
}

Se destacan tres aspectos:

1. Las herramientas del MCP se convierten en habilidades de A2A: Cada herramienta de Notion a la que tiene acceso el administrador de proyectos (API-post-page, API-retrieve-a-database, etcétera) se muestra como una habilidad independiente en la tarjeta del agente. Cualquier otro agente de la red puede descubrir exactamente qué herramientas puede usar este agente, sin leer ningún código.

2. La instrucción del sistema está integrada: El primer description de la habilidad contiene la instrucción completa del sistema, incluida la fecha de hoy y los IDs de la base de datos de Notion. Así es como el director creativo sabe qué pasar cuando llama al gerente de proyectos.

3. La URL es el extremo en vivo: El campo url es exactamente lo que usa RemoteA2aAgent cuando el director creativo llama a este especialista. Si la URL de la tarjeta es incorrecta, el organizador no podrá comunicarse con el agente.

Por eso, el inspector es una herramienta de depuración potente: con solo mirar la tarjeta del agente, puedes saber si se está ejecutando, qué herramientas tiene y si el extremo es correcto.

Envía un mensaje de prueba

Una vez que te conectes, escribe una instrucción en el panel de chat y envíala. El inspector la envía como una tarea de A2A y transmite la respuesta, de la misma manera en que el director creativo llamará a este agente en producción.

Chatea con un Brand Strategist a través del Inspector de A2A

Apunta el inspector a cualquier puerto local (8082-8086) para probar cada especialista de forma individual.

11. Cómo compilar el organizador de directores creativos

El director creativo es el principal organizador. Lee URLs especializadas de las variables de entorno, ajusta cada una como un RemoteA2aAgent y las expone como AgentTools a los que puede llamar el LLM.

Asegúrate de que los 5 agentes especializados sigan en ejecución (terminales 1 a 5 del paso 10).

En la terminal 6 (la terminal del Inspector de A2A), detén el inspector con Ctrl+C.

Abre el archivo:

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

Este archivo tiene tres TODOs. Trabaja en ellos en orden.

TAREA PENDIENTE 1: Revisar la instrucción del sistema ya escrita

La instrucción del sistema se encuentra en prompt.py en el mismo directorio y se importa automáticamente:

from .prompt import SYSTEM_INSTRUCTION_TEMPLATE

Abre prompt.py para leerlo antes de continuar:

cloudshell edit agents/creative_director/prompt.py

Es importante comprenderlo porque controla todo el comportamiento de la organización.

Por qué la instrucción del organizador controla todo

Abre prompt.py junto a esta sección. Los ejemplos que se muestran a continuación hacen referencia a partes específicas de ella.

La instrucción en prompt.py no es solo documentación, sino que es el plano de control de todo el sistema. Una instrucción del organizador mal estructurada produce lo siguiente: agentes llamados fuera de orden, contenido generado por el organizador en lugar de especialistas, flujos de trabajo que continúan después de las fallas y contexto que se descarta de forma silenciosa entre los agentes. Estos nueve elementos evitan las fallas más comunes:

Elemento 0: Primero planifica y, luego, ejecuta

Este es el elemento más importante. Antes de llamar a cualquier especialista, se le indica al orquestador que genere un plan numerado:

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

Sin este paso, el LLM pasa directamente a las llamadas a herramientas y pierde la noción de dónde se encuentra en el flujo de trabajo, especialmente después de recibir una respuesta larga de un especialista. Primero, delinear el plan ancla el orquestador: sabe en qué paso está, qué sigue y cómo se ve una ejecución completa. Si se omite, el organizador se detendrá a mitad del flujo de trabajo o repetirá pasos.

Elemento 1: Definición explícita del rol

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

Sin la prohibición explícita, a veces el LLM omitirá llamar a especialistas y generará contenido directamente, ya que es más rápido y "sabe" cómo hacerlo. La instrucción debe hacer que esto sea incorrecto.

Elemento 2: Sintaxis de llamada a herramienta con patrones incorrectos

Mostrar solo la sintaxis correcta no es suficiente. El LLM puede generar llamadas que parecen plausibles, pero fallan de forma silenciosa. La instrucción incluye explícitamente el patrón correcto y los que nunca se deben usar:

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

Enumerar los patrones incorrectos de forma explícita redujo las llamadas a herramientas con formato incorrecto en aproximadamente un 95% en producción.

Elemento 3: Ejecución secuencial explicada paso a paso

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

Sin los pasos (b) y (c), a veces, el LLM llamará a dos agentes de forma simultánea o asumirá que la operación se realizó correctamente y continuará antes de recibir la respuesta.

Elemento 4: Directivas de error: DETENER, informar, no continuar

En las primeras versiones, el orquestador recibía un error de un especialista, fabricaba un resultado plausible para él y continuaba con el siguiente agente. El usuario obtuvo una campaña de aspecto completo basada en una base alucinada. La corrección es explícita: DETENTE de inmediato. Informa el error exacto. Nunca continuar.

Elemento 5: Reglas de transferencia de contexto

Los agentes remotos no tienen historial de conversaciones. Cuando el organizador llama al redactor a través de A2A, el redactor solo ve el mensaje en esa única solicitud, no sabe lo que dijo el estratega de marca. El orquestador debe agrupar explícitamente los resultados anteriores en cada llamada posterior:

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.")

La instrucción lo indica de forma explícita: "Los agentes remotos NO tienen memoria compartida. Debes pasar las salidas anteriores de forma explícita". Sin esta información, cada agente trabaja a ciegas.

Elemento 6: Clasificación de la solicitud: simple vs. compleja

No todas las solicitudes necesitan los cinco agentes. La instrucción le indica al orquestador que clasifique la solicitud antes de planificarla:

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

Sin esta clasificación, el orquestador ejecutaría los cinco agentes para cada solicitud, incluida la de "solo dame 3 ideas para publicaciones", lo que agregaría latencia y costos innecesarios.

Elemento 7: Reglas de comunicación: Mostrar resultados completos, sin filtrar

La instrucción es explícita en que el orquestador no debe resumir ni editar lo que devuelven los especialistas:

- 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

Sin esto, el orquestador reescribe los resultados de los especialistas con sus propias palabras, lo que hace que se pierdan detalles, se introduzcan errores y se anule el propósito de tener especialistas.

Elemento 8: Finalización del flujo de trabajo: Nunca te detengas antes de tiempo

Un modo de falla sutil, pero crítico: El orquestador anuncia un plan de 5 pasos, completa 3 y, luego, presenta los resultados como si hubiera terminado. La instrucción evita esto con una lista de tareas explícita que debe aprobarse antes de que el orquestador pueda finalizar:

✓ 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.

Esto evita que el organizador considere que una ejecución parcial está completa.

El bucle de control de calidad

El flujo de trabajo de revisión es la parte más compleja de prompt.py. Abre la sección ## REVISION WORKFLOW y sigue las instrucciones.

Cómo funciona

Después de que el crítico responde, el director creativo no continúa ciegamente con el administrador de proyectos. Lee el resultado del Crítico y se bifurca:

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)

Esto se basa en LLM, no en código.

En el codelab, se mencionó anteriormente que el orquestador "analiza" la respuesta de Critic. No hay código de Python que realice este análisis, ni regex ni coincidencia de cadenas. El director creativo es un LLM que lee sus propias instrucciones. Esa instrucción dice lo siguiente:

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

El LLM lee esas cadenas exactas en el resultado del Crítico y sigue la rama. Por eso, el formato de Crítico es innegociable: si el Crítico escribe "necesita algo de trabajo" en lugar de NEEDS_REVISION, el LLM no encuentra ninguna coincidencia en su instrucción y omite silenciosamente el paso de revisión.

Cómo se reenvía el contexto en una llamada de revisión

La llamada de revisión sigue la misma regla de paso de contexto del elemento 5: el orquestador debe incluir todo de forma explícita porque el redactor no tiene memoria de su primera versión:

"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."

Sin la sección "TU PRIMERA VERSIÓN", el redactor escribiría desde cero en lugar de mejorar lo que ya produjo.

El límite de 1 revisión y por qué es importante

Después de una ronda de revisión, el orquestador pasa al administrador de proyectos independientemente de la puntuación. La instrucción hace un seguimiento mental de lo siguiente:

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

Sin este límite, el ciclo podría ejecutarse de forma indefinida: El crítico marca un problema → El redactor lo revisa → El crítico lo vuelve a marcar → El redactor lo vuelve a revisar. Cada ronda cuesta fichas y tiempo. Una revisión es suficiente para mejorar la calidad sin riesgo de un ciclo descontrolado.

Qué se pasa al administrador de proyectos

El administrador de proyectos siempre recibe las versiones finales aprobadas, no las originales. Si hubo revisiones, el organizador pasa la copia y los elementos visuales revisados. Si todo se aprobó en la primera revisión, se aprueban directamente. El PM nunca ve los borradores rechazados.

TODO 2: Registra a cada especialista como RemoteA2aAgent + AgentTool

Busca el comentario # TODO 2: For each specialist URL... y reemplázalo por lo siguiente:

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

TAREA PENDIENTE 3: Envuelve en una app con compactación de contexto

Por qué es necesaria la compactación

Cada mensaje de una conversación (la instrucción del usuario, cada llamada a una herramienta y cada respuesta de la herramienta) se agrega a la ventana de contexto que el LLM lee en el siguiente turno. En un flujo de trabajo de 5 agentes, esto se acumula rápidamente:

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
...

En el caso del agente 4 (crítico), la ventana de contexto contiene el resultado completo de los tres agentes anteriores, a menudo, entre 8,000 y 12,000 tokens solo en las respuestas de las herramientas. Incluso con la gran ventana de contexto de Gemini 2.5 Pro, la calidad del razonamiento del orquestador se degrada a medida que debe prestar atención a un historial cada vez más grande. Sin la compactación, los flujos de trabajo largos alcanzan límites prácticos en torno al agente 4.

Qué hace la compactación

En lugar de conservar cada evento completo, el ADK llama periódicamente a un LLM para resumir los eventos más antiguos en una representación compacta. Solo se conservan en el contexto el resumen de los eventos pasados y el resultado completo del agente más reciente.

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

El resumen conserva los hechos esenciales (estadísticas clave, leyendas aprobadas, conceptos visuales) y descarta el formato detallado, el contexto repetido que se pasa a cada agente y el razonamiento intermedio. El Crítico sigue teniendo todo lo que necesita para evaluar, solo que lee un resumen en lugar de tres informes completos.

El código

Busca el comentario # TODO 3: Wrap the agent in an App... y reemplaza el marcador de posición App(...) por lo siguiente:

    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 compactación se activa después de cada 3 finalizaciones del agente. En el caso de una canalización de 5 agentes, esto significa que se activa una vez (después de los agentes 1 a 3), y luego el Crítico y el PM ven un resumen de los agentes 1 a 3 más el resultado completo del agente anterior.

overlap_size=1: El resultado completo más reciente del agente siempre se mantiene de forma literal, nunca se resume. Esto es importante porque el Crítico necesita el resultado completo del Diseñador, incluidos los valores de gcs_uri, para cargar y revisar las imágenes reales. Un resumen perdería esos URIs.

Cómo se desarrolla en una campaña completa:

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]

Información sobre RemoteA2aAgent y 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

El LLM decide cuándo llamar a cada herramienta según las instrucciones del sistema y la solicitud del usuario. El organizador nunca llama a los agentes directamente en el código; todo se basa en el razonamiento del LLM.

Prueba el Creative Director de forma local

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

Abre la vista previa en la Web en el puerto 8000. Usa el menú desplegable del agente para seleccionar creative_director y, luego, prueba lo siguiente:

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

Verás que el director creativo derivará esto solo al estratega de marca, y recibirás una respuesta de este.

Para la campaña completa, prueba lo siguiente:

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.

Verás al director creativo coordinar a los 5 especialistas en secuencia, y el resultado de cada agente se incorporará al siguiente.

Demostración: Ejecución de una campaña de extremo a extremo

Detén el Creative Director (Ctrl+C) antes de continuar. El inspector de A2A también usa el puerto 8000.

Detén los 5 servidores especializados (Ctrl+C en cada terminal) cuando termines con las pruebas locales.

12. Implementa y prueba los agentes especialistas

Ahora sí, podemos implementar nuestros agentes en Google Cloud. Cloud Run es un excelente servicio para implementar agentes. Es sin servidores, escalable y fácil de usar. Cada agente especializado se implementa como un servicio independiente de Cloud Run.

Configuración de implementación

El Dockerfile de cada especialista sigue este patrón:

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"]

Implementa los 5 especialistas de forma secuencial

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

uv run deploy/deploy_all_specialists.py

Esta secuencia de comandos implementa los 5 agentes de a uno por vez (aproximadamente entre 10 y 12 minutos en total). La implementación secuencial evita la cuota de sondeo de Cloud Build (60 solicitudes por minuto). Cuando se completa, escribe la URL de Cloud Run de cada agente en .env.

Después de que se implementa Designer, la secuencia de comandos otorga automáticamente a su cuenta de servicio de Cloud Run roles/storage.objectCreator en tu bucket de GCS para que pueda subir las imágenes generadas.

Si configuraste las credenciales de Notion en .env, la secuencia de comandos también las almacena de forma segura en Secret Manager (como notion-token, notion-project-db-id, notion-tasks-db-id) y las inyecta en el servicio Project Manager a través de --set-secrets en lugar de variables de entorno simples. Esto significa que el token nunca aparece en la pestaña de entorno de Cloud Run ni en el historial de comandos de gcloud.

Verifica las implementaciones

Cuando se completa la implementación, la secuencia de comandos escribe automáticamente las URLs de Cloud Run en .env, lo que reemplaza las URLs de localhost del paso anterior:

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"

El director creativo usará automáticamente estas URLs de Cloud Run cuando se implementen en Agent Runtime en el siguiente paso.

Verifica las tarjetas de agente

Cada agente implementado expone una tarjeta de agente en /.well-known/agent.json. Recupéralos para confirmar que todo esté publicado:

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

Resultado esperado para cada agente:

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

Prueba con el Inspector de A2A (Cloud Run)

El inspector de A2A ya se instaló en el paso 10. Inícialo:

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

Abre Vista previa en la Web → Cambiar puerto5001. Ingresa tu URL de Cloud Run en el campo de conexión:

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

Haz clic en Connect. No se necesita un token de autorización, ya que los servicios se implementan con --allow-unauthenticated.

El inspector se conecta, valida la tarjeta del agente y te permite chatear de forma interactiva a través de A2A.

Inspecciona los agentes implementados en Cloud Run

Después de implementar en Cloud Run, apunta el inspector a la URL HTTPS pública para verificar que la implementación en la nube funcione:

Inspector de A2A conectado al agente de Cloud Run

El flujo de trabajo es idéntico: pega la URL de Cloud Run, conéctate y envía un mensaje de prueba. Si se carga la tarjeta del agente y el chat responde, significa que el especialista se implementó correctamente y está disponible.

13. Implementa Creative Director en Agent Runtime

El organizador se implementa en Agent Runtime, que proporciona un estado de sesión administrado, escalamiento automático y seguimiento integrado.

¿Por qué usar Agent Runtime para el organizador?

Los cinco especialistas se implementan en Cloud Run, son ligeros, no tienen estado y cada uno controla una tarea. El director creativo tiene diferentes requisitos:

Requisito

Por qué es importante

Estado de la sesión

Un flujo de trabajo de varios pasos tarda más de 45 segundos. El tiempo de ejecución del agente mantiene el estado de la conversación entre las llamadas a herramientas del orquestador para que no se pierda nada a mitad de la canalización.

Carga variable

A veces, una campaña por hora y, a veces, muchas en paralelo. El tiempo de ejecución del agente se reduce a cero cuando está inactivo y se expande automáticamente, por lo que no pagas por la capacidad inactiva.

Observabilidad

Cloud Logging, Cloud Monitoring y Cloud Trace vienen integrados. Puedes ver cada llamada de A2A, cada token utilizado y cada aumento repentino de latencia sin agregar ninguna instrumentación.

Flujos de trabajo de larga duración

Cloud Run tiene un tiempo de espera de solicitud de 3,600 s. Agent Runtime está diseñado para flujos de trabajo que pueden tardar minutos, con reintentos administrados y persistencia de estado.

Cloud Run es la plataforma adecuada para los especialistas en aplicaciones sin estado. Agent Runtime es la plataforma adecuada para el organizador con estado.

Implementa el organizador

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

uv run deploy/deploy_orchestrator.py --action deploy

Este proceso tarda entre 5 y 10 minutos. Cuando se complete, se guardarán AGENT_ENGINE_ID y AGENT_ENGINE_RESOURCE_NAME en .env.

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

Cómo funciona la implementación

client.agent_engines.create() empaqueta tu objeto App, lo sube con sus dependencias y lo implementa en la infraestructura administrada. A continuación, se explica la función de cada parámetro:

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]

Qué sucede tras bambalinas:

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

Después de la implementación, el orquestador se conecta con los cinco especialistas de Cloud Run a través de las URLs en sus variables de entorno.

  • Estos se pasan a través de .env antes de que se ejecute la secuencia de comandos de implementación.

14. Publica una campaña de extremo a extremo

Se implementó todo el sistema. Ejecuta una campaña completa desde el Playground de Agent Runtime.

Abre el sitio de pruebas de Agent Runtime

  1. Ve a https://console.cloud.google.com/agent-platform/runtimes. También puedes navegar al entorno de ejecución del agente desde Agent Platform > Agents > Deployments.
  2. Selecciona el entorno de ejecución del agente implementado (creative-director).
  3. Haz clic en Playground en la barra lateral izquierda.
  4. Haz clic en Nueva sesión para abrir una conversación nueva.

Ejecuta una campaña completa

Pega este resumen en el chat y envíalo:

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

El director creativo ejecutará los 5 agentes en secuencia:

  1. Estratega de marca → Investigación de mercado, análisis de la competencia, estadísticas del público
  2. Redacción creativa → 3 publicaciones de Instagram con leyendas, hashtags y CTA
  3. Diseñador → Conceptos visuales y fotos reales generados a través de Gemini (URIs de GCS) para cada publicación
  4. Crítico → Revisión de calidad con puntuaciones APROBADO / NECESITA_REVISIÓN
  5. (Revisión si es necesario) → Se vuelve a llamar al redactor o diseñador con comentarios
  6. Gerente de proyectos → Cronograma de 2 semanas, desglose de tareas y asignación de presupuesto

Demostración: Ejecución de la campaña con la integración de Notion

Prueba el enrutamiento de un solo agente

Envía esta solicitud más corta en una sesión nueva:

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

Observa que el director creativo deriva esto solo al Brand Strategist. No se llama a ningún otro agente. Esta es la lógica de clasificación de solicitudes de la instrucción del sistema que funciona correctamente.

Cómo inspeccionar registros de ejecución

Mientras permaneces en la consola, haz lo siguiente:

  1. Haz clic en Registros en la barra lateral izquierda (junto a Playground).
  2. En Trace View, selecciona el registro de la sesión que acabas de ejecutar.
  3. Expande el árbol de seguimiento para ver cada llamada del agente, sus entradas y salidas, la latencia y el uso de tokens.

Cada llamada de A2A a un especialista aparece como un intervalo independiente. Puedes ver exactamente qué contexto le pasó el director creativo a cada agente y qué recibió a cambio.

Opcional: Ejecuta desde la terminal

También puedes ejecutar la campaña de forma programática con la secuencia de comandos run_campaign.py que ya se incluye en el programa de inicio.

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

15. Limpieza

Limpia los recursos de Google Cloud para evitar cargos continuos.

Ejecuta la secuencia de comandos de desmontaje. Esta lee tu .env y borra todo lo que se creó durante este codelab:

bash deploy/teardown_gcp.sh

La secuencia de comandos te mostrará exactamente lo que borrará y te pedirá que confirmes antes de hacer cualquier acción:

Recurso

Qué se borra

Servicios de Cloud Run

Estratega de marca, redactor, diseñador, crítico, gerente de proyectos

Agent Runtime

Motor de razonamiento del director creativo y todas las sesiones

Artifact Registry

cloud-run-source-deploy repositorio y todas las imágenes de Docker

Buckets de 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 (se omite si no se crea)

Verifica que se haya quitado todo

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

El resultado esperado son listas vacías o solo tus propios recursos preexistentes.

16. Resumen

¡Felicitaciones! Creaste e implementaste un sistema de IA de agentes múltiples de nivel de producción en Google Cloud.

Qué compilaste

Agente

Función

Implementación

Estratega de marca

Investigación de mercado a través de la Búsqueda de Google

Cloud Run

Redactor creativo

Creación de leyendas de Instagram

Cloud Run

Diseñador

Generación de imágenes a través de Gemini y carga en GCS

Cloud Run

Crítico

Revisión de calidad con puntuación

Cloud Run

Administrador de proyectos

Cronograma y MCP de Notion

Cloud Run

Director de creatividades

Organización completa a través de A2A

Agent Runtime

Patrones clave que aprendiste

  1. ADK Agent: Define un agente de LLM con una instrucción y herramientas opcionales
  2. adk web: Ejecuta y prueba cualquier agente del ADK de forma local con una IU de chat integrada.
  3. SkillToolset: Empaqueta el conocimiento reutilizable en archivos modulares que se cargan a pedido.
  4. FunctionTool: Encapsula cualquier función de Python (o modelo externo) como una herramienta de agente invocable.
  5. to_a2a(): Expone cualquier agente de ADK como un servicio HTTPS compatible con A2A.
  6. RemoteA2aAgent + AgentTool: Organiza agentes remotos como herramientas invocables
  7. McpToolset: Conéctate a servicios externos a través de servidores stdio de MCP
  8. EventsCompactionConfig: Controla los límites de tokens en flujos de trabajo multiagente largos.
  9. Salida estructurada de críticos: Control de calidad legible por máquina con revisión automática
  10. Cloud Run: Implementa agentes en contenedores a gran escala
  11. Agent Runtime: Orquestadores de host con sesiones y seguimiento administrados

Próximos pasos

  • Se agregó la edición de imágenes de varios turnos al Diseñador con la capacidad de edición de gemini-3.1-flash-image-preview.
  • Se agregó la autenticación de IAM a los servicios de Cloud Run (se quitó --allow-unauthenticated).
  • Reemplaza a un especialista por un agente de LangGraph o CrewAI: A2A es independiente del framework.
  • Agrega comentarios de los usuarios como herramienta para que los participantes puedan calificar los resultados y realizar iteraciones sobre ellos.
  • Explora el seguimiento del tiempo de ejecución del agente en la consola de Cloud

Recursos