Ir al contenido

Investigacion: Deploy ADK en Cloud Run — Hechos Completos

Investigacion: Deploy ADK en Cloud Run — Hechos Completos

Sección titulada «Investigacion: Deploy ADK en Cloud Run — Hechos Completos»

Fecha: 2026-04-05 Fuentes analizadas:

  • adk.dev/deploy/cloud-run/ (guia oficial ADK)
  • docs.cloud.google.com/run/docs/deploy-a2a-agents (guia Google Cloud)
  • github.com/a2aproject/a2a-samples/samples/python/agents/adk_cloud_run (sample oficial)
  • Codigo fuente ADK: fast_api.py, cli_deploy.py, cli_tools_click.py (version 1.22+)

1. LAS TRES FORMAS OFICIALES DE DEPLOYAR ADK EN CLOUD RUN

Sección titulada «1. LAS TRES FORMAS OFICIALES DE DEPLOYAR ADK EN CLOUD RUN»

FORMA A: adk deploy cloud_run (CLI automatico)

Sección titulada «FORMA A: adk deploy cloud_run (CLI automatico)»

El CLI de ADK genera un Dockerfile temporal, copia el agente y ejecuta gcloud run deploy.

Que genera internamente (del codigo fuente cli_deploy.py):

temp_folder/
agents/{app_name}/ <- copia del codigo del agente
Dockerfile <- generado automaticamente

Dockerfile generado (template real del codigo fuente):

FROM python:3.11-slim
WORKDIR /app
RUN adduser --disabled-password --gecos "" myuser
USER myuser
ENV PATH="/home/myuser/.local/bin:$PATH"
ENV GOOGLE_GENAI_USE_VERTEXAI=1
ENV GOOGLE_CLOUD_PROJECT={gcp_project_id}
ENV GOOGLE_CLOUD_LOCATION={gcp_region}
RUN pip install google-adk=={adk_version}
COPY --chown=myuser:myuser "agents/{app_name}/" "/app/agents/{app_name}/"
# Si existe requirements.txt en el agente:
RUN pip install -r "/app/agents/{app_name}/requirements.txt"
EXPOSE {port}
CMD adk api_server --port={port} --host=0.0.0.0 {service_options} {a2a_option} "/app/agents"

Comando:

adk deploy cloud_run \
--project=$GOOGLE_CLOUD_PROJECT \
--region=$GOOGLE_CLOUD_LOCATION \
--service_name=mi-agente \
--a2a \
path/to/agent_folder \
-- --no-allow-unauthenticated --memory=1Gi

Flags disponibles:

  • --project (requerido): GCP project ID
  • --region (requerido): GCP region
  • --service_name (default: adk-default-service-name)
  • --app_name (default: nombre de la carpeta del agente)
  • --port (default: 8000)
  • --with_ui: incluye web UI (Angular)
  • --a2a: habilita endpoint A2A
  • --trace_to_cloud: Cloud Trace
  • --otel_to_cloud: OpenTelemetry a GCP
  • --session_service_uri: URI de sesiones
  • --artifact_service_uri: URI de artefactos
  • --memory_service_uri: URI de memoria
  • --adk_version: version de ADK a usar
  • -- [gcloud args]: argumentos pasados directamente a gcloud

Detalle critico del codigo fuente (cli_deploy.py linea 687-689): Cuando adk_version >= 1.3.0 y use_local_storage=False (default para Cloud Run):

  • session_service_uri se fuerza a memory://
  • artifact_service_uri se fuerza a memory://

Esto significa: sin configuracion explicita, las sesiones son IN-MEMORY y se pierden en cada restart de Cloud Run.

Estructura del agente esperada:

agent_folder/
__init__.py <- from . import agent
agent.py <- variable root_agent
requirements.txt <- opcional, dependencias extra

FORMA B: gcloud run deploy con Dockerfile propio + main.py custom

Sección titulada «FORMA B: gcloud run deploy con Dockerfile propio + main.py custom»

