Crie um estúdio criativo multiagente com a pilha de agentes do Google: ADK, A2A, MCP no Cloud Run e no tempo de execução do agente

1. Visão geral

Neste codelab, você vai criar o AI Creative Studio, um sistema multiagente distribuído que transforma um único comando em uma campanha completa do Instagram.

Digite uma frase. Receba de volta pesquisa de público-alvo, legendas, conceitos visuais, cópias revisadas e um cronograma completo do projeto, tudo gerado por uma equipe de agentes de IA colaborativos.

Os agentes que você vai criar

Agente

Papel

Estrategista de marca

Pesquisa na Web insights sobre público-alvo, análise da concorrência e tendências para 2025

Redatora

Escreve legendas do Instagram com hashtags e CTAs, com tecnologia de uma habilidade do ADK que carrega diretrizes da plataforma e fórmulas de legenda sob demanda.

Designer

Cria conceitos visuais e gera imagens reais com o Gemini, armazenadas no GCS

Crítico

Cópias e recursos visuais de avaliações: retornam APPROVED ou NEEDS_REVISION com feedback específico

Gerente de projeto

Cria uma linha do tempo e um detalhamento de tarefas do projeto, que podem ser sincronizados com o Notion usando o MCP.

Diretor de criação

Orquestra todos os cinco especialistas em sequência: você dá um comando, e ele coordena o restante

Os cinco agentes são implantados como microsserviços independentes do Cloud Run. Eles se comunicam pelo protocolo A2A, um padrão aberto independente de linguagem para que qualquer agente possa chamar outro, seja qual for o framework. O Creative Director é executado no Agent Runtime e se conecta a cada especialista remotamente.

Arquitetura

Visão geral do sistema

O que você vai aprender

  • Crie agentes de LLM com o ADK do Google: Agent, instruções do sistema e ferramentas integradas.
  • Empacote o conhecimento reutilizável do agente em arquivos modulares com as habilidades do ADK (SkillToolset).
  • Gere imagens reais conectando um agente de texto a um modelo de imagem usando um FunctionTool.
  • Integre APIs externas sem código de junção personalizado usando o Protocolo de Contexto de Modelo (MCP).
  • Transforme qualquer agente em um serviço que pode ser chamado pela rede usando o protocolo de agente para agente (A2A) via HTTPS.
  • Orquestre agentes distribuídos com RemoteA2aAgent e AgentTool.
  • Empacote e implante agentes independentes como microsserviços do Cloud Run.
  • Hospede um orquestrador com estado no Agent Runtime.
  • Mantenha fluxos de trabalho multiagentes longos dentro dos limites de contexto usando a compactação de contexto.
  • Crie um ciclo de controle de qualidade: o Critic analisa a saída → revisão automática quando necessário.

O que é necessário

  • Um projeto do Google Cloud com o faturamento ativado
  • Papel do IAM de Proprietário ou Editor
  • Conhecimento básico de Python

2. Configuração de seu ambiente

Neste codelab, vamos usar o Cloud Shell.

O que é o Cloud Shell?

O Cloud Shell é um ambiente Linux sem custo financeiro baseado em navegador com tudo pré-instalado: gcloud, git, Python, Docker e muito mais. Não é necessário instalar nada localmente.

Para abrir o Cloud Shell, clique no ícone do terminal na barra de ferramentas superior direita do console do GCP:

Abra o Cloud Shell na barra de ferramentas do console do GCP.

Ao abrir o Cloud Shell pela primeira vez, você vai precisar verificar sua conta. Clique em Verificar:

Caixa de diálogo "Verificar sua conta"

Em seguida, clique em Autorizar para permitir que o Cloud Shell faça chamadas de API do Google Cloud:

Caixa de diálogo "Autorizar o Cloud Shell"

O Cloud Shell está pronto. Uma mensagem de boas-vindas vai aparecer no terminal: Terminal do Cloud Shell pronto

Autenticar e configurar seu projeto

O Cloud Shell já está autenticado com sua Conta do Google. Confirme sua conta ativa e encontre o ID do projeto:

gcloud config list

Você também pode conferir o ID do projeto no painel do console do GCP, no painel lateral esquerdo. Copie o caminho. Você vai precisar dele no próximo comando:

Encontre o ID do projeto no console do GCP e defina-o no Cloud Shell.

Agora, defina o projeto:

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

Saída esperada:

Project: my-project-123

Ativar APIs obrigatórias

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

Isso leva cerca de 2 minutos. Você vai ver Operation finished successfully quando terminar.

Configurar as credenciais padrão do aplicativo (ADC)

Os agentes vão chamar a plataforma de agentes do Gemini Enterprise usando a biblioteca Google Auth, que exige o Application Default Credentials, separado da autenticação da CLI gcloud.

Execute este comando uma vez:

gcloud auth application-default login

Uma guia do navegador será aberta pedindo confirmação. Clique em Permitir. Você vai ver:

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

Clonar o repositório inicial

Este codelab usa um repositório inicial, um projeto esqueleto com toda a infraestrutura no lugar (Dockerfiles, pyproject.toml, scripts de implantação), mas com a lógica do agente para você escrever.

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

Cada agent.py contém marcadores de posição # TODO em que você vai escrever a lógica do agente. Os scripts Dockerfile, pyproject.toml e de implantação já estão concluídos.

Configure as variáveis de ambiente

Copie o exemplo fornecido e injete o ID do projeto em uma etapa:

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

Em seguida, crie o bucket do GCS em que o Designer vai armazenar as imagens geradas e atualize .env com o nome dele:

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

Em seguida, configure o suporte a URLs de imagens assinados. O diretor de criação gera links HTTPS clicáveis para cada imagem no resumo final da campanha. Isso exige uma conta de serviço para assinar os URLs. Execute estes comandos para configurar:

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

Abra .env no editor para revisar todas as configurações:

cloudshell edit .env

Isso abre .env como uma guia no editor do Cloud Shell. Clique no botão Abrir editor na barra de ferramentas se o painel do editor não estiver visível:

Clique em "Abrir editor" na barra de ferramentas do Cloud Shell.

Editor do Cloud Shell com árvore de arquivos do projeto

Confirme se o projeto foi definido corretamente:

grep GOOGLE_CLOUD_PROJECT .env

Instalar dependências

Usamos o uv, um gerenciador de pacotes Python rápido e moderno que lida com ambientes virtuais e instalações em uma única ferramenta. Ele é de 10 a 100 vezes mais rápido que o pip e é a maneira recomendada de gerenciar projetos Python.

O Cloud Shell já tem o uv instalado. Todos os agentes compartilham as mesmas dependências principais. Portanto, instale uma vez e isso vai funcionar para todos os agentes neste codelab:

uv sync

O comando uv syncpyproject.toml e cria um diretório .venv/ com todas as dependências. Cada especialista também tem o próprio pyproject.toml usado exclusivamente por builds do Docker. A instalação compartilhada acima abrange tudo o que você precisa para testes locais.

