Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 17 additions & 177 deletions agents/agent_engine/tutorial_deploy_your_containerised_agent.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
"\n",
"# fmt: off\n",
"PROJECT_ID = \"\" # @param {type: \"string\", placeholder: \"[your-project-id]\", isTemplate: true}\n",
"LOCATION = \"\" # @param {type: \"string\", placeholder: \"[your-project-id]\", isTemplate: true}\n",
"LOCATION = \"\" # @param {type: \"string\", placeholder: \"us-central1\", isTemplate: true}\n",
"MODEL = \"gemini-3.1-flash-lite\" # @param {type: \"string\", placeholder: \"[gemini-3.1-flash-lite]\", isTemplate: true}\n",
"MODEL_REGION = \"global\" # @param {type: \"string\", placeholder: \"[global]\", isTemplate: true}\n",
"# fmt: on\n",
Expand Down Expand Up @@ -368,11 +368,11 @@
"import json\n",
"import logging\n",
"import os\n",
"from typing import Any, Dict, Optional\n",
"import uvicorn\n",
"import vertexai\n",
"from weather_agent.agent import root_agent\n",
"from fastapi import FastAPI, encoders, responses\n",
"from pydantic import BaseModel\n",
"from fastapi import FastAPI, encoders, responses, Request\n",
"from vertexai import agent_engines\n",
"\n",
"app = FastAPI()\n",
Expand All @@ -382,19 +382,10 @@
"LOCATION = config_json[\"LOCATION\"]\n",
"MODEL_REGION = config_json[\"MODEL_REGION\"]\n",
"\n",
"class QueryRequest(BaseModel):\n",
" input: dict | None = None\n",
" class_method: str | None = None\n",
"\n",
"vertexai.init(\n",
" project=PROJECT_ID,\n",
" location=MODEL_REGION,\n",
")\n",
"\n",
"vertexai.init(project=PROJECT_ID, location=MODEL_REGION)\n",
"adk_app = agent_engines.AdkApp(agent=root_agent)\n",
"\n",
"def _encode_chunk_to_json(chunk):\n",
" \"\"\"Encodes a chunk to a JSON string with a newline.\"\"\"\n",
" try:\n",
" json_chunk = encoders.jsonable_encoder(chunk)\n",
" return json.dumps(json_chunk) + \"\\n\"\n",
Expand All @@ -416,25 +407,26 @@
" return invocation_callable(**invocation_payload)\n",
"\n",
"@app.post(\"/api/reasoning_engine\")\n",
"async def query(request: QueryRequest) -> responses.JSONResponse:\n",
" method = getattr(adk_app, request.class_method)\n",
" output = await _invoke_callable_or_raise(method, request.input or {})\n",
"\n",
"async def query(request: Request) -> responses.JSONResponse:\n",
" request_json = await request.json()\n",
" class_method = request_json.get(\"class_method\")\n",
" input_val = request_json.get(\"input\")\n",
" method = getattr(adk_app, class_method)\n",
" output = await _invoke_callable_or_raise(method, input_val or {})\n",
" try:\n",
" json_serialized_content = encoders.jsonable_encoder({\"output\": output})\n",
" except ValueError as encoding_error:\n",
" logging.exception(\n",
" \"FastAPI could not JSON-encode the response from invocation method\"\n",
" \" %s. Error: %s. Invocation method's original response: %r\",\n",
" request.class_method, encoding_error, output,\n",
" )\n",
" logging.exception(\"Failed to encode response\")\n",
" raise encoding_error\n",
" return responses.JSONResponse(content=json_serialized_content)\n",
"\n",
"@app.post(\"/api/stream_reasoning_engine\")\n",
"async def stream_query(request: QueryRequest) -> responses.StreamingResponse:\n",
" method = getattr(adk_app, request.class_method)\n",
" output = await _invoke_callable_or_raise(method, request.input or {})\n",
"async def stream_query(request: Request) -> responses.StreamingResponse:\n",
" request_json = await request.json()\n",
" class_method = request_json.get(\"class_method\")\n",
" input_val = request_json.get(\"input\")\n",
" method = getattr(adk_app, class_method)\n",
" output = await _invoke_callable_or_raise(method, input_val or {})\n",
" return responses.StreamingResponse(\n",
" content=json_generator(output),\n",
" media_type=\"application/json\",\n",
Expand Down Expand Up @@ -685,158 +677,6 @@
" ./weather-agent-code"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "eZDioJLZLUBw"
},
"source": [
"### Tenant Service Account - Identification & Permissions"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7DtrAeZeQt8N"
},
"source": [
"A tenant service account (or tenant service agent) is a managed service account created and maintained by Google to perform actions on your behalf within a tenant project. It allows managed Google services to interact with your resources securely without requiring manual user credentials.<br>\n",
"\n",
"In this section, let's identify the tenant service account and grant the necessary permissions needed to access the container image from the Artifact Registry for the containerized agent deployment.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3Yjb7Pl_Nhei"
},
"outputs": [],
"source": [
"!mkdir -p tenant_sa_setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5k5PTRHENoDK"
},
"outputs": [],
"source": [
"%%writefile tenant_sa_setup/requirements.txt\n",
"google-cloud-aiplatform[agent_engines]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "IynotT-8NscP"
},
"outputs": [],
"source": [
"%%writefile tenant_sa_setup/my_agent.py\n",
"class MetadataAgent:\n",
"\n",
" def query(self):\n",
" import requests\n",
" url = \"http://metadata.google.internal/computeMetadata/v1/project/numeric-project-id\"\n",
" try:\n",
" response = requests.get(url, headers={\"Metadata-Flavor\": \"Google\"})\n",
" response.raise_for_status()\n",
" return f\"service-{response.text}@serverless-robot-prod.iam.gserviceaccount.com\"\n",
" except Exception:\n",
" return None\n",
"\n",
"root_agent = MetadataAgent()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c8lSh3sTN6UZ"
},
"outputs": [],
"source": [
"tenant_sa_agent = client.agent_engines.create(\n",
" config={\n",
" \"source_packages\": [\"tenant_sa_setup\"],\n",
" \"entrypoint_module\": \"tenant_sa_setup.my_agent\",\n",
" \"entrypoint_object\": \"root_agent\",\n",
" \"requirements_file\": \"tenant_sa_setup/requirements.txt\",\n",
" \"class_methods\": [{\"api_mode\": \"\", \"name\": \"query\"}],\n",
" },\n",
")\n",
"\n",
"tenant_sa_agent"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e38a4eaa6859"
},
"source": [
"Let's copy the value of the tenant service account indicated by the cell containing `tenant_sa_agent.query()` into the variable `TENANT_SERVICE_ACCOUNT` present in the cell below."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "PXYsywieN9ou"
},
"outputs": [],
"source": [
"# Your tenant service account in the format \"service-{number}@serverless-robot-prod.iam.gserviceaccount.com\"\n",
"TENANT_SERVICE_ACCOUNT = tenant_sa_agent.query()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "pBZqAEmAN_jx"
},
"outputs": [],
"source": [
"# Delete the agent.\n",
"tenant_sa_agent.delete(force=True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mnSbrHt4UEZh"
},
"source": [
"Grant the Artifact Registry reader role to the Tenant Service Account."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jdRssoqTOSNL"
},
"outputs": [],
"source": [
"!gcloud projects add-iam-policy-binding $PROJECT_NUMBER \\\n",
" --member=\"serviceAccount:$TENANT_SERVICE_ACCOUNT\" \\\n",
" --role=\"roles/artifactregistry.reader\" --condition=None"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5FT8LNYTqBos"
},
"source": [
"❗ Additionally, if your Organization enforces the `iam.allowedPolicyMemberDomains` organization policy constraint, you need to allow the Google's customer domain before adding the permissions to the Tenant Service account. For detailed instructions, check [Tenant Service Account Setup Documentation](https://docs.cloud.google.com/gemini-enterprise-agent-platform/build/runtime/setup#tenant_service_account)"
]
},
{
"cell_type": "markdown",
"metadata": {
Expand Down
Loading