La guia oficial de adk.dev describe esta forma para mas control.

Estructura:

project/
capital_agent/
__init__.py
agent.py
main.py <- entry point custom con get_fast_api_app()
requirements.txt
Dockerfile

main.py oficial (de adk.dev/deploy/cloud-run/):

import os
import uvicorn
from fastapi import FastAPI
from google.adk.cli.fast_api import get_fast_api_app
AGENT_DIR = os.path.dirname(os.path.abspath(__file__))
SESSION_SERVICE_URI = "sqlite+aiosqlite:///./sessions.db"
ALLOWED_ORIGINS = ["http://localhost", "http://localhost:8080", "*"]
SERVE_WEB_INTERFACE = True
app: FastAPI = get_fast_api_app(
agents_dir=AGENT_DIR,
session_service_uri=SESSION_SERVICE_URI,
allow_origins=ALLOWED_ORIGINS,
web=SERVE_WEB_INTERFACE,
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

Dockerfile oficial (de adk.dev/deploy/cloud-run/):

FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN adduser --disabled-password --gecos "" myuser && \
chown -R myuser:myuser /app
COPY . .
USER myuser
ENV PATH="/home/myuser/.local/bin:$PATH"
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]

Deploy:

gcloud run deploy capital-agent-service \
--source . \
--region $GOOGLE_CLOUD_LOCATION \
--project $GOOGLE_CLOUD_PROJECT \
--allow-unauthenticated \
--set-env-vars="GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_GENAI_USE_VERTEXAI=$GOOGLE_GENAI_USE_VERTEXAI"

NOTA: Esta forma NO incluye A2A en el ejemplo oficial. Para A2A habria que pasar a2a=True a get_fast_api_app().

FORMA C: Sample A2A oficial (a2a-samples/python/agents/adk_cloud_run)

Sección titulada «FORMA C: Sample A2A oficial (a2a-samples/python/agents/adk_cloud_run)»

Esta es la forma mas completa para A2A. NO usa ADK’s get_fast_api_app(). Construye el servidor A2A desde cero con a2a-sdk.

Estructura:

adk_cloud_run/
__init__.py <- from . import agent
__main__.py <- entry point (Starlette + A2A SDK)
agent.py <- ADK Agent con root_agent
agent_executor.py <- A2aAgentExecutor custom
pyproject.toml <- dependencias con uv
Dockerfile
.dockerignore

Dockerfile:

FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
EXPOSE 8080
WORKDIR /app
COPY . ./
RUN uv sync
ENTRYPOINT ["uv", "run", ".", "--host", "0.0.0.0", "--port", "8080"]

main.py (entry point completo):

Crea el AgentCard en codigo (NO lee agent.json):

agent_card = AgentCard(
name=calendar_agent.name,
description=calendar_agent.description,
version='1.0.0',
url=os.environ['APP_URL'], # <--- URL del servicio Cloud Run
default_input_modes=['text', 'text/plain'],
default_output_modes=['text', 'text/plain'],
capabilities=AgentCapabilities(streaming=True),
skills=[AgentSkill(id='...', name='...', description='...', tags=[...])],
)

Soporta dos task stores:

  • InMemoryTaskStore() (default)
  • DatabaseTaskStore(engine) (AlloyDB PostgreSQL)

Monta las rutas con A2AStarletteApplication:

a2a_app = A2AStarletteApplication(agent_card=agent_card, http_handler=request_handler)
routes = a2a_app.routes() # <--- SIN url prefix, rutas en /
app = Starlette(routes=routes)

agent_executor.py:

Implementa AgentExecutor del A2A SDK. Es el bridge entre A2A y ADK:

  • Crea un Runner con InMemorySessionService, InMemoryArtifactService, InMemoryMemoryService
  • Recibe mensaje A2A, lo convierte a types.Content
  • Ejecuta runner.run_async()
  • Recoge event.is_final_response() y lo devuelve como artifact A2A

pyproject.toml:

[project]
name = "adk-cloud-run"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"a2a-sdk>=0.3.0",
"starlette>=0.46.1",
"uvicorn>=0.34.0",
"click>=8.1.8",
"google-adk>=1.7.0",
"python-dotenv>=1.1.0",
"asyncpg>=0.30.0",
"google-cloud-alloydb-connector[asyncpg]>=1.9.0",
"litellm>=1.40.0",
]

Deploy (con InMemoryTaskStore):

gcloud run deploy sample-a2a-agent \
--port=8080 \
--source=. \
--no-allow-unauthenticated \
--memory="1Gi" \
--region="us-central1" \
--project="{project-id}" \
--service-account=a2a-service-account \
--set-env-vars=GOOGLE_GENAI_USE_VERTEXAI=true,\
GOOGLE_CLOUD_PROJECT="{project-id}",\
GOOGLE_CLOUD_LOCATION="us-central1",\
APP_URL="https://sample-a2a-agent-{number}.{region}.run.app"

Deploy (con AlloyDB):

gcloud run deploy sample-a2a-agent \
--port=8080 \
--source=. \
--no-allow-unauthenticated \
--memory="1Gi" \
--region="us-central1" \
--project="{project-id}" \
--update-secrets=DB_USER=alloy_db_user:latest,DB_PASS=alloy_db_pass:latest \
--service-account=a2a-service-account \
--set-env-vars=GOOGLE_GENAI_USE_VERTEXAI=true,\
GOOGLE_CLOUD_PROJECT="{project-id}",\
GOOGLE_CLOUD_LOCATION="us-central1",\
USE_ALLOY_DB="True",\
DB_INSTANCE="projects/{id}/locations/{region}/clusters/{cluster}/instances/primary-instance",\
DB_NAME="postgres",\
APP_URL="https://sample-a2a-agent-{number}.{region}.run.app"

Despues del deploy, actualizar APP_URL:

gcloud run services update sample-a2a-agent \
--project="{project-id}" \
--region="us-central1" \
--update-env-vars=APP_URL="{url-real-del-servicio}"

2. COMO FUNCIONA get_fast_api_app() — ANALISIS DEL CODIGO FUENTE

Sección titulada «2. COMO FUNCIONA get_fast_api_app() — ANALISIS DEL CODIGO FUENTE»

Archivo: google/adk/cli/fast_api.py

def get_fast_api_app(
*,
agents_dir: str, # REQUERIDO
agent_loader: Optional[BaseAgentLoader] = None,
session_service_uri: Optional[str] = None,
session_db_kwargs: Optional[Mapping] = None,
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
use_local_storage: bool = True,
eval_storage_uri: Optional[str] = None,
allow_origins: Optional[list[str]] = None,
web: bool, # REQUERIDO
a2a: bool = False,
host: str = "127.0.0.1",
port: int = 8000,
url_prefix: Optional[str] = None,
trace_to_cloud: bool = False,
otel_to_cloud: bool = False,
reload_agents: bool = False,
lifespan: Optional[Lifespan] = None,
extra_plugins: Optional[list[str]] = None,
logo_text: Optional[str] = None,
logo_image_url: Optional[str] = None,
auto_create_session: bool = False,
) -> FastAPI:
  • None (default) -> SQLite local en .adk/session.db (si use_local_storage=True)
  • "memory://" -> InMemorySessionService (se pierde en restart)
  • "sqlite+aiosqlite:///path.db" -> DatabaseSessionService con SQLite async
  • "postgresql+asyncpg://..." -> DatabaseSessionService con PostgreSQL
  • "mysql+aiomysql://..." -> DatabaseSessionService con MySQL
  • "agentengine://..." -> Agent Engine service