3. Entender o ADK do Google

Antes de escrever o código, vamos entender o Kit de Desenvolvimento de Agente (ADK), a estrutura que você vai usar para criar todos os agentes neste codelab.

O que é o ADK?

O Agent Development Kit (ADK, na sigla em inglês) é um framework flexível e modular para desenvolver e implantar agentes de IA. Embora otimizado para o Gemini e o ecossistema do Google, o ADK é independente de modelo e implantação e foi criado para ser compatível com outros frameworks. O ADK foi projetado para tornar o desenvolvimento de agentes mais parecido com o desenvolvimento de software, facilitando a criação, a implantação e a orquestração de arquiteturas de agentes que variam de tarefas simples a fluxos de trabalho complexos.

O ADK lida com as partes complexas (chamada de função, conversa com várias interações, gerenciamento de contexto, streaming) para que você possa se concentrar na lógica do agente.

Elementos básicos de um agente do ADK

Cada agente é composto de quatro elementos básicos:

Bloquear

Papel

Modelo

O LLM que analisa metas, determina um plano e gera respostas

Ferramentas

Funções que buscam dados ou realizam ações chamando APIs ou serviços

Orquestração

Mantém a memória e o estado entre as conversas, encaminha chamadas de ferramentas e transmite os resultados de volta ao modelo.

Ambiente de execução

Executa o sistema quando invocado, localmente via adk web ou como um serviço implantado.

Definição de agente

Cada um dos cinco agentes neste codelab é definido da mesma maneira:

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

Finalidade

name

ID exclusivo: usado por orquestradores para encaminhar chamadas.

model

O modelo do Gemini que oferece suporte a esse agente

instruction

Comando do sistema: define a função, as restrições e o formato de saída do agente.

description

Resumo de uma linha: o orquestrador lê isso para decidir qual especialista chamar.

tools

Funções que o LLM pode invocar (integradas, como google_search, ou funções personalizadas do Python)

Como o ADK executa um agente

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

O LLM decide de forma autônoma se vai chamar uma ferramenta, qual ferramenta e com quais argumentos. Você escreve a instrução, e o ADK cuida do resto.

4. Criar e testar o agente de estratégia de marca

Vamos começar com o primeiro agente: o estrategista de marca. Este é um agente de pesquisa que busca insights sobre o público-alvo, análise da concorrência e temas em alta usando a Pesquisa Google.

Abra o arquivo do agente esqueleto no editor do Cloud Shell:

cloudshell edit agents/brand_strategist/agent.py

Você vai encontrar duas seções # TODO para preencher.

TODO 1: escreva a instrução do sistema

Primeiro, você vai escrever a instrução do sistema para o agente. A instrução do sistema é uma string que define a função, as restrições e o formato de saída do 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 - Create the root_agent

Em seguida, substitua o root_agent incompleto por:

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

Testar localmente com a interface da Web do ADK

Agora vamos testar o agente usando a interface da Web do ADK, uma interface de chat integrada para testar agentes antes da implantação na nuvem.

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

Você vai ver:

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

O servidor agora está sendo executado no Cloud Shell:

Para abrir no navegador, use a Visualização da Web:

  1. Procure a barra de ferramentas do Cloud Shell na parte de cima da página.
  2. Clique no ícone Visualização da Web (parece uma caixa com uma seta para cima, no canto superior direito da barra de ferramentas do Cloud Shell).
  3. Clique em "Alterar porta", insira 8000 e clique em "Alterar e visualizar".

Uma nova guia do navegador será aberta com a interface da Web do ADK. Clique no menu suspenso Selecionar um agente no canto superior esquerdo. Todos os seus agentes vão aparecer:

Escolha brand_strategist para começar a testar:

Teste estes comandos

Na caixa de chat da interface da Web do ADK, tente:

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

O agente vai chamar a Pesquisa Google e retornar uma pesquisa estruturada com as seções "Insights do público-alvo", "Análise da concorrência" e "Assuntos em alta".

5. Criar o Copywriter: habilidades do ADK

Função:transformar pesquisas de marca em legendas do Instagram. O redator cria três variações de legenda com diferentes tons (inspirador, educativo, comunidade), cada uma com hashtags e uma CTA.

Conceito: habilidades do ADK

Uma abordagem simples incorporaria todo o conhecimento da plataforma (limites de caracteres, níveis de hashtags, fórmulas de legendas, exemplos de voz da marca) diretamente no comando do sistema. Isso funciona, mas aumenta cada solicitação com conteúdo que o agente só precisa ocasionalmente.

Com as habilidades do ADK (SkillToolset, introduzidas no ADK 1.25.0), é possível empacotar esse conhecimento em arquivos modulares com três níveis de carregamento:

  • L1: frontmatter (name + description em SKILL.md): sempre disponível, usado para descoberta de habilidades
  • L2 - instruções (corpo de SKILL.md): carregadas quando o agente aciona a habilidade
  • L3: recursos (arquivos references/ e assets/): carregados apenas quando o agente os lê explicitamente.

A instrução do sistema é reduzida a uma breve declaração de função mais "carregue a habilidade antes de escrever". Os detalhes da plataforma só entram na janela de contexto quando o agente realmente precisa deles.

A habilidade do redator publicitário está em 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

Abra o arquivo diretamente no editor do Cloud Shell:

cloudshell edit agents/copywriter/agent.py

TODO 1 - Importe load_skill_from_dir e skill_toolset

Encontre o comentário # TODO 1: Import load_skill_from_dir and skill_toolset e adicione as duas importações:

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

TODO 2 - Carregar a habilidade e criar um SkillToolset

Encontre os dois comentários abaixo das importações:

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

Substitua por:

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

load_skill_from_dirSKILL.md e todos os arquivos em references/ e assets/. O SkillToolset envolve isso no formato aceito pelos agentes do ADK: um conjunto de ferramentas, não uma habilidade bruta.

TODO 3: registrar o conjunto de ferramentas com o agente

Encontre tools=[], # TODO 3: Add the SkillToolset here e substitua por:

tools=[_copywriting_skills],

Abra o arquivo de habilidade para ver como ele é estruturado:

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

Mantenha a interface da web do ADK em execução. Use o menu suspenso do agente para mudar para copywriter sem reiniciar o servidor.

Se ele não estiver em execução, inicie-o novamente:

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

Faça um teste:mude o menu suspenso para copywriter e envie:

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. Criar o Designer: geração de imagens multimodais

Mantenha a interface da web do ADK em execução. Use o menu suspenso de agentes para trocar de agente sem reiniciar o servidor.

Função:crie conceitos visuais para cada legenda e gere as imagens usando a geração de imagens nativa do Gemini. O Designer gera exatamente um conceito visual por legenda, com um comando detalhado, estilo, paleta de cores, clima e formato do Instagram. Em seguida, ele chama imediatamente a ferramenta generate_image para produzir a imagem real e fazer upload dela no GCS.

