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 automaticamenteDockerfile generado (template real del codigo fuente):
FROM python:3.11-slimWORKDIR /app
RUN adduser --disabled-password --gecos "" myuserUSER myuserENV PATH="/home/myuser/.local/bin:$PATH"
ENV GOOGLE_GENAI_USE_VERTEXAI=1ENV 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=1GiFlags 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_urise fuerza amemory://artifact_service_urise fuerza amemory://
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 extraFORMA 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 Dockerfilemain.py oficial (de adk.dev/deploy/cloud-run/):
import osimport uvicornfrom fastapi import FastAPIfrom 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-slimWORKDIR /app
COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
RUN adduser --disabled-password --gecos "" myuser && \ chown -R myuser:myuser /appCOPY . .USER myuserENV 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 .dockerignoreDockerfile:
FROM python:3.13-slimCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/EXPOSE 8080WORKDIR /appCOPY . ./RUN uv syncENTRYPOINT ["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
RunnerconInMemorySessionService,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
Firma completa:
Sección titulada «Firma completa:»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:Session Storage URIs soportados:
Sección titulada «Session Storage URIs soportados:»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
Como monta A2A (lineas 550-622):
Sección titulada «Como monta A2A (lineas 550-622):»Cuando a2a=True:
- Itera sobre TODAS las subcarpetas de
agents_dir - Solo procesa carpetas que tienen un archivo
agent.json - Para cada carpeta con
agent.json:- Lee el
agent.jsony lo parsea comoAgentCard - Crea un
A2aAgentExecutorque usa el runner de ADK - Crea
DefaultRequestHandlerconInMemoryTaskStorecompartido - Crea
A2AStarletteApplication - Monta rutas en:
/a2a/{app_name}(endpoint JSON-RPC)/a2a/{app_name}/.well-known/agent-card.json(agent card)
- Lee el
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().
Como monta la Web UI:
Sección titulada «Como monta la Web UI:»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)
Rutas del API server (sin web, sin A2A):
Sección titulada «Rutas del API server (sin web, sin A2A):»El servidor base (AdkWebServer) expone:
GET /list-apps-> lista agentes disponiblesPOST /apps/{app_name}/users/{user_id}/sessions/{session_id}-> crear/actualizar sesionPOST /run_sse-> ejecutar agente (streaming SSE o no)- Otras rutas de evaluacion y gestion
3. AUTH: API Key vs Vertex AI
Sección titulada «3. AUTH: API Key vs Vertex AI»Vertex AI (recomendado para Cloud Run):
Sección titulada «Vertex AI (recomendado para Cloud Run):»GOOGLE_GENAI_USE_VERTEXAI=True (o =1)GOOGLE_CLOUD_PROJECT=project-idGOOGLE_CLOUD_LOCATION=us-central1- No requiere API key
- Usa la identidad del service account de Cloud Run
- Requiere rol
roles/aiplatform.useren el service account
API Key (Google AI Studio):
Sección titulada «API Key (Google AI Studio):»GOOGLE_GENAI_USE_VERTEXAI=FALSEGOOGLE_API_KEY=tu-api-keyGOOGLE_CLOUD_PROJECT=project-id # aun requerido en algunos contextosGOOGLE_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
LiteLLM (sample A2A):
Sección titulada «LiteLLM (sample A2A):»El sample oficial usa LiteLlm(model='gemini/gemini-2.5-flash-lite'), lo que permite usar multiples proveedores. Esto requiere litellm como dependencia.
4. Session Storage
Sección titulada «4. Session Storage»| Opcion | URI | Persistencia | Uso recomendado |
|---|---|---|---|
| In-Memory | memory:// | Se pierde en restart | Prototipos, agentes stateless |
| SQLite local | None + use_local_storage=True | Persiste en disco | Desarrollo local |
| SQLite async | sqlite+aiosqlite:///path.db | Persiste en disco | Cloud Run con volumen |
| PostgreSQL | postgresql+asyncpg://... | Persiste en DB | Produccion |
| MySQL | mysql+aiomysql://... | Persiste en DB | Produccion |
| AlloyDB | Via conector Google Cloud | Persiste en DB | Produccion 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.
5. IAM y Service Accounts
Sección titulada «5. IAM y Service Accounts»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 para el usuario/SA que deploya:
Sección titulada «Roles para el usuario/SA que deploya:»roles/run.sourceDeveloperroles/resourcemanager.projectIamAdminroles/secretmanager.adminroles/iam.serviceAccountUser
Auth entre servicios (orchestrator -> agentes):
Sección titulada «Auth entre servicios (orchestrator -> agentes):»--no-allow-unauthenticateden cada Cloud Run service- El service account del caller necesita
roles/run.invoker - Token:
gcloud auth print-identity-tokeno identity token via metadata server
Crear service account:
Sección titulada «Crear service account:»gcloud iam service-accounts create a2a-service-account \ --description="service account for a2a cloud run service" \ --display-name="A2A cloud run service account"6. Testing de agentes deployados
Sección titulada «6. Testing de agentes deployados»Via curl (API de ADK):
Sección titulada «Via curl (API de ADK):»export APP_URL="https://tu-servicio.run.app"export TOKEN=$(gcloud auth print-identity-token)
# Listar appscurl -X GET -H "Authorization: Bearer $TOKEN" $APP_URL/list-apps
# Crear sesioncurl -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 agentecurl -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 }'Via curl (A2A):
Sección titulada «Via curl (A2A):»# Verificar agent cardcurl -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"}] } } }'7. COMPARACION CON NUESTRO PROYECTO ACTUAL
Sección titulada «7. COMPARACION CON NUESTRO PROYECTO ACTUAL»Lo que tenemos:
Sección titulada «Lo que tenemos:»Dockerfile (platform_architect y otros):
FROM python:3.13-slimCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/EXPOSE 8080WORKDIR /appCOPY pyproject.toml uv.lock ./RUN uv sync --no-dev --frozenCOPY . /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
BIEN (alineado con fuentes oficiales):
Sección titulada «BIEN (alineado con fuentes oficiales):»- Base image:
python:3.13-slim— correcto (Google Cloud docs usa esta) - uv como package manager: correcto (Google Cloud docs lo usa)
- Port 8080: correcto (Cloud Run default)
- agent.json existe: requerido para A2A via
get_fast_api_app() --no-allow-unauthenticated: mencionado en CLAUDE.md del proyecto, correcto__init__.pyconfrom . import agent: correcto- Orchestrator con RemoteA2aAgent: approach valido
- IAM auth en orchestrator: correcto, pattern oficial
PROBLEMAS / DIFERENCIAS:
Sección titulada «PROBLEMAS / DIFERENCIAS:»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.
PROBLEMA 3: __main__.py NO pasa a2a=True
Sección titulada «PROBLEMA 3: __main__.py NO pasa a2a=True»app = get_fast_api_app(agents_dir=agents_dir, web=False)# FALTA: a2a=TrueSi 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.
PROBLEMA 5: url en agent.json es hardcoded
Sección titulada «PROBLEMA 5: url en agent.json es hardcoded»"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.
8. CONTRADICCIONES ENTRE FUENTES
Sección titulada «8. CONTRADICCIONES ENTRE FUENTES»Contradiccion 1: Python version en Dockerfile
Sección titulada «Contradiccion 1: Python version en Dockerfile»adk deploy cloud_rungenera: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
Contradiccion 2: Package manager
Sección titulada «Contradiccion 2: Package manager»adk deploy cloud_rungenera:pip install google-adk=={version}+pip install -r requirements.txt- Google Cloud docs:
uvcon ENTRYPOINTuv run . - adk.dev docs (gcloud method):
pip install - Sample A2A:
uvcon ENTRYPOINTuv run .
Contradiccion 3: Port default
Sección titulada «Contradiccion 3: Port default»adk api_serverdefault: 8000- Cloud Run default: 8080
- Nuestro Dockerfile: 8080 (correcto para Cloud Run)
adk deploy cloud_rundefault: 8000 (necesita override con--port)
Contradiccion 4: A2A approach
Sección titulada «Contradiccion 4: A2A approach»- ADK
get_fast_api_app(a2a=True)leeagent.jsonde cada subcarpeta y monta en/a2a/{name} - Sample A2A oficial NO usa
get_fast_api_app(), construye todo cona2a-sdk+ Starlette, monta rutas en raiz - Son approaches completamente diferentes. El primero es mas simple, el segundo da mas control.
Contradiccion 5: AgentCard
Sección titulada «Contradiccion 5: AgentCard»- ADK approach: lee de archivo
agent.json - Sample A2A: construye
AgentCarden codigo Python conAPP_URLde env var - Ninguna fuente combina ambos approaches
9. EL APPROACH CORRECTO PARA NUESTRO CASO
Sección titulada «9. EL APPROACH CORRECTO PARA NUESTRO CASO»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.
Cambios recomendados:
Sección titulada «Cambios recomendados:»1. Limpiar el __main__.py o eliminarlo
Sección titulada «1. Limpiar el __main__.py o eliminarlo»Opciones:
- Opcion A: Eliminarlo. Dejar solo el Dockerfile con
adk api_server --a2a. Simple. - Opcion B: Mantenerlo pero con
a2a=Truepara poder ejecutar localmente conpython -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)
3. Agregar env vars de Vertex AI al deploy
Sección titulada «3. Agregar env vars de Vertex AI al deploy»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-central14. Crear service account dedicado
Sección titulada «4. Crear service account dedicado»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-central1Repetir 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:
| Endpoint | Metodo | Descripcion |
|---|---|---|
/list-apps | GET | Lista agentes disponibles |
/apps/{name}/users/{uid}/sessions/{sid} | POST | Crear/actualizar sesion |
/run_sse | POST | Ejecutar agente (SSE o sync) |
/a2a/{name} | POST | Endpoint A2A JSON-RPC |
/a2a/{name}/.well-known/agent-card.json | GET | Agent card A2A |
El orchestrator usa /a2a/{name}/.well-known/agent-card.json para descubrir y /a2a/{name} para enviar tareas.
11. TABLA COMPARATIVA FINAL
Sección titulada «11. TABLA COMPARATIVA FINAL»| Aspecto | Forma A (adk deploy) | Forma B (gcloud + main.py) | Forma C (sample A2A) | Nosotros |
|---|---|---|---|---|
| Dockerfile | Generado automatico | Manual (pip) | Manual (uv) | Manual (uv) |
| Entry point | adk api_server | uvicorn main:app | uv run . (main.py) | adk api_server |
| A2A | Flag --a2a | a2a=True en codigo | Manual con a2a-sdk | Flag --a2a |
| AgentCard | Lee agent.json | Lee agent.json | En codigo Python | Lee agent.json |
| TaskStore A2A | InMemory (fijo) | InMemory (fijo) | InMemory o AlloyDB | InMemory (fijo) |
| Session storage | Configurable | Configurable | Manual (InMemory) | Default (memory) |
| Web UI | Flag --with_ui | web=True | No | No |
| Package manager | pip | pip | uv | uv |
| Python version | 3.11 | 3.13 | 3.13 | 3.13 |
| Control | Bajo | Alto | Maximo | Medio |