Cuando a2a=True:

  1. Itera sobre TODAS las subcarpetas de agents_dir
  2. Solo procesa carpetas que tienen un archivo agent.json
  3. Para cada carpeta con agent.json:
    • Lee el agent.json y lo parsea como AgentCard
    • Crea un A2aAgentExecutor que usa el runner de ADK
    • Crea DefaultRequestHandler con InMemoryTaskStore compartido
    • Crea A2AStarletteApplication
    • Monta rutas en:
      • /a2a/{app_name} (endpoint JSON-RPC)
      • /a2a/{app_name}/.well-known/agent-card.json (agent card)

HECHO CRITICO: El agent.json es OBLIGATORIO para A2A via get_fast_api_app(). Sin el, el agente se ignora silenciosamente.

HECHO CRITICO 2: El InMemoryTaskStore es compartido entre TODOS los agentes A2A en el mismo servidor. No se puede configurar un task store diferente (como AlloyDB) desde get_fast_api_app().

Cuando web=True:

  • Sirve archivos estaticos de Angular desde el directorio browser/ dentro del package ADK
  • Registra endpoints /builder/save, /builder/app/{app_name}, etc.
  • Los endpoints del builder solo se registran cuando web=True (por seguridad)

El servidor base (AdkWebServer) expone:

  • GET /list-apps -> lista agentes disponibles
  • POST /apps/{app_name}/users/{user_id}/sessions/{session_id} -> crear/actualizar sesion
  • POST /run_sse -> ejecutar agente (streaming SSE o no)
  • Otras rutas de evaluacion y gestion

GOOGLE_GENAI_USE_VERTEXAI=True (o =1)
GOOGLE_CLOUD_PROJECT=project-id
GOOGLE_CLOUD_LOCATION=us-central1
  • No requiere API key
  • Usa la identidad del service account de Cloud Run
  • Requiere rol roles/aiplatform.user en el service account
GOOGLE_GENAI_USE_VERTEXAI=FALSE
GOOGLE_API_KEY=tu-api-key
GOOGLE_CLOUD_PROJECT=project-id # aun requerido en algunos contextos
GOOGLE_CLOUD_LOCATION=us-central1
  • Mas simple para desarrollo
  • La API key debe guardarse como Secret en Secret Manager
  • El service account necesita roles/secretmanager.secretAccessor

El sample oficial usa LiteLlm(model='gemini/gemini-2.5-flash-lite'), lo que permite usar multiples proveedores. Esto requiere litellm como dependencia.


OpcionURIPersistenciaUso recomendado
In-Memorymemory://Se pierde en restartPrototipos, agentes stateless
SQLite localNone + use_local_storage=TruePersiste en discoDesarrollo local
SQLite asyncsqlite+aiosqlite:///path.dbPersiste en discoCloud Run con volumen
PostgreSQLpostgresql+asyncpg://...Persiste en DBProduccion
MySQLmysql+aiomysql://...Persiste en DBProduccion
AlloyDBVia conector Google CloudPersiste en DBProduccion GCP

Para Cloud Run sin volumen persistente: las sesiones se pierden en cada cold start si usas memory:// o SQLite local. Para persistencia real necesitas PostgreSQL/AlloyDB.


Roles necesarios para el service account del servicio Cloud Run:

Sección titulada «Roles necesarios para el service account del servicio Cloud Run:»
  • roles/aiplatform.user (para llamar a Vertex AI)
  • roles/secretmanager.secretAccessor (para leer secrets)
  • roles/alloydb.client (si usa AlloyDB)
  • roles/serviceusage.serviceUsageConsumer (si usa AlloyDB)
  • roles/run.sourceDeveloper
  • roles/resourcemanager.projectIamAdmin
  • roles/secretmanager.admin
  • roles/iam.serviceAccountUser

Auth entre servicios (orchestrator -> agentes):

Sección titulada «Auth entre servicios (orchestrator -> agentes):»
  • --no-allow-unauthenticated en cada Cloud Run service
  • El service account del caller necesita roles/run.invoker
  • Token: gcloud auth print-identity-token o identity token via metadata server
gcloud iam service-accounts create a2a-service-account \
--description="service account for a2a cloud run service" \
--display-name="A2A cloud run service account"