Conceito: como conectar um agente de texto a um modelo de imagem usando uma ferramenta

O Designer é executado no gemini-3-flash-preview (o modelo de texto definido via GEMINI_MODEL em .env), mas a geração de imagens exige um modelo dedicado (gemini-3.1-flash-image-preview). Esse modelo de imagem não oferece suporte a chamadas de função, então não pode ser usado diretamente como um agente do ADK. Em vez disso, ele é envolvido em uma função Python simples e registrado como um FunctionTool.

Esse é o padrão para qualquer modelo ou API que o LLM não pode chamar diretamente: envolva em uma ferramenta, deixe o agente orquestrar quando chamar e receba um resultado estruturado.

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)

Abra o arquivo diretamente no editor do Cloud Shell:

cloudshell edit agents/designer/image_gen_tool.py

A assinatura da função, a configuração do ambiente e a injeção de proporção são fornecidas. Siga as três tarefas TODO em ordem:

TODO 1 - Chamar o modelo de imagem do Gemini

Encontre o comentário # TODO 1 e substitua por:

        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: extrair bytes de imagem da resposta

Encontre o comentário # TODO 2 e substitua por:

        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: fazer upload para o GCS e retornar o URI

Encontre o comentário # TODO 3 e substitua por:

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

Faça um teste:mude o menu suspenso para designer e envie:

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. Criar o Critic - Structured Output

Função:garantir a qualidade do texto e dos recursos visuais antes de entregá-los ao gerente de projetos. O Critic avalia os dois entregáveis e retorna APPROVED ou NEEDS_REVISION com sugestões específicas. Quando os valores gcs_uri estão presentes na entrada, a ferramenta review_image é chamada para inspecionar visualmente cada imagem gerada antes da pontuação.

Conceito: quando usar um modelo Pydantic para a saída do Gemini

A regra é sobre quem consome a saída:

  • O código Python o consome → use response_schema + Pydantic. O código não consegue lidar com ambiguidade. Por isso, você precisa de uma estrutura garantida para extrair campos de maneira confiável.
  • Um LLM consome → formato de texto + instrução do sistema são suficientes. Os LLMs entendem regras de formatação e toleram variações.

Em review_image, o código Python precisa de score, approval_status, what_works, issues e suggestions como valores tipados. A transmissão de response_schema=_GeminiReview restringe o Gemini no nível da API para retornar um JSON válido. model_validate_json() o analisa em um objeto tipado que seu código pode usar de maneira confiável.

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

Abra o arquivo diretamente no editor do Cloud Shell:

cloudshell edit agents/critic/image_review_tool.py

Os modelos Pydantic e o comando são fornecidos. Siga as três tarefas TODO em ordem:

TODO 1 - Create an image part from the GCS URI

Encontre o comentário # TODO 1 e substitua por:

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

TODO 2: chamar o Gemini com um esquema de resposta estruturada

Encontre o comentário # TODO 2 e substitua por:

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

TODO 3: analisar a resposta e retornar o resultado

Encontre o comentário # TODO 3 e substitua por:

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

Faça um teste:mude o menu suspenso para critic e envie:

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.

Verifique se a resposta contém **POSTS REVIEW:**, Status: APPROVED (ou NEEDS_REVISION) e **OVERALL ASSESSMENT:**. Se essas seções estiverem presentes, o Critic estará pronto para ser conectado ao orquestrador.

Quando terminar de testar todos os três agentes, pressione Ctrl+C para interromper o servidor.

8. Criar o agente do gerenciador de projetos com o MCP

O gerente de projetos apresenta um novo conceito: MCP (Protocolo de Contexto de Modelo).

Abra o arquivo:

cloudshell edit agents/project_manager/agent.py

Esse arquivo é mais complexo. Ele tem uma função create_project_manager_agent() com duas ramificações: uma sem o Notion (linhas do tempo somente de texto) e outra com o conjunto de ferramentas do MCP do Notion. Você vai preencher os dois.

O problema que o MCP resolve

Seu agente precisa chamar um serviço externo, por exemplo, criar uma página no Notion. Você pode escrever código Python que chama diretamente a API REST do Notion. Mas:

  • Cada desenvolvedor escreve um wrapper diferente
  • Você precisa manter o código de integração personalizado
  • O LLM não sabe que a API existe, a menos que você descreva cada endpoint manualmente.

O MCP resolve isso definindo uma maneira padrão para que serviços externos exponham seus recursos como ferramentas que um LLM pode descobrir e chamar automaticamente.

O que é o MCP?

O MCP (Protocolo de Contexto de Modelo) é um padrão aberto (publicado pela Anthropic) para conectar agentes de IA a ferramentas e fontes de dados externas. Ele funciona como um adaptador universal.

Um servidor MCP é um pequeno programa que:

  1. Encapsula uma API externa (Notion, GitHub, bancos de dados, sistemas de arquivos etc.).
  2. Expõe essa API como uma lista de ferramentas tipadas e documentadas.
  3. Comunicação com o agente por um protocolo simples (stdio ou HTTP).

O agente se conecta ao servidor MCP, descobre automaticamente as ferramentas disponíveis e pode chamá-las como qualquer outra ferramenta. O LLM vê API-post-page(...) como uma função chamável.

A2A x MCP: qual é a diferença?

Esse é um ponto comum de confusão. Confira a principal diferença:

A2A

MCP

O que conecta

Agente ↔ Agente

Agente ↔ ferramenta/serviço externo

O outro lado é

Outro agente de LLM

Um wrapper de API (sem LLM)

Exemplo

O diretor de criação liga para o estrategista de marca

O gerente de projetos chama a API do Notion

Protocolo

JSON-RPC por HTTPS

stdio ou fluxo HTTP

Definido por

Google

Anthropic

Pense no seguinte:

  • A2A = como os agentes conversam com outros agentes
  • MCP = como os agentes conversam com ferramentas e serviços

Neste projeto, os dois são usados 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

Como o MCP funciona neste projeto

Quando o agente é executado, o ADK inicia o notion-mcp-server como um processo filho. Esse processo expõe essas ferramentas diretamente ao LLM:

Ferramenta

O que faz?

API-retrieve-a-database

Busca o esquema (nomes de propriedades, tipos, valores válidos)

API-post-database-query

Consulta páginas existentes

API-post-page

Cria uma página

API-patch-page

Atualiza uma página existente.

O LLM chama essas funções como qualquer outra. Ele não sabe que elas passam pelo MCP para a API REST do Notion em segundo plano.

Por que stdio? Por que não apenas HTTP?

O servidor do MCP é executado como um processo secundário do agente, comunicando-se por stdin/stdout. Isso significa que:

  • Não é necessário ter uma porta de rede extra
  • O ciclo de vida é gerenciado pelo agente (iniciado sob demanda, interrompido ao sair).
  • Tudo é enviado em uma imagem Docker. Não há um serviço separado para implantação.

(Opcional) Ativar a integração com o Notion

Você pode pular toda esta seção. O agente Gerente de projetos sempre produz uma linha do tempo completa da campanha baseada em texto, com ou sem o Notion. Se você pular essa configuração, o agente vai voltar ao modo na memória e mostrar a linha do tempo como texto simples no chat. Nada vai quebrar. Você só não vai ver as tarefas aparecerem em um banco de dados do Notion. Vá direto para a tarefa 1 se quiser pular.

Se você tem uma conta do Notion e quer ver a integração do MCP em ação, conclua a configuração abaixo agora. Os TODOs a seguir fazem referência aos IDs do banco de dados do Notion, que é onde você os encontra.

Etapa 1: criar o banco de dados do Notion com base em um modelo

Usamos o modelo oficial Projetos e tarefas do Notion como nosso banco de dados. Escolhemos esse modelo propositadamente para demonstrar uma configuração complexa do mundo real. Ele tem vários tipos de propriedades (status, intervalos de datas, relações, seleções) com nomes não óbvios. Esse é um ótimo teste da descoberta dinâmica de esquema do MCP: o agente precisa descobrir os nomes exatos das propriedades no tempo de execução, em vez de tê-los codificados.

Clique no link abaixo para adicionar o modelo ao seu espaço de trabalho do Notion:

→ Adicionar o modelo "Projetos e tarefas" ao Notion

Modelo de projetos e tarefas do Notion no Marketplace

Depois de adicionados, você terá dois bancos de dados vinculados: Projetos e Tarefas. O modelo vem com entradas de exemplo. Exclua todas elas antes de continuar para que o agente comece com um espaço de trabalho limpo (selecione tudo → Excluir).

Etapa 2: criar uma integração do Notion

Crie a integração:

  1. Acesse notion.so/my-integrations
  2. Clique em New Integration → nomeie como AI Creative Studio
  3. Associe ao seu espaço de trabalho
  4. Clique em Configurar configurações e verifique se as funcionalidades Ler conteúdo, Atualizar conteúdo e Inserir conteúdo estão marcadas.

Configurações de integração do Notion: dê o nome "AI Creative Studio" e copie o token.

  1. Copie o token de integração interna (ntn_...) e cole no arquivo .env:
NOTION_TOKEN=ntn_your-token-here

Conecte a integração aos seus bancos de dados:

  1. Abra a página do modelo que você acabou de duplicar e clique no banco de dados Projetos.
  2. Clique no menu ... (canto superior direito) → ConexõesAdicionar uma conexão → selecione AI Creative Studio

Clique em "Conexões" no menu do banco de dados para compartilhar com sua integração.

O AI Creative Studio aparece como uma conexão ativa.

  1. Faça o mesmo com o banco de dados Tarefas.

Receba os IDs do banco de dados:

  1. Clique no link do banco de dados Projetos para abrir. Ele será aberto em uma página própria com um URL como:
https://www.notion.so/9887b6a94f7f83f68f8581e038d1aaa4?v=2c37b6a94f7f838685f1086e312c7278

Como abrir o banco de dados de projetos na página de modelos

O ID do banco de dados é o primeiro UUID no URL, tudo antes de ?v=:

https://www.notion.so/{DATABASE_ID}?v=...
                       ^^^^^^^^^^^^^^^^
                       9887b6a94f7f83f68f8581e038d1aaa4  ← this is your DATABASE_ID
  1. Faça o mesmo com o link do banco de dados Tarefas para receber o ID do banco de dados.
  2. Adicione os três valores ao seu .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

Etapa 3: instalar o servidor MCP do Notion

O Project Manager se conecta ao Notion usando o pacote oficial @notionhq/notion-mcp-server Node.js. Instale globalmente:

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

Verifique a instalação:

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

Saída esperada:

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

notion-mcp-server: command not found

? Verifique se o Node.js está instalado (node --version) e se o bin global do npm está no seu PATH (export PATH=$PATH:$(npm bin -g)).

Etapa 4: verifique seu arquivo .env

Abra .env e confirme se todos os três valores do Notion estão definidos (você os adicionou na etapa 2):

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

O agente do Project Manager detecta automaticamente essas variáveis na inicialização e ativa o conjunto de ferramentas do MCP do Notion.

Como a descoberta de esquema funciona

O Gerenciador de projetos usa a descoberta dinâmica de esquemas e nunca codifica nomes de propriedades do 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

Isso significa que o agente se adapta automaticamente a qualquer estrutura de banco de dados do Notion. Renomeie suas propriedades para francês, árabe ou qualquer outra coisa, e o agente ainda vai funcionar.

TODO 1: escreva a instrução do sistema

O modelo inicial já calcula notion_section, uma string vazia quando o Notion não está configurado ou um bloco que contém os IDs do banco de dados e orientações completas da ferramenta quando está. Isso mantém as instruções do Notion totalmente fora do comando do agente que não usa o Notion. O LLM nunca vê regras para ferramentas que não tem.

Substitua o marcador de posição return por uma instrução real do sistema 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 sem Notion

Dentro de create_project_manager_agent(), na ramificação if not notion_token, substitua o agente incompleto por:

        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 com MCP do Notion

Observação:o arquivo inicial já contém um callback handle_notion_error pré-gravado acima de create_project_manager_agent(). Ele intercepta erros da API do Notion (400/404) e substitui os payloads de erro brutos por mensagens limpas e práticas para que o LLM possa se autocorrigir. Basta conectá-lo via after_tool_callback.

Primeiro, leia os dois IDs de banco de dados na parte de cima 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")

Em seguida, na ramificação else, crie o conjunto de ferramentas do MCP e o 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ática recomendada:nunca falhe de forma definitiva em integrações opcionais. A linha do tempo de texto é sempre o principal item entregue, e o Notion é complementar.

Testar o gerenciador de projetos localmente com o ADK Web

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

Abra a Visualização da Web na porta 8000. Use o menu suspenso do agente para selecionar project_manager e tente:

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.

Você vai ver um cronograma de texto estruturado com fases, lista de tarefas e marcos. Se as credenciais do Notion estiverem definidas em .env, o agente também vai criar entradas no seu espaço de trabalho do Notion.

9. Entender o protocolo A2A

Vamos usar o protocolo Agente-para-Agente (A2A) para conectar os diferentes agentes no nosso sistema. Vamos entender como isso funciona.

O problema que o A2A resolve

Imagine que você tem um agente de estratégia de marca criado com o ADK e um agente de redação publicitária criado com o LangGraph. Como uma pessoa liga para a outra? Elas falam linguagens internas diferentes. Você precisaria escrever um código de junção personalizado todas as vezes.

O A2A resolve isso definindo uma linguagem universal que qualquer agente, independente da estrutura, pode usar. É o HTTP do mundo dos agentes: um padrão que todos concordam para que qualquer pessoa possa conversar com qualquer pessoa.