export APP_URL="https://tu-servicio.run.app"
export TOKEN=$(gcloud auth print-identity-token)
# Listar apps
curl -X GET -H "Authorization: Bearer $TOKEN" $APP_URL/list-apps
# Crear sesion
curl -X POST -H "Authorization: Bearer $TOKEN" \
$APP_URL/apps/platform_architect/users/user_123/sessions/session_abc \
-H "Content-Type: application/json" \
-d '{"state": {}}'
# Ejecutar agente
curl -X POST -H "Authorization: Bearer $TOKEN" \
$APP_URL/run_sse \
-H "Content-Type: application/json" \
-d '{
"app_name": "platform_architect",
"user_id": "user_123",
"session_id": "session_abc",
"new_message": {"role": "user", "parts": [{"text": "Build IDP for FastAPI"}]},
"streaming": false
}'
# Verificar agent card
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
$APP_URL/a2a/platform_architect/.well-known/agent-card.json
# Enviar tarea A2A (JSON-RPC 2.0)
curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" \
$APP_URL/a2a/platform_architect \
-d '{
"jsonrpc": "2.0",
"method": "tasks/send",
"id": "1",
"params": {
"id": "task-1",
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "Build IDP for FastAPI"}]
}
}
}'

Dockerfile (platform_architect y otros):

FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
EXPOSE 8080
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --no-dev --frozen
COPY . /app/platform_architect/
ENTRYPOINT ["uv", "run", "adk", "api_server", "--a2a", "--host", "0.0.0.0", "--port", "8080"]

main.py:

agents_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, agents_dir)
app = get_fast_api_app(agents_dir=agents_dir, web=False)

agent.json (platform_architect):

{
"name": "Platform Architect Agent",
"description": "...",
"url": "https://platform-architect-r7xydrdtta-uc.a.run.app/a2a/platform_architect",
"version": "1.0.0",
"capabilities": { "streaming": false, "pushNotifications": false },
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [...]
}

orchestrator_sequential_a2a.py:

  • Usa RemoteA2aAgent + SequentialAgent
  • Agent card URL: {base_url}/a2a/{agent_name}/.well-known/agent-card.json
  • IAM auth via gcloud auth print-identity-token
  1. Base image: python:3.13-slim — correcto (Google Cloud docs usa esta)
  2. uv como package manager: correcto (Google Cloud docs lo usa)
  3. Port 8080: correcto (Cloud Run default)
  4. agent.json existe: requerido para A2A via get_fast_api_app()
  5. --no-allow-unauthenticated: mencionado en CLAUDE.md del proyecto, correcto
  6. __init__.py con from . import agent: correcto
  7. Orchestrator con RemoteA2aAgent: approach valido
  8. IAM auth en orchestrator: correcto, pattern oficial

PROBLEMA 1: Dockerfile usa ENTRYPOINT con adk api_server PERO tambien tiene __main__.py con get_fast_api_app()

Sección titulada «PROBLEMA 1: Dockerfile usa ENTRYPOINT con adk api_server PERO tambien tiene __main__.py con get_fast_api_app()»

Son DOS mecanismos diferentes. El Dockerfile ejecuta adk api_server (que internamente llama get_fast_api_app() con sus propios defaults), pero el __main__.py tambien crea su propio app con get_fast_api_app(). Solo UNO se ejecuta.

Con el ENTRYPOINT actual (uv run adk api_server --a2a ...), el __main__.py es IGNORADO completamente. El CLI adk api_server resuelve el agents_dir como el directorio actual (/app).

Consecuencia: El __main__.py NO se usa. Esto no es un bug, pero es confuso.

PROBLEMA 2: Estructura de directorios dentro del container

Sección titulada «PROBLEMA 2: Estructura de directorios dentro del container»

El Dockerfile copia el agente a /app/platform_architect/. El ENTRYPOINT ejecuta adk api_server sin especificar agents_dir, asi que usa el CWD que es /app.