O que é A2A?

O Agent-to-Agent (A2A) é um padrão aberto para comunicação entre agentes publicado pelo Google. Ele define:

  1. Como um agente se descreve: card do agente em /.well-known/agent.json
  2. Como outro agente o chama: JSON-RPC por HTTPS
  3. Como os resultados são retornados: streaming ou resposta única

O que torna o A2A flexível:

  • Independente de linguagem: os agentes Python podem conversar com agentes TypeScript.
  • Independente de framework: os agentes do ADK podem conversar com agentes do LangGraph ou do CrewAI
  • Independente da infraestrutura: os agentes locais podem conversar com os agentes na nuvem

Como funciona: passo a passo

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

Etapa 1:descoberta. O orquestrador busca o card do agente uma vez para saber o nome, o URL e os recursos dele.

Etapa 2:invocação. O orquestrador envia uma tarefa via POST JSON-RPC. O corpo contém a mensagem (o comando para o especialista).

Etapa 3: resposta: o especialista transmite a resposta em partes, assim como uma chamada normal de LLM.

O card do agente

Cada agente publica uma autodescrição em /.well-known/agent.json. É como um cartão de visitas: informa ao mundo o que o agente pode fazer e onde encontrá-lo:

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

O orquestrador lê esse card para criar o objeto RemoteA2aAgent. Não é necessário conhecimento codificado dos elementos internos do especialista.

Expor um agente via A2A no ADK

O to_a2a() encapsula qualquer agente do ADK em um app FastAPI compatível com A2A. Uma linha:

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)

Isso cria automaticamente:

  • /.well-known/agent.json: o card do agente
  • /: o endpoint JSON-RPC. Todas as solicitações de tarefas A2A vão para o caminho raiz.

10. Expor agentes como serviços A2A

Para expor agentes como serviços A2A, use a função utilitária to_a2a() do ADK.

Como o to_a2a() funciona

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)

O to_a2a() envolve seu agente do ADK em um aplicativo FastAPI que expõe automaticamente:

  • /.well-known/agent.json: o card do agente (nome, descrição, recursos)
  • /a2a/{agent_name}: o endpoint JSON-RPC para receber tarefas.

O código de estrutura de cada agente já inclui um bloco __main__ que envolve o agente em um servidor A2A usando to_a2a(). Não é preciso escrever esse código, ele já está disponível.

Como entender a configuração de URL duplo

Quando você executa python agent.py, o bloco __main__ usa duas configurações 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)

Ambiente

HOST:PORT (audiolivros)

PUBLIC_HOST:PUBLIC_PORT (anunciado no card do 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 apontam para a mesma máquina. No Cloud Run, o contêiner fica em escuta interna em 8080, mas o card do agente precisa anunciar o URL HTTPS público. Caso contrário, o diretor de criação não consegue entrar em contato com o especialista de fora do contêiner.

Inicie todos os cinco servidores A2A especializados.

Vamos executar todos os cinco especialistas como servidores A2A simultaneamente e testar o diretor de criação localmente apontando para eles.

Abra cinco terminais separados do Cloud Shell (clique no ícone + na barra de guias do terminal) e execute um agente por terminal.

O uv run ativa automaticamente o .venv. Não é necessário fazer source manual em cada terminal.

Terminal 1: estrategista de marca (porta 8082)

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

Terminal 2: redator publicitário (porta 8083)

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

Terminal 3: designer (porta 8084)

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

Terminal 4: Critic (porta 8085)

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

Terminal 5: gerente de projetos (porta 8086)

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

Definir URLs de localhost em .env

Em Terminal 6, atualize .env com os URLs do agente local para que o diretor de criação possa encontrá-los:

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

Inspecionar agentes com o A2A Inspector

O A2A Inspector é uma ferramenta de desenvolvedor de código aberto que usa o protocolo A2A de forma nativa. Ele permite se conectar diretamente a qualquer agente A2A em execução, ler o card do agente e enviar tarefas sem escrever código do cliente.

O que ela mostra:

  • Card do agente: os metadados estruturados que seu agente anuncia, como nome, descrição, modos de entrada/saída compatíveis e URL do endpoint. É isso que o diretor de criação lê quando descobre um especialista.
  • Interface de chat: envie qualquer mensagem para o agente pelo A2A e confira a resposta bruta. Você pode testar comandos isoladamente antes de conectar os agentes.
  • Validação de protocolo: o inspetor verifica se o card do agente está em conformidade com a especificação A2A, mostrando campos ausentes ou respostas malformadas no início.

Por que isso é importante:quando você implanta no Cloud Run mais tarde, o diretor de criação descobre cada especialista buscando o card do agente em /.well-known/agent.json. Se o card estiver errado (URL inválido, recursos ausentes), o orquestrador falhará silenciosamente. Com ele, é possível detectar esses problemas localmente antes de qualquer implantação na nuvem.

Card do agente de estratégia de marca

O card do agente mostra a identidade e os recursos do especialista exatamente como outros agentes os veem.

Detalhes do card do agente

Instalar e iniciar o inspetor

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

A atualização .env é um comando único. Use o Terminal 6 para iniciar o inspetor:

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

Para abrir a interface do inspetor, use Visualização da WebAlterar porta → digite 5001.

Conectar-se ao estrategista de marca

Insira http://localhost:8082 no campo de URL do inspetor e clique em Conectar. O inspetor busca o card do agente e mostra os metadados do especialista.

O A2A Inspector conectado ao estrategista de marca

O que o card do agente informa

O card do agente é mais do que metadados. Ele é o contrato de capacidade total que o agente anuncia para a rede. Entre em contato com o gerente de projeto (http://localhost:8086) para ver o exemplo mais 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"
    }
  ]
}

Três coisas se destacam:

1. As ferramentas do MCP se tornam habilidades de A2A: todas as ferramentas do Notion a que o gerente de projetos tem acesso (API-post-page, API-retrieve-a-database etc.) são listadas como uma habilidade separada no card do agente. Qualquer outro agente na rede pode descobrir exatamente quais ferramentas esse agente pode usar sem ler nenhum código.

2. A instrução do sistema está incorporada: a primeira habilidade description contém a instrução completa do sistema, incluindo a data de hoje e os IDs do banco de dados do Notion. É assim que o diretor de criação sabe o que transmitir quando chama o gerente de projetos.

3. O URL é o endpoint ativo: o campo url é exatamente o que o RemoteA2aAgent usa quando o diretor de criação chama esse especialista. Se o URL no card estiver errado, o orquestrador não poderá acessar o agente.

Por isso, o inspetor é uma ferramenta de depuração eficiente: basta olhar o card do agente para saber se ele está em execução, quais ferramentas ele tem e se o endpoint está correto.

Enviar uma mensagem de teste

Depois de conectado, digite um comando no painel de chat e envie. O inspetor envia como uma tarefa A2A e transmite a resposta de volta, da mesma forma que o diretor de criação vai chamar esse agente em produção.

Conversar com um estrategista de marca pelo A2A Inspector

Aponte o inspetor para qualquer porta local (8082 a 8086) para testar cada especialista individualmente.

11. Criar o orquestrador do diretor de criação

O diretor de criação é o principal organizador. Ele lê URLs especializados de variáveis de ambiente, encapsula cada um como um RemoteA2aAgent e os expõe como AgentTools que o LLM pode chamar.

Verifique se os cinco agentes especializados ainda estão em execução (terminais 1 a 5 da etapa 10).

No Terminal 6 (o terminal do inspetor A2A), interrompa o inspetor com Ctrl+C.

Abra o arquivo:

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

Este arquivo tem três TODOs. Siga as etapas na ordem.

PENDENTE 1: revise a instrução do sistema já escrita

A instrução do sistema está em prompt.py no mesmo diretório e é importada automaticamente:

from .prompt import SYSTEM_INSTRUCTION_TEMPLATE

Abra prompt.py para ler antes de continuar:

cloudshell edit agents/creative_director/prompt.py

É importante entender isso porque ele controla todo o comportamento de orquestração.

Por que o comando do orquestrador controla tudo

Abra prompt.py ao lado desta seção. Os exemplos abaixo fazem referência a partes específicas dela.

O comando em prompt.py não é apenas documentação, mas o plano de controle de todo o sistema. Um comando mal estruturado do orquestrador produz: agentes chamados fora de ordem, conteúdo gerado pelo orquestrador em vez de especialistas, fluxos de trabalho que continuam após falhas e contexto descartado silenciosamente entre agentes. Esses nove elementos evitam as falhas mais comuns:

Elemento 0: planeje primeiro, depois execute

Esse é o elemento mais importante. Antes de ligar para qualquer especialista, o orquestrador recebe instruções para gerar um plano 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

Sem essa etapa, o LLM passa direto para as chamadas de ferramentas e perde o controle de onde está no fluxo de trabalho, principalmente depois de receber uma resposta longa de um especialista. Ao delinear o plano primeiro, o orquestrador sabe em qual etapa está, o que vem a seguir e como é uma execução completa. Se você pular essa etapa, o orquestrador vai parar no meio do fluxo de trabalho ou repetir etapas.

Elemento 1: definição explícita de função

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

Sem essa proibição explícita, o LLM às vezes pula a chamada de especialistas e gera conteúdo diretamente. Isso é mais rápido e ele "sabe" como fazer isso. A instrução precisa fazer isso errado.

Elemento 2: sintaxe de chamada de função com padrões incorretos listados

Mostrar apenas a sintaxe correta não é suficiente. O LLM pode gerar chamadas que parecem plausíveis, mas falham silenciosamente. O comando lista explicitamente o padrão correto e os que nunca devem ser usados:

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

Listar os padrões errados explicitamente reduziu as chamadas de ferramentas malformadas em cerca de 95% na produção.

Elemento 3: execução sequencial detalhada etapa por etapa

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

Sem as etapas (b) e (c), o LLM às vezes chama dois agentes simultaneamente ou presume o sucesso e continua antes de receber a resposta.

Elemento 4: diretivas de erro STOP, report, do not proceed

Nas primeiras versões, o orquestrador recebia um erro de um especialista, criava uma saída plausível para ele e continuava para o próximo agente. O usuário recebeu uma campanha completa criada com base em uma alucinação. A correção é explícita: PARE imediatamente. Informe o erro exato. Nunca continue.

Elemento 5: regras de transmissão de contexto

Os agentes remotos não têm histórico de conversas. Quando o orquestrador chama o redator publicitário por A2A, ele vê apenas a mensagem nessa única solicitação. Ele não tem ideia do que o estrategista de marca disse. O orquestrador precisa agrupar explicitamente as saídas anteriores em cada chamada subsequente:

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

A instrução diz explicitamente: "Agentes remotos NÃO têm memória compartilhada. Você precisa transmitir as saídas anteriores explicitamente". Sem isso, cada agente trabalha sem informações.

Elemento 6: classificação de solicitações simples e complexas

Nem toda solicitação precisa dos cinco agentes. O comando instrui o orquestrador a classificar a solicitação antes de planejar:

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

Sem essa classificação, o orquestrador executaria todos os cinco agentes para cada solicitação, incluindo "me dê três ideias de postagem", adicionando latência e custo desnecessários.

Elemento 7: regras de comunicação: mostrar resultados completos, sem filtragem

O comando é explícito de que o orquestrador não pode resumir nem editar o que os especialistas retornam:

- 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

Sem isso, o orquestrador reescreve as saídas dos especialistas com as próprias palavras, perdendo detalhes, introduzindo erros e anulando o propósito de ter especialistas.

Elemento 8: conclusão do fluxo de trabalho: nunca pare antes da hora

Um modo de falha sutil, mas crítico: o orquestrador anuncia um plano de cinco etapas, conclui três e apresenta os resultados como se tudo estivesse pronto. O comando impede isso com uma checklist explícita que precisa ser aprovada antes que o orquestrador possa terminar:

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

Isso impede que o orquestrador trate uma execução parcial como completa.

O ciclo de controle de qualidade

O fluxo de trabalho de revisão é a parte mais complexa do prompt.py. Abra a seção ## REVISION WORKFLOW e acompanhe.

Como funciona

Depois que o crítico responde, o diretor de criação não continua cegamente com o gerente de projeto. Ele lê a saída do Critic e ramifica:

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)

Isso é orientado por LLM, não por código

O codelab mencionado anteriormente diz que o orquestrador "analisa" a resposta do Critic. Não há código Python fazendo essa análise, nem regex, nem correspondência de strings. O diretor de criação é um LLM lendo a própria instrução. Essa instrução diz:

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

O LLM lê essas strings exatas na saída do Critic e segue a ramificação. Por isso, o formato de crítica é inegociável: se o crítico escrever "precisa de algum trabalho" em vez de NEEDS_REVISION, o LLM não vai encontrar uma correspondência na instrução e vai pular a etapa de revisão sem avisar.

Como o contexto é encaminhado em uma chamada de revisão

A chamada de revisão segue a mesma regra de transmissão de contexto do Elemento 5. O orquestrador precisa incluir tudo explicitamente porque o redator não se lembra da primeira versão:

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

Sem a seção "SUA PRIMEIRA VERSÃO", o Copywriter escreveria do zero em vez de melhorar o que já produziu.

O limite de uma revisão e por que isso é importante

Após uma rodada de revisão, o orquestrador passa para o gerente de projetos, independente da pontuação. A instrução rastreia isso mentalmente:

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

Sem esse limite, o loop pode ser executado indefinidamente: o revisor sinaliza um problema → o redator revisa → o revisor sinaliza de novo → o redator revisa de novo. Cada rodada custa tokens e tempo. Uma revisão é suficiente para melhorar a qualidade sem risco de um ciclo descontrolado.