Esto FUNCIONA porque ADK busca subcarpetas de agents_dir con __init__.py + agent.py. La carpeta /app/platform_architect/ seria descubierta.

PERO: tambien copia pyproject.toml y uv.lock a /app/, y el propio __main__.py y otros archivos quedarian en /app/platform_architect/. Esto es correcto.

app = get_fast_api_app(agents_dir=agents_dir, web=False)
# FALTA: a2a=True

Si alguien ejecutara el __main__.py directamente (ej: python -m platform_architect), NO tendria A2A habilitado. Solo funciona A2A porque el Dockerfile usa adk api_server --a2a.

PROBLEMA 4: uv sync --no-dev --frozen requiere uv.lock

Sección titulada «PROBLEMA 4: uv sync --no-dev --frozen requiere uv.lock»

El Dockerfile hace COPY pyproject.toml uv.lock ./ y luego RUN uv sync --no-dev --frozen. Esto esta bien, pero el sample oficial de Google hace COPY . ./ y luego RUN uv sync (sin --frozen). La diferencia: nosotros tenemos mejor layer caching pero dependemos de que uv.lock exista y este actualizado.

"url": "https://platform-architect-r7xydrdtta-uc.a.run.app/a2a/platform_architect"

Esta URL cambia cada vez que se redeploya. El sample oficial usa APP_URL como env var y construye el AgentCard en codigo (no en JSON). Nuestro approach requiere actualizar el JSON despues de cada deploy.

PERO: El mecanismo de ADK’s get_fast_api_app() lee el agent.json directamente. No hay opcion de inyectar la URL desde env var. La URL en agent.json es usada por los CLIENTES (como el orchestrator), no por el servidor. El servidor monta la ruta en /a2a/{app_name} independientemente de lo que diga url.

Conclusion: La url en agent.json es informativa para los clientes. El orchestrator usa su propia logica para construir la URL. No es un blocker, pero es mejor tenerla correcta.

PROBLEMA 6: No hay variable APP_URL en el deploy

Sección titulada «PROBLEMA 6: No hay variable APP_URL en el deploy»

El sample A2A oficial de Google requiere APP_URL como env var para construir el AgentCard en runtime. Nosotros no la usamos porque el agent.json tiene la URL hardcoded. Si migramos al approach del sample (AgentCard en codigo), necesitariamos esta env var.


Contradiccion 1: Python version en Dockerfile

Sección titulada «Contradiccion 1: Python version en Dockerfile»
  • adk deploy cloud_run genera: FROM python:3.11-slim (del template en cli_deploy.py)
  • Google Cloud docs muestra: FROM python:3.13-slim
  • Sample A2A oficial: FROM python:3.13-slim
  • Nuestra eleccion: python:3.13-slim — alineado con las fuentes mas recientes
  • adk deploy cloud_run genera: pip install google-adk=={version} + pip install -r requirements.txt
  • Google Cloud docs: uv con ENTRYPOINT uv run .
  • adk.dev docs (gcloud method): pip install
  • Sample A2A: uv con ENTRYPOINT uv run .
  • adk api_server default: 8000
  • Cloud Run default: 8080
  • Nuestro Dockerfile: 8080 (correcto para Cloud Run)
  • adk deploy cloud_run default: 8000 (necesita override con --port)
  • ADK get_fast_api_app(a2a=True) lee agent.json de cada subcarpeta y monta en /a2a/{name}
  • Sample A2A oficial NO usa get_fast_api_app(), construye todo con a2a-sdk + Starlette, monta rutas en raiz
  • Son approaches completamente diferentes. El primero es mas simple, el segundo da mas control.
  • ADK approach: lee de archivo agent.json
  • Sample A2A: construye AgentCard en codigo Python con APP_URL de env var
  • Ninguna fuente combina ambos approaches

Nuestro caso: 7 agentes independientes + A2A + orchestrator

Sección titulada «Nuestro caso: 7 agentes independientes + A2A + orchestrator»

Recomendacion: mantener el approach actual (Forma A/B hibrida) con correcciones.

Razon: Nuestro caso usa adk api_server --a2a, que internamente llama get_fast_api_app(a2a=True). Esto:

  • Descubre el agente en la subcarpeta
  • Lee agent.json
  • Monta A2A en /a2a/{agent_name}
  • Monta API en /run_sse, /list-apps, etc.
  • Todo en UN solo proceso/container

Esto es mas simple que el sample A2A (que requiere escribir agent_executor.py y __main__.py custom). Y funciona perfectamente para nuestro caso.

Opciones:

  • Opcion A: Eliminarlo. Dejar solo el Dockerfile con adk api_server --a2a. Simple.
  • Opcion B: Mantenerlo pero con a2a=True para poder ejecutar localmente con python -m platform_architect.

Si mantenemos, corregir:

app = get_fast_api_app(agents_dir=agents_dir, web=False, a2a=True)

2. Actualizar agent.json con URL parametrica o placeholder

Sección titulada «2. Actualizar agent.json con URL parametrica o placeholder»

No se puede usar env vars en JSON estatico. Opciones:

  • Poner un placeholder y actualizarlo post-deploy con script
  • Dejar la URL real despues del primer deploy y no cambiarla (Cloud Run mantiene la URL si no borras el servicio)
gcloud run deploy platform-architect \
--source=agents_adk/platform_architect/ \
--port=8080 \
--no-allow-unauthenticated \
--memory=1Gi \
--region=us-central1 \
--project=$PROJECT_ID \
--service-account=a2a-service-account \
--set-env-vars=GOOGLE_GENAI_USE_VERTEXAI=true,\
GOOGLE_CLOUD_PROJECT=$PROJECT_ID,\
GOOGLE_CLOUD_LOCATION=us-central1
gcloud iam service-accounts create a2a-service-account \
--description="SA for A2A agents on Cloud Run" \
--display-name="A2A Service Account"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:a2a-service-account@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

5. Para el orchestrator: dar roles/run.invoker al SA que lo ejecuta

Sección titulada «5. Para el orchestrator: dar roles/run.invoker al SA que lo ejecuta»

Si el orchestrator tambien corre en Cloud Run, su service account necesita poder invocar los otros 7 servicios:

gcloud run services add-iam-policy-binding platform-architect \
--member="serviceAccount:orchestrator-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/run.invoker" \
--region=us-central1

Repetir para cada uno de los 7 servicios.


10. RESUMEN DE ENDPOINTS POR AGENTE DEPLOYADO

Sección titulada «10. RESUMEN DE ENDPOINTS POR AGENTE DEPLOYADO»

Cuando un agente corre con adk api_server --a2a:

EndpointMetodoDescripcion
/list-appsGETLista agentes disponibles
/apps/{name}/users/{uid}/sessions/{sid}POSTCrear/actualizar sesion
/run_ssePOSTEjecutar agente (SSE o sync)
/a2a/{name}POSTEndpoint A2A JSON-RPC
/a2a/{name}/.well-known/agent-card.jsonGETAgent card A2A

El orchestrator usa /a2a/{name}/.well-known/agent-card.json para descubrir y /a2a/{name} para enviar tareas.


AspectoForma A (adk deploy)Forma B (gcloud + main.py)Forma C (sample A2A)Nosotros
DockerfileGenerado automaticoManual (pip)Manual (uv)Manual (uv)
Entry pointadk api_serveruvicorn main:appuv run . (main.py)adk api_server
A2AFlag --a2aa2a=True en codigoManual con a2a-sdkFlag --a2a
AgentCardLee agent.jsonLee agent.jsonEn codigo PythonLee agent.json
TaskStore A2AInMemory (fijo)InMemory (fijo)InMemory o AlloyDBInMemory (fijo)
Session storageConfigurableConfigurableManual (InMemory)Default (memory)
Web UIFlag --with_uiweb=TrueNoNo
Package managerpippipuvuv
Python version3.113.133.133.13
ControlBajoAltoMaximoMedio