O que é transmitido ao gerente de projeto

O gerente de projeto sempre recebe as versões finais aprovadas, não os originais. Se houver revisões, o orquestrador vai passar a cópia e os recursos visuais revisados. Se tudo foi aprovado na primeira vez, ele passa essas informações diretamente. O PM nunca vê rascunhos rejeitados.

TODO 2 - Registre cada especialista como um RemoteA2aAgent + AgentTool

Encontre o comentário # TODO 2: For each specialist URL... e substitua por:

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

PENDENTE 3: unir em um app com compactação de contexto

Por que a compactação é necessária

Todas as mensagens em uma conversa (o comando do usuário, cada chamada de ferramenta e cada resposta de ferramenta) são adicionadas à janela de contexto que o LLM lê no próximo turno. Em um fluxo de trabalho de cinco agentes, isso se acumula rapidamente:

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

No Agente 4 (Crítico), a janela de contexto contém a saída completa de todos os três agentes anteriores, geralmente de 8.000 a 12.000 tokens apenas em respostas de ferramentas. Mesmo com a grande janela de contexto do Gemini 2.5 Pro, a qualidade do raciocínio do orquestrador diminui à medida que ele precisa consultar um histórico cada vez maior. Sem compactação, fluxos de trabalho longos atingem limites práticos no Agent 4.

O que a compactação faz

Em vez de manter todos os eventos completos, o ADK chama periodicamente um LLM para resumir eventos mais antigos em uma representação compacta. Apenas o resumo de eventos anteriores e a saída completa do agente mais recente são mantidos no contexto.

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

O resumo preserva os fatos essenciais (insights principais, legendas aprovadas, conceitos visuais) e descarta a formatação detalhada, o contexto repetido transmitido a cada agente e o raciocínio intermediário. O Critic ainda tem tudo o que precisa para avaliar, só que lê um resumo em vez de três relatórios completos.

O código

Encontre o comentário # TODO 3: Wrap the agent in an App... e substitua o marcador App(...) por:

    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: a compactação é acionada após cada três conclusões do agente. Para um pipeline de cinco agentes, isso significa que ele é acionado uma vez (após os agentes 1 a 3). Em seguida, o Critic e o PM veem um resumo dos agentes 1 a 3 mais a saída completa do agente anterior.

overlap_size=1: a resposta completa mais recente do agente é sempre mantida na íntegra, nunca resumida. Isso é importante porque o Critic precisa da saída completa do Designer, incluindo os valores gcs_uri, para carregar e analisar as imagens reais. Um resumo perderia esses URIs.

Como isso funciona em uma campanha 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]

Noções básicas sobre RemoteA2aAgent e 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

O LLM decide quando chamar cada ferramenta com base na instrução do sistema e na solicitação do usuário. O orquestrador nunca chama agentes diretamente no código. Tudo é impulsionado pelo raciocínio do LLM.

Testar o Creative Director localmente

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

Abra a Visualização da Web na porta 8000. Use o menu suspenso do agente para selecionar creative_director e tente:

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

O diretor de criação vai encaminhar isso apenas para o estrategista de marca, e você vai receber uma resposta dele.

Para a campanha completa, tente o seguinte:

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.

Você vai ver o diretor de criação coordenar todos os cinco especialistas em sequência, com a saída de cada agente fluindo para o próximo.

Demonstração: execução de campanha de ponta a ponta

Pare o Creative Director (Ctrl+C) antes de continuar. O inspetor A2A também usa a porta 8000.

Interrompa os cinco servidores especializados (Ctrl+C em cada terminal) quando terminar o teste local.

12. Implantar e testar os agentes especializados

Agora estamos prontos para implantar nossos agentes no Google Cloud. O Cloud Run é um ótimo serviço para implantar agentes. É sem servidor, escalonável e fácil de usar. Cada agente especialista é implantado como um serviço independente do Cloud Run.

Configuração da implantação

O Dockerfile de cada especialista segue este padrão:

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

Implantar todos os cinco especialistas em sequência

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

uv run deploy/deploy_all_specialists.py

Esse script implanta todos os cinco agentes um de cada vez (~10 a 12 minutos no total). A implantação sequencial evita a cota de polling do Cloud Build (60 solicitações/minuto). Quando concluído, ele grava o URL do Cloud Run de cada agente de volta em .env.

Depois que o Designer é implantado, o script concede automaticamente à conta de serviço do Cloud Run roles/storage.objectCreator no seu bucket do GCS para que ele possa fazer upload das imagens geradas.

Se você configurou as credenciais do Notion em .env, o script também as armazena com segurança no Secret Manager (como notion-token, notion-project-db-id, notion-tasks-db-id) e as injeta no serviço Project Manager via --set-secrets em vez de variáveis de ambiente simples. Isso significa que o token nunca aparece na guia "Ambiente" do Cloud Run nem no histórico de comandos gcloud.

Verificar implantações

Quando a implantação for concluída, o script vai gravar automaticamente os URLs do Cloud Run de volta no .env, substituindo os URLs de localhost da etapa 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"

O diretor de criação vai usar automaticamente esses URLs do Cloud Run quando forem implantados no ambiente de execução do agente na próxima etapa.

Verificar cartões de agente

Cada agente implantado expõe um card em /.well-known/agent.json. Busque-os para confirmar se tudo está ativo:

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

Saída esperada para cada agente:

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

Teste com o A2A Inspector (Cloud Run)

O A2A Inspector já está instalado desde a etapa 10. Inicie-o:

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

Abra a Visualização na Web → Alterar porta5001. Insira o URL do Cloud Run no campo de conexão:

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

Clique em Conectar. Não é necessário um token de autenticação, já que os serviços são implantados com --allow-unauthenticated.

O inspetor se conecta, valida o card do agente e permite que você converse de forma interativa por A2A.

Inspecionar agentes implantados no Cloud Run

Depois de implantar no Cloud Run, aponte o inspetor para o URL HTTPS público para verificar se a implantação na nuvem está funcionando:

Inspetor A2A conectado ao agente do Cloud Run

O fluxo de trabalho é idêntico: cole o URL do Cloud Run, conecte e envie uma mensagem de teste. Se o card do agente carregar e o chat responder, o especialista foi implantado e está disponível.

13. Implantar o Creative Director no Agent Runtime

O orquestrador é implantado no tempo de execução do agente, que fornece estado de sessão gerenciado, escalonamento automático e rastreamento integrado.

Por que usar o Agent Runtime para o orquestrador?

Os cinco especialistas são implantados no Cloud Run, que é leve, sem estado e processa uma tarefa por vez. O diretor de criação tem requisitos diferentes:

Requisito

Por que isso é importante

Estado da sessão

Um fluxo de trabalho com várias etapas leva mais de 45 segundos. O Agent Runtime mantém o estado da conversa entre as chamadas de ferramentas do orquestrador para que nada seja perdido no meio do pipeline.

Carga variável

Às vezes, uma campanha por hora, às vezes, várias em paralelo. O tempo de execução do agente é reduzido a zero quando está inativo e é escalonado horizontalmente de forma automática. Você não paga por capacidade ociosa.

Observabilidade

O Cloud Logging, o Cloud Monitoring e o Cloud Trace vêm integrados. É possível ver todas as chamadas A2A, todos os tokens usados e todos os picos de latência sem adicionar instrumentação.

Fluxos de trabalho de longa duração

O Cloud Run tem um tempo limite de solicitação de 3.600 segundos. O Agent Runtime foi projetado para fluxos de trabalho que podem levar minutos, com novas tentativas gerenciadas e persistência de estado.

O Cloud Run é a plataforma certa para especialistas em aplicativos sem estado. O Agent Runtime é a plataforma certa para o orquestrador com estado.

Implantar o orquestrador

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

uv run deploy/deploy_orchestrator.py --action deploy

Isso leva de 5 a 10 minutos. Quando concluídos, o AGENT_ENGINE_ID e o AGENT_ENGINE_RESOURCE_NAME são salvos em .env.

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

Como a implantação funciona

O client.agent_engines.create() empacota o objeto App, faz upload dele com as dependências e o implanta na infraestrutura gerenciada. Confira o que cada parâmetro faz:

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]

O que acontece nos bastidores:

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

Após a implantação, o orquestrador se conecta aos cinco especialistas do Cloud Run usando os URLs nas variáveis de ambiente.

  • Elas são transmitidas por .env antes da execução do script de implantação.

14. Veicular uma campanha completa

Todo o sistema é implantado. Execute uma campanha completa no playground do Agent Runtime.

Abra o playground do Agent Runtime

  1. Acesse https://console.cloud.google.com/agent-platform/runtimes. Você também pode acessar o Agent Runtime em Plataforma de agentes > Agentes > Implantações.
  2. Selecione o ambiente de execução do agente implantado (creative-director).
  3. Clique em Playground na barra lateral esquerda.
  4. Clique em Nova sessão para abrir uma conversa.

Veicular uma campanha completa

Cole este resumo no chat e envie:

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

O diretor de criação vai executar todos os cinco agentes em sequência:

  1. Estrategista de marca: pesquisa de mercado, análise da concorrência, insights sobre público-alvo
  2. Redator → 3 postagens no Instagram com legendas, hashtags e CTAs
  3. Designer → conceitos visuais + imagens reais geradas pelo Gemini (URIs do GCS) para cada postagem
  4. Crítica → revisão de qualidade com pontuações APROVADO / PRECISA_DE_REVISÃO
  5. (Revisão, se necessário) → O redator ou designer foi chamado novamente com feedback
  6. Gerente de projetos → cronograma de duas semanas, detalhamento de tarefas, alocação de orçamento

Demonstração: execução de campanha com integração do Notion

Testar o roteamento de um único agente

Envie essa solicitação mais curta em uma nova sessão:

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

O diretor de criação encaminha isso apenas para o estrategista de marca. Nenhum outro agente é chamado. Essa é a lógica de classificação de solicitações da instrução do sistema funcionando corretamente.

Inspecionar rastreamentos de execução

Ainda no console:

  1. Clique em Rastreamentos na barra lateral esquerda (ao lado de Playground).
  2. Em Visualização de rastreamento, selecione o rastreamento da sessão que você acabou de executar.
  3. Expanda a árvore de rastreamento para ver cada chamada de agente, as entradas/saídas, a latência e o uso de tokens.

Cada chamada de A2A para um especialista aparece como um intervalo separado. Você pode ver exatamente qual contexto o diretor de criação passou para cada agente e o que ele recebeu de volta.

Opcional: executar no terminal

Você também pode executar a campanha de forma programática usando o script run_campaign.py, que já está incluído no starter.

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

15. Limpeza

Limpe os recursos do Google Cloud para evitar cobranças contínuas.

Execute o script de encerramento. Ele lê seu .env e exclui tudo o que foi criado durante este codelab:

bash deploy/teardown_gcp.sh

O script mostra exatamente o que será excluído e pede confirmação antes de fazer qualquer coisa:

Recurso

O que é excluído

Serviços do Cloud Run

estrategista de marca, redator, designer, crítico, gerente de projetos

Agent Runtime

Mecanismo de raciocínio do diretor de criação + todas as sessões

Artifact Registry

cloud-run-source-deploy repositório + todas as imagens do Docker

Buckets do 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 (ignorados se não forem criados)

Verificar se tudo foi removido

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

Saída esperada: listas vazias ou apenas seus próprios recursos preexistentes.

16. Resumo

Parabéns! Você criou e implantou um sistema de IA multiagente de nível de produção no Google Cloud.

O que você criou

Agente

Capacidade

Implantação

Estrategista de marca

Pesquisa de mercado pela Pesquisa Google

Cloud Run

Copywriter

Criação de legendas para o Instagram

Cloud Run

Designer

Geração de imagens com upload do Gemini + GCS

Cloud Run

Crítico

Avaliação de qualidade com pontuação

Cloud Run

Gerente de projetos

Cronograma + MCP do Notion

Cloud Run

Diretor de criação

Orquestração completa via A2A

Agent Runtime

Principais padrões aprendidos

  1. ADK Agent: defina um agente de LLM com uma instrução e ferramentas opcionais
  2. adk web: execute e teste qualquer agente do ADK localmente com uma interface de chat integrada
  3. SkillToolset: empacota o conhecimento reutilizável em arquivos modulares carregados sob demanda.
  4. FunctionTool: encapsula qualquer função Python (ou modelo externo) como uma ferramenta de agente chamável.
  5. to_a2a(): expõe qualquer agente do ADK como um serviço HTTPS compatível com A2A.
  6. RemoteA2aAgent + AgentTool: orquestrar agentes remotos como ferramentas chamáveis
  7. McpToolset: conecte-se a serviços externos via servidores stdio do MCP.
  8. EventsCompactionConfig: como lidar com limites de tokens em fluxos de trabalho multiagente longos
  9. Saída estruturada da crítica: controle de qualidade legível por máquina com revisão automática
  10. Cloud Run: implante agentes em contêineres em grande escala
  11. Agent Runtime: orquestradores de host com sessões gerenciadas e rastreamento.

Próximas etapas

  • Adicionar a edição de imagens em várias etapas ao Designer usando a capacidade de edição do gemini-3.1-flash-image-preview
  • Adicionar autenticação do IAM aos serviços do Cloud Run (remover --allow-unauthenticated)
  • Substituir um especialista por um agente LangGraph ou CrewAI: o A2A é independente de framework
  • Adicione o feedback do usuário como uma ferramenta para que os participantes possam classificar e iterar nas saídas
  • Conheça o rastreamento do tempo de execução do agente no console do Cloud

Recursos