diff --git a/README.md b/README.md index 6fdb7801..753fbe96 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Teams are not left on their own to solve the challenges. Coaches work with each > In this hack, we'll step into the role of a creative director at a cutting-edge ad agency. Our mission is to create a compelling 20-30 second video advertisement for a revolutionary new product using Google Cloud's generative AI tools. - [Introduction to Agents with ADK](./hacks/adk-intro/README.md) > **UPDATED FOR ADK 2** This is an introduction to Agentic AI using Agent Development Kit (ADK) framework. We'll be introducing various ADK concepts step-by-step, starting with a single agent and progressively building a tool-using, collaborative multi-agent system. +- [Custom ADK Agents and Gemini Enterprise app](./hacks/adk-ge/README.md) + > In this hack, you will leverage the Agent Development Kit (ADK) to develop a custom AI agent, and deliver it to business users through the Gemini Enterprise. During the development of the agent we'll build custom tools, use BigQuery MCP server, deploy our agent to Agent Platform, integrate it with Gemini Enterprise app and experience how to build custom visualizations through A2UI. - [Modernizing Classic Data Warehousing with BigQuery](./hacks/bq-dwh/README.md) > In this hack we'll implement a classic data warehouse using modern tools, such as Cloud Storage, BigQuery, Dataform and Looker Studio. We'll start with a modified version of the well known AdventureWorks OLTP database, and we'll implement a dimensional model to report on business questions using a BI visualization tool. - [Open Lakehouse with Apache Iceberg](./hacks/bq-olh/README.md) diff --git a/hacks/adk-ge/QL_OWNER b/hacks/adk-ge/QL_OWNER new file mode 100644 index 00000000..68724db2 --- /dev/null +++ b/hacks/adk-ge/QL_OWNER @@ -0,0 +1,3 @@ +meken@google.com +# Collaborators +ginof@google.com diff --git a/hacks/adk-ge/README.md b/hacks/adk-ge/README.md new file mode 100644 index 00000000..cbfd5b5d --- /dev/null +++ b/hacks/adk-ge/README.md @@ -0,0 +1,233 @@ +# Custom ADK Agents and Gemini Enterprise app + +## Introduction + +In this hack, you will step into the shoes of a developer tasked with building an agentic solution for Sara, a Product Owner at a retail bank. Sara needs to track and analyze performance metrics for a newly launched banking product. Instead of relying on static dashboards or waiting for manual database reports, she wants a conversational interface that can securely interact with live banking data in real-time. + +To solve this end-to-end business problem, you will leverage the *Agent Development Kit (ADK)* to build a custom AI agent, run and test it securely on Google Cloud's enterprise-grade infrastructure, and deliver it directly to Sara inside her existing *Gemini Enterprise* workspace. + +![Architecture Overview](./images/ge-adk-arch.png) + +## Learning Objectives + +1. Set up and test an ADK agent locally. +2. Integrate BigQuery using a secure *MCP (Model Context Protocol) server* for natural language database querying. +3. Deploy and host agents securely in Google Cloud using *Agent Runtime* with managed Agent identities. +4. Integration with Gemini Enterprise app. +5. Implement A2UI for data visualization. + +## Challenges + +- Challenge 1: Getting Started with ADK + - Clone skeleton code, run it locally in Cloud Shell. +- Challenge 2: What's the date? + - Implement a basic custom function tool in Python to fetch the current date. +- Challenge 3: Talking to BigQuery + - Connect the Google-managed *BigQuery MCP Server* to query banking data using natural language. +- Challenge 4: Agent Runtime + - Deploy your agent to Agent Runtime and configure secure identity and access. +- Challenge 5: Gemini Enterprise Integration + - Make the agent A2A compatible and register it as a custom agent in the Gemini Enterprise app. +- Challenge 6: Visualizing Data (A2UI) + - Generate and visualize charts directly in the chat interface using native A2UI. + +## Prerequisites + +- Access to a Google Cloud Project with the required APIs enabled. +- Cloud Shell environment. +- Familiarity with Python and basic SQL. + +## Challenge 1: Getting Started with ADK + +### Introduction + +Before building advanced features, you need to understand the foundation. The [Agent Development Kit (ADK)](https://adk.dev/) is an open-source framework designed to help developers build, test, and run production-ready AI agents. + +In this challenge, you will configure your local development environment by running an ADK agent inside Cloud Shell before we scale our architecture to the cloud. + +### Description + +We've already prepared a code base for you and put it in a Git repository (your coach will provide you the link). Clone that on Cloud Shell, create a virtual environment, activate it and install the Python dependencies from the `requirements.txt`. Configure the agent to use *user credentials* for local development through Agent Platform (formerly known as Vertex AI). + +Once everything is set up, run `adk web` and make sure that the agent responds back. + +### Success Criteria + +- The Git repository has been cloned to Cloud Shell. +- You get no errors when you greet the agent from the `adk web` UI. +- No code was modified. + +### Tips + +- The utility `adk web` is part of ADK CLI that gets installed when you install the dependencies in your virtual environment. +- Newer versions of the `adk web` feature require you to set the `--allow_origins` to `"*"` to prevent CORS errors. + +### Learning Resources + +- [Cloud Shell](https://cloud.google.com/shell/docs/launching-cloud-shell) +- [Cloud Shell Editor](https://cloud.google.com/shell/docs/launching-cloud-shell-editor) +- [Previewing web apps](https://cloud.google.com/shell/docs/using-web-preview) +- [Creating and activating Python virtual environments](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments) +- [Setting up authentication for ADK](https://adk.dev/agents/models/google-gemini/#google-cloud-agent-platform) +- [ADK CLI](https://adk.dev/api-reference/cli/#adk) + +## Challenge 2: What's the date? + +### Introduction + +Large Language Models (LLMs) are incredibly capable, but they are limited to the data they were trained on, meaning they don't have access to real-time information or the ability to execute actions. To overcome this, agents use *Tools*. + +Tools are basically external interfaces (functions, APIs, or scripts) that the agent can invoke dynamically when it needs real-time context. To see this in action, you will write a custom Python function to get the current date, allowing the agent to handle references such as last month, last quarter based on current date. + +### Description + +At the moment if our users would ask our agent the current date, it would emit a date from the past. In order to make our agent aware of the current date, we'll introduce a new *function tool* that dynamically calculates and returns the current date. + +Create a new Python function `get_current_date` that returns the current date in `YYYY-MM-DD` format. Add a [docstring](https://peps.python.org/pep-0257/#one-line-docstrings) to that function explaining what it returns and in which format. Make that function available as a tool to the agent. + +Commit and push your changes to the Git repository when you're done. + +### Success Criteria + +- When you ask the agent what the current date is, it returns today's date (it's okay if the UI shows it formatted differently, but the tool output should be the correct format). +- All the changes are committed and pushed to the Git repository. + +### Tips + +- The ADK web UI lets you inspect every step, you can hover over the steps and see the details. + +### Learning Resources + +- [Example function tool in ADK](https://adk.dev/tools-custom/function-tools/#example) + +## Challenge 3: Talking to BigQuery + +### Introduction + +Sara's goal is to analyze how the new banking product is performing, which requires querying a company data store. Since she is a product owner, not a database administrator, she wants to use natural language instead of SQL. + +Writing custom code to map user queries to database schemas can be incredibly tedious. Instead, we'll let our model generate the SQL queries and use a tool to access the underlying data source and run queries. + +We could build our own tool as we did in the previous challenge, but there's also a plethora of tools available built by others. This is where the *Model Context Protocol (MCP)* plays a role; it offers a standardized abstraction layer for tools so that any agent can use them. + +For this challenge we'll use the the Google-managed *BigQuery MCP Server* to access the company data source with customer data. + +### Description + +Integrate the *BigQuery MCP Server* into your ADK agent. Once the agent is equipped with the BigQuery MCP tools, and can run SQL queries successfully, commit and push your changes. + +### Success Criteria + +- Ask the agent: *How many accounts were created in the last quarter?*. This should successfully retrieve the result from BigQuery (around 150, exact numbers might be different to randomly generated data). +- Verify the agent utilizes the MCP tools to inspect and query the database under the hood. +- All the changes are committed and pushed to the Git repository. + +### Tips + +- Keep in mind that this MCP server is a remote server available through Streamable HTTP. + +### Learning Resources + +- [MCP Toolset in ADK](https://adk.dev/tools-custom/mcp-tools) +- [BigQuery MCP Server Reference](https://docs.cloud.google.com/bigquery/docs/reference/mcp) + +## Challenge 4: Agent Runtime + +### Introduction + +Running agents locally with personal user credentials is great for prototyping, but enterprise-grade business applications require a secure, reliable, and scalable hosting environment. + +In this challenge, you will move your agent off your local machine and deploy it to Google Cloud's *Agent Runtime* (part of the Gemini Enterprise Agent Platform). You will configure a dedicated *Agent Identity*, a managed principal, and grant it the precise, minimum IAM permissions required to access BigQuery. This ensures your agent runs securely under its own cloud identity without exposing personal user credentials. + +### Description + +Deploy your agent to Agent Runtime, using the ADK CLI. Make sure that the Agent Runtime uses Agent Identity. + +Grant the required permissions to the identity of the Agent so that it can read data from and run jobs on BigQuery, and can use the BigQuery MCP tools. + +Once the agent on Agent Runtime can successfully answer questions that require accessing BigQuery, commit and push your changes. + +### Success Criteria + +- Ask the agent on Agent Runtime: *How many customers do we have in total?*. This should run a query on the `customers` table and return `1000`. +- All the changes are committed and pushed to the Git repository. + +### Tips + +- Agent Runtime used to be called Agent Engine, some ADK CLI options still use that terminology +- If you need to redeploy your agent, provide the `--agent_engine_id` option so that it *replaces* your deployment (and doesn't create a new agent with a new identity) +- You can use the *Playground* section in Agent Runtime interface to have a similar experience as locally testing through ADK web UI. +- Easiest option to configure the Agent Identity is to through the agent config file `.agent_engine_config.json`. + +### Learning Resources + +- [Deploying with ADK CLI](https://adk.dev/api-reference/cli/#adk-deploy) +- [Creating an Agent with Agent Identity](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/agent-identity#create-agent-identity) + +## Challenge 5: Gemini Enterprise Integration + +### Introduction + +Our agent is only useful if business users can easily access it. Instead of forcing Sara to open a terminal or use a developer-focused console, we want to deliver this agent directly into the communication hub she uses every day: the *Gemini Enterprise* app. + +### Description + +First create a new Gemini Enterprise app instance (use the 30-day trial option) and choose Google Identity when setting up identity. + +Add our agent to the Gemini Enterprise app through the *Custom agent via Agent Runtime* option, and verify that the agent is available and functional from Gemini Enterprise app. + +### Success Criteria + +- In the Gemini Enterprise app, ask the agent: "What was the adoption rate of Advantage Plus accounts last quarter?" and verify that the agent returns something like `90%` (exact numbers might vary due to random data). + +### Tips + +- You can see all the sessions and their details in Agent Runtime UI under the *Sessions* section, including the ones started from Gemini Enterprise app. + +### Learning Resources + +- [Creating a Gemini Enterprise app](https://docs.cloud.google.com/gemini/enterprise/docs/create-app) +- [Registering a custom agent in Gemini Enterprise app](https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent) + +## Challenge 6: Visualizing Data (A2UI) + +### Introduction + +While reading numbers or CSV tables in a chat window is helpful, business trends are best understood visually. Sara wants to see product performance represented in clean, interactive charts. + +Traditionally, displaying visualizations from a chat agent required building bespoke web applications or executing unsafe client-side scripts. The *Agent-to-User Interface (A2UI)* project solves this by defining an open, secure, and declarative standard for rendering native interface components. + +### Description + +In principle our agent could generate A2UI specs (the declarative model for a UI) if we prompted it properly. However, we're going to keep things simple, and we'll put the data in A2UI format ourselves using the ADK callback functionality. + +Update the agent instructions to return data in csv format surrounded with `` tags *only* when the user asks for a bar chart. Here's an example: + +```text + +category,amount +A, 5 +B, 20 +C, 35 +D, 15 + +``` + +We've already provided a function that can detect these blocks in the model response and replace them with A2UI components. Go ahead and make sure that this function is called after the model is run. + +Verify that a bar chart is generated on the Gemini Enterprise app when the user request a bar chart. + +Finally commit and push your changes. + +### Tips + +- If you update your existing deployment, Gemini Enterprise app will use the latest version of your agent. But if you create another deployment, you'll need to grant the required roles to run queries as you'll have a new Agent Identity and you'll have to add the new Agent to the Gemini Enterprise app. + +### Success Criteria + +- Ask the agent: "Generate a bar chart showing the number of customers for each account type." and verify that a bar chart is rendered in the interface. +- All the changes are committed and pushed to the Git repository. + +### Learning Resources + +- [ADK Callbacks](https://adk.dev/callbacks/) diff --git a/hacks/adk-ge/artifacts/Makefile b/hacks/adk-ge/artifacts/Makefile new file mode 100644 index 00000000..60ef7134 --- /dev/null +++ b/hacks/adk-ge/artifacts/Makefile @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +TARGET=ghacks-setup.zip +OBJS=*.tf runtime.yaml *.tftpl *.sql + +$(TARGET): $(OBJS) + zip -r $(TARGET) $(OBJS) + +clean: + rm -f $(TARGET) diff --git a/hacks/adk-ge/artifacts/generate_data.sql b/hacks/adk-ge/artifacts/generate_data.sql new file mode 100644 index 00000000..09d9aa96 --- /dev/null +++ b/hacks/adk-ge/artifacts/generate_data.sql @@ -0,0 +1,276 @@ +-- BigQuery SQL Script to generate mock retail banking data. + +-- Set up previous quarter start and end dates dynamically +DECLARE today DATE DEFAULT CURRENT_DATE(); +DECLARE prev_q_start DATE; +DECLARE prev_q_end DATE; + +SET prev_q_start = DATE_TRUNC(DATE_SUB(today, INTERVAL 3 MONTH), QUARTER); +SET prev_q_end = LAST_DAY(DATE_SUB(today, INTERVAL 3 MONTH), QUARTER); + +-- ========================================== +-- 1. Create and populate the CUSTOMERS table +-- ========================================== +CREATE OR REPLACE TABLE `${dataset_id}.customers` +OPTIONS( + description = "Retail banking customer profiles, containing demographic information and join dates." +) AS +WITH raw_data AS ( + SELECT + id AS customer_id, + -- Deterministic pseudorandom values using FARM_FINGERPRINT for reproducibility + ABS(MOD(FARM_FINGERPRINT(CAST(id AS STRING)), 100)) AS rand_pct, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_age')), 100)) AS rand_age, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_gender')), 100)) AS rand_gender, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_region')), 4)) AS rand_region, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_join')), 1621)) AS rand_join_days + FROM UNNEST(GENERATE_ARRAY(1, 1000)) AS id +), +customers_pre AS ( + SELECT + customer_id, + CASE + WHEN rand_pct < 40 THEN 18 + MOD(rand_age, 18) -- young: 18 to 35 + WHEN rand_pct < 80 THEN 36 + MOD(rand_age, 25) -- middle: 36 to 60 + ELSE 61 + MOD(rand_age, 25) -- senior: 61 to 85 + END AS age, + CASE + WHEN rand_gender < 48 THEN 'M' + WHEN rand_gender < 96 THEN 'F' + ELSE 'U' + END AS gender, + CASE rand_region + WHEN 0 THEN 'Amsterdam' + WHEN 1 THEN 'Utrecht' + WHEN 2 THEN 'Rotterdam' + ELSE 'Groningen' + END AS region, + -- Join date in last 5 years: today - (180 + rand_join_days) + DATE_SUB(today, INTERVAL (180 + rand_join_days) DAY) AS join_date + FROM raw_data +) +SELECT customer_id, age, gender, region, join_date FROM customers_pre; + + +-- ============================================== +-- 2. Create and populate the CORE_ACCOUNTS_V2 table +-- ============================================== +CREATE OR REPLACE TABLE `${dataset_id}.core_accounts_v2` +OPTIONS( + description = "Core deposit accounts representing products held by customers, with balances and statuses." +) AS + +-- PART 1: First 1000 accounts (one for each customer) +WITH part1_raw AS ( + SELECT + id AS account_id, + id AS customer_id, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_actype')), 100)) AS rand_actype, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_opendate')), 31)) AS rand_open_days, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_balance')), 1000000)) / 1000000.0 AS rand_balance, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_status')), 100)) AS rand_status, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_last_txn')), 101)) AS rand_txn_days + FROM UNNEST(GENERATE_ARRAY(1, 1000)) AS id +), +part1 AS ( + SELECT + p.account_id, + p.customer_id, + c.join_date, + -- If customer joined in previous quarter, force AP. Else, AP (10%), BS (40%), SC (50%) + CASE + WHEN c.join_date BETWEEN prev_q_start AND prev_q_end THEN 'AP' + WHEN p.rand_actype < 10 THEN 'AP' + WHEN p.rand_actype < 50 THEN 'BS' + ELSE 'SC' + END AS account_type, + p.rand_open_days, + p.rand_balance, + p.rand_status, + p.rand_txn_days + FROM part1_raw p + JOIN `${dataset_id}.customers` c ON p.customer_id = c.customer_id +), +part1_processed AS ( + SELECT + account_id, + customer_id, + account_type, + -- open_date: join_date + rand_open_days + DATE_ADD(join_date, INTERVAL rand_open_days DAY) AS open_date, + -- balance: AP [5000, 150000], BS [100, 20000], SC [50, 10000] + ROUND( + CASE + WHEN account_type = 'AP' THEN 5000 + (150000 - 5000) * rand_balance + WHEN account_type = 'BS' THEN 100 + (20000 - 100) * rand_balance + ELSE 50 + (10000 - 50) * rand_balance + END, + 2 + ) AS balance, + CASE WHEN rand_status < 92 THEN 'Active' ELSE 'Closed' END AS status, + rand_txn_days + FROM part1 +), +part1_final AS ( + SELECT + account_id, + customer_id, + account_type, + CAST(balance AS FLOAT64) AS balance, + open_date, + status, + CASE + WHEN status = 'Active' THEN + LEAST(DATE_ADD(open_date, INTERVAL rand_txn_days DAY), today) + ELSE NULL + END AS last_transaction_date + FROM part1_processed +), + +-- PART 2: Remaining 500 accounts +part2_raw AS ( + SELECT + id AS account_id, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_cust')), 1000)) + 1 AS customer_id, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_actype')), 100)) AS rand_actype, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_ap_pq')), 100)) AS rand_ap_pq, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_pq_days')), DATE_DIFF(prev_q_end, prev_q_start, DAY) + 1)) AS rand_pq_days, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_norm_days')), 501)) AS rand_norm_days, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_future_cap')), 31)) AS rand_future_cap, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_balance')), 1000000)) / 1000000.0 AS rand_balance, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_status')), 100)) AS rand_status, + ABS(MOD(FARM_FINGERPRINT(CONCAT(CAST(id AS STRING), '_last_txn')), 101)) AS rand_txn_days + FROM UNNEST(GENERATE_ARRAY(1001, 1500)) AS id +), +part2 AS ( + SELECT + p.account_id, + p.customer_id, + c.join_date, + CASE + WHEN p.rand_actype < 40 THEN 'AP' + WHEN p.rand_actype < 70 THEN 'BS' + ELSE 'SC' + END AS account_type, + p.rand_ap_pq, + p.rand_pq_days, + p.rand_norm_days, + p.rand_future_cap, + p.rand_balance, + p.rand_status, + p.rand_txn_days + FROM part2_raw p + JOIN `${dataset_id}.customers` c ON p.customer_id = c.customer_id +), +part2_processed AS ( + SELECT + account_id, + customer_id, + account_type, + CASE + WHEN + (CASE + WHEN account_type = 'AP' AND rand_ap_pq < 60 THEN + DATE_ADD(prev_q_start, INTERVAL rand_pq_days DAY) + ELSE + DATE_ADD(join_date, INTERVAL rand_norm_days DAY) + END) > today + THEN + DATE_SUB(today, INTERVAL rand_future_cap DAY) + ELSE + (CASE + WHEN account_type = 'AP' AND rand_ap_pq < 60 THEN + DATE_ADD(prev_q_start, INTERVAL rand_pq_days DAY) + ELSE + DATE_ADD(join_date, INTERVAL rand_norm_days DAY) + END) + END AS open_date, + ROUND( + CASE + WHEN account_type = 'AP' THEN 5000 + (150000 - 5000) * rand_balance + WHEN account_type = 'BS' THEN 100 + (20000 - 100) * rand_balance + ELSE 50 + (10000 - 50) * rand_balance + END, + 2 + ) AS balance, + CASE WHEN rand_status < 90 THEN 'Active' ELSE 'Closed' END AS status, + rand_txn_days + FROM part2 +), +part2_final AS ( + SELECT + account_id, + customer_id, + account_type, + CAST(balance AS FLOAT64) AS balance, + open_date, + status, + CASE + WHEN status = 'Active' THEN + LEAST(DATE_ADD(open_date, INTERVAL rand_txn_days DAY), today) + ELSE NULL + END AS last_transaction_date + FROM part2_processed +), + +-- Combine both parts +combined AS ( + SELECT * FROM part1_final + UNION ALL + SELECT * FROM part2_final +) +SELECT + account_id, + customer_id, + account_type, + balance, + open_date, + status, + last_transaction_date +FROM combined +ORDER BY account_id; + + +-- ============================================================================== +-- 3. Set COLUMN descriptions for the created tables +-- ============================================================================== + +-- Column descriptions for CUSTOMERS table +ALTER TABLE `${dataset_id}.customers` + ALTER COLUMN customer_id SET OPTIONS(description="Unique identifier for the customer."); + +ALTER TABLE `${dataset_id}.customers` + ALTER COLUMN age SET OPTIONS(description="Customer age in years."); + +ALTER TABLE `${dataset_id}.customers` + ALTER COLUMN gender SET OPTIONS(description="Customer gender code (M = Male, F = Female, U = Unknown)."); + +ALTER TABLE `${dataset_id}.customers` + ALTER COLUMN region SET OPTIONS(description="Geographic region of the customer's branch location."); + +ALTER TABLE `${dataset_id}.customers` + ALTER COLUMN join_date SET OPTIONS(description="The date the customer joined the retail bank."); + + +-- Column descriptions for CORE_ACCOUNTS_V2 table +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN account_id SET OPTIONS(description="Unique identifier for the account."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN customer_id SET OPTIONS(description="Foreign key referencing the customer who owns this account."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN account_type SET OPTIONS(description="The account product type code (AP = Advantage Plus, BS = Basic Savings, SC = Standard Checking)."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN balance SET OPTIONS(description="Current ledger balance of the account in EUR."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN open_date SET OPTIONS(description="The date the account was officially opened."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN status SET OPTIONS(description="The current status of the account (Active, Closed)."); + +ALTER TABLE `${dataset_id}.core_accounts_v2` + ALTER COLUMN last_transaction_date SET OPTIONS(description="The date of the last financial transaction on this account (NULL for closed or inactive accounts)."); + diff --git a/hacks/adk-ge/artifacts/main.tf b/hacks/adk-ge/artifacts/main.tf new file mode 100644 index 00000000..9be33515 --- /dev/null +++ b/hacks/adk-ge/artifacts/main.tf @@ -0,0 +1,177 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +resource "google_project_service" "default" { + project = var.gcp_project_id + for_each = toset([ + "cloudresourcemanager.googleapis.com", + "serviceusage.googleapis.com", + "iam.googleapis.com", + "storage.googleapis.com", + "compute.googleapis.com", + "sourcerepo.googleapis.com", + "bigquery.googleapis.com", + "agentregistry.googleapis.com", + "aiplatform.googleapis.com", + "apphub.googleapis.com", + "apptopology.googleapis.com", + "cloudapiregistry.googleapis.com", + "iamconnectors.googleapis.com", + "iap.googleapis.com", + "modelarmor.googleapis.com", + "networksecurity.googleapis.com", + "networkservices.googleapis.com", + "observability.googleapis.com", + "saasservicemgmt.googleapis.com", + "texttospeech.googleapis.com", + "geminidataanalytics.googleapis.com", + "sourcerepo.googleapis.com" + ]) + service = each.key + + disable_on_destroy = false +} + +# In case a default network is not present in the project the variable `create_default_network` needs to be set. +resource "google_compute_network" "default_network_created" { + name = "default" + auto_create_subnetworks = true + count = var.create_default_network ? 1 : 0 + depends_on = [ + google_project_service.default + ] +} + +resource "google_compute_firewall" "fwr_allow_custom" { + name = "fwr-ingress-allow-custom" + network = google_compute_network.default_network_created[0].self_link + count = var.create_default_network ? 1 : 0 + source_ranges = ["10.128.0.0/9"] + allow { + protocol = "all" + } +} + +resource "google_compute_firewall" "fwr_allow_iap" { + name = "fwr-ingress-allow-iap" + network = google_compute_network.default_network_created[0].self_link + count = var.create_default_network ? 1 : 0 + source_ranges = ["35.235.240.0/20"] + allow { + protocol = "tcp" + ports = ["22"] + } +} + +# This piece of code makes it possible to deal with the default network the same way, +# regardless of how it has been created. Make sure to refer to the default network through +# this resource when needed. +data "google_compute_network" "default_network" { + name = "default" + depends_on = [ + google_project_service.default, + google_compute_network.default_network_created + ] +} + +resource "google_sourcerepo_repository" "repo" { + name = "ghacks-adk-ge" + depends_on = [google_project_service.default] +} + +resource "google_bigquery_dataset" "retail_banking" { + dataset_id = "retail_banking" + friendly_name = "Retail Banking" + description = "Dataset containing retail banking customers and accounts data" + location = var.gcp_region + delete_contents_on_destroy = true + + depends_on = [google_project_service.default] +} + +resource "random_id" "job_suffix" { + byte_length = 8 + keepers = { + # Re-run the job when the SQL query content or dataset ID changes + sql_hash = sha256(file("${path.module}/generate_data.sql")) + dataset_id = google_bigquery_dataset.retail_banking.dataset_id + } +} + +resource "google_bigquery_job" "generate_data" { + job_id = "generate_data_${random_id.job_suffix.hex}" + location = var.gcp_region + + query { + query = templatefile("${path.module}/generate_data.sql", { + dataset_id = google_bigquery_dataset.retail_banking.dataset_id + }) + create_disposition = "" + write_disposition = "" + } + + depends_on = [google_bigquery_dataset.retail_banking] +} + +resource "google_service_account" "startup_vm_sa" { + account_id = "sa-startup-vm" + display_name = "Startup VM Service Account" +} + +resource "google_project_iam_member" "startup_vm_sa_roles" { + project = var.gcp_project_id + for_each = toset([ + "roles/source.admin", + "roles/logging.logWriter" + ]) + role = each.key + member = "serviceAccount:${google_service_account.startup_vm_sa.email}" +} + + +resource "google_compute_instance" "startup_vm" { + name = "gce-lnx-env-setup" + machine_type = "e2-micro" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-12" + } + } + + shielded_instance_config { + enable_secure_boot = true + enable_vtpm = true + } + + network_interface { + network = data.google_compute_network.default_network.self_link + access_config {} + } + + service_account { + email = google_service_account.startup_vm_sa.email + scopes = ["cloud-platform"] + } + + metadata_startup_script = templatefile("${path.module}/setup.tftpl", { + gcp_project_id = var.gcp_project_id, + gcp_region = var.gcp_region, + source_repo = google_sourcerepo_repository.repo.url + }) + + depends_on = [ + google_project_iam_member.startup_vm_sa_roles + ] +} + diff --git a/hacks/adk-ge/artifacts/outputs.tf b/hacks/adk-ge/artifacts/outputs.tf new file mode 100644 index 00000000..538c5585 --- /dev/null +++ b/hacks/adk-ge/artifacts/outputs.tf @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +output "project_id" { + value = var.gcp_project_id +} + +output "source_repo" { + value = google_sourcerepo_repository.repo.url +} + diff --git a/hacks/adk-ge/artifacts/providers.tf b/hacks/adk-ge/artifacts/providers.tf new file mode 100644 index 00000000..1a684301 --- /dev/null +++ b/hacks/adk-ge/artifacts/providers.tf @@ -0,0 +1,31 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 7.24.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.0.0" + } + } +} + +provider "google" { + project = var.gcp_project_id + region = var.gcp_region + zone = var.gcp_zone +} diff --git a/hacks/adk-ge/artifacts/runtime.yaml b/hacks/adk-ge/artifacts/runtime.yaml new file mode 100644 index 00000000..d39ccff0 --- /dev/null +++ b/hacks/adk-ge/artifacts/runtime.yaml @@ -0,0 +1,15 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +runtime: terraform +version: 1.12.1 diff --git a/hacks/adk-ge/artifacts/setup.tftpl b/hacks/adk-ge/artifacts/setup.tftpl new file mode 100644 index 00000000..5a4ac1b7 --- /dev/null +++ b/hacks/adk-ge/artifacts/setup.tftpl @@ -0,0 +1,22 @@ +#!/bin/bash +export PROJECT_ID=${gcp_project_id} +export REGION=${gcp_region} +export SOURCE_REPO=${source_repo} + +# Suppressing some warnings +echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections + +# Installing the OS dependencies +apt-get update && apt-get install -y git + +# Initialize the repo +echo "Retrieving the source code..." +git clone https://github.com/meken/gcp-adk-ge-agent.git adk-agent + +echo "Configuring the repo credentials..." +cd adk-agent +git config credential.helper gcloud.sh + +echo "Pushing the cloned repo to the new remote..." +git remote add google $SOURCE_REPO +git push google main:master diff --git a/hacks/adk-ge/artifacts/variables.tf b/hacks/adk-ge/artifacts/variables.tf new file mode 100644 index 00000000..2843c2de --- /dev/null +++ b/hacks/adk-ge/artifacts/variables.tf @@ -0,0 +1,38 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +variable "gcp_project_id" { + type = string + description = "The GCP project ID to create resources in." +} + +# Default value passed in +variable "gcp_region" { + type = string + description = "Region to create resources in." + default = "us-central1" +} + +# Default value passed in +variable "gcp_zone" { + type = string + description = "Zone to create resources in." + default = "us-central1-c" +} + +# Relevant when running on a system where no default network exists yet +variable "create_default_network" { + type = bool + default = false + description = "Whether to create a default network with subnets for all regions" +} diff --git a/hacks/adk-ge/images/.gitkeep b/hacks/adk-ge/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/hacks/adk-ge/images/ge-adk-arch.png b/hacks/adk-ge/images/ge-adk-arch.png new file mode 100644 index 00000000..26b9e975 Binary files /dev/null and b/hacks/adk-ge/images/ge-adk-arch.png differ diff --git a/hacks/adk-ge/qwiklabs.yaml b/hacks/adk-ge/qwiklabs.yaml new file mode 100644 index 00000000..30b15468 --- /dev/null +++ b/hacks/adk-ge/qwiklabs.yaml @@ -0,0 +1,96 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +schema_version: 2 +default_locale: en +title: "[gHacks] Custom ADK Agents and Gemini Enterprise app" +description: "In this hack, you will leverage the Agent Development Kit (ADK) + to develop a custom AI agent, run and test it securely on Agent Platform, + and deliver it to business users through the Gemini Enterprise app." +instruction: + type: md + uri: instructions/en.md +duration: 420 +max_duration: 420 +credits: 0 +level: fundamental +tags: +product_tags: +role_tags: +domain_tags: +environment: + resources: + - type: gcp_project + id: project + variant: gcp_medium_extra + startup_script: + type: qwiklabs + path: artifacts + - type: gcp_user + id: user_1 + permissions: + - project: project + roles: + - roles/owner + - type: gcp_user + id: user_2 + permissions: + - project: project + roles: + - roles/owner + - type: gcp_user + id: user_3 + permissions: + - project: project + roles: + - roles/owner + - type: gcp_user + id: user_4 + permissions: + - project: project + roles: + - roles/owner + - type: gcp_user + id: user_5 + permissions: + - project: project + roles: + - roles/owner + + student_visible_outputs: + - label: Open Console + reference: project.console_url + - label: Username 1 + reference: user_1.username + - label: Password 1 + reference: user_1.password + - label: Username 2 + reference: user_2.username + - label: Password 2 + reference: user_2.password + - label: Username 3 + reference: user_3.username + - label: Password 3 + reference: user_3.password + - label: Username 4 + reference: user_4.username + - label: Password 4 + reference: user_4.password + - label: Username 5 + reference: user_5.username + - label: Password 5 + reference: user_5.password + - label: Source repository + reference: project.startup_script.source_repo + - label: Project ID + reference: project.project_id diff --git a/hacks/adk-ge/resources/Makefile b/hacks/adk-ge/resources/Makefile new file mode 100644 index 00000000..726b6c70 --- /dev/null +++ b/hacks/adk-ge/resources/Makefile @@ -0,0 +1,21 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +TARGET=student-files.zip +OBJS=*.* + +$(TARGET): $(OBJS) + zip -r $(TARGET) $(OBJS) + +clean: + rm -f $(TARGET) diff --git a/hacks/adk-ge/resources/generate_data.py b/hacks/adk-ge/resources/generate_data.py new file mode 100644 index 00000000..555f60f6 --- /dev/null +++ b/hacks/adk-ge/resources/generate_data.py @@ -0,0 +1,38 @@ +import csv +import random +from datetime import datetime, timedelta + +def generate_data(filename, num_records=100): + products = ['Gold Saver', 'Student Account', 'Business Plus', 'Fixed Deposit'] + names = ['John Doe', 'Jane Smith', 'Alice Johnson', 'Bob Brown', 'Charlie Davis', 'Eve White', 'Frank Miller', 'Grace Hopper', 'Henry Ford', 'Ivy League'] + emails = ['john.doe@example.com', 'jane.smith@test.org', 'alice.j@corp.net', 'bob.b@gmail.com', 'charlie.d@yahoo.com'] + + with open(filename, 'w', newline='') as csvfile: + fieldnames = ['account_id', 'customer_id', 'pii_name', 'pii_email', 'pii_phone', 'balance', 'open_date', 'product_type'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + + for i in range(num_records): + account_id = f'ACC{1000 + i}' + customer_id = f'CUST{500 + (i % 10)}' + name = names[i % len(names)] + email = emails[i % len(emails)] + phone = f'+1-555-{random.randint(100, 999)}-{random.randint(1000, 9999)}' + balance = round(random.uniform(1000, 50000), 2) + open_date = (datetime.now() - timedelta(days=random.randint(1, 3650))).strftime('%Y-%m-%d') + product_type = random.choice(products) + + writer.writerow({ + 'account_id': account_id, + 'customer_id': customer_id, + 'pii_name': name, + 'pii_email': email, + 'pii_phone': phone, + 'balance': balance, + 'open_date': open_date, + 'product_type': product_type + }) + +if __name__ == '__main__': + generate_data('bank_data.csv') + print("bank_data.csv generated successfully.") diff --git a/hacks/adk-ge/resources/template-lectures.pdf b/hacks/adk-ge/resources/template-lectures.pdf new file mode 100644 index 00000000..fe6e6e91 Binary files /dev/null and b/hacks/adk-ge/resources/template-lectures.pdf differ diff --git a/hacks/adk-ge/solutions.md b/hacks/adk-ge/solutions.md new file mode 100644 index 00000000..308305b7 --- /dev/null +++ b/hacks/adk-ge/solutions.md @@ -0,0 +1,226 @@ +# Custom ADK Agents and Gemini Enterprise app - Coach's Guide + +## Introduction + +This guide provides notes, guidance, and solutions for the Gemini Enterprise with ADK gHack, utilizing the BigQuery MCP Server. + +## Challenge 1: Getting Started with ADK + +### Notes & Guidance + +Participants should clone the repository and run it locally. + +> [!NOTE] +> It's possible to run these challenges on a local machine too as long as it's been configured and authenticated with `gcloud`. Cloud Shell is the easy option as it already provides these pre-requisites. + +```shell +# Clone the skeleton code +git clone https://source.developers.google.com/p/$GOOGLE_CLOUD_PROJECT/r/ghacks-adk-ge +cd ghacks-adk-ge +# Region for Agent Runtime deployment, used later +REGION=us-central1 +# Authentication +cat > retail_bank_agent/.env < str: + """Returns the current date in YYYY-MM-DD format. + + Returns: + The current date as a string. + """ + return date.today().strftime("%Y-%m-%d") + + +root_agent = Agent( + model=helpers.MODEL, + name="root_agent", + description="A helpful assistant for Retail Bank performance related questions.", + instruction=f""" + You are an expert Retail Banking Data Analyst and answer questions by grounding them in data. + Use project {helpers.PROJECT_ID} and dataset {helpers.DATASET_ID} as your context. + """, + tools=[get_current_date] +) +``` + +The very first time someone needs to commit anything to the Git repository they'll have to identify themselves by running the following commands. + +```shell +git config --global user.name "$USER" # or their own name +git config --global user.email "$USER_EMAIL" # or their own email +``` + +Adding, committing and pushing the changes should be trivial, but see the following commands for the sake of completeness. + +```shell +git add . # stage everything that's changed in this directory and sub-directories +git commit -m "YOUR COMMIT MESSAGE" +git push +``` + +## Challenge 3: Talking to BigQuery + +### Notes & Guidance + +Instead of the built-in `BigQueryToolset`, participants will use the *BigQuery MCP Server* through ADK's `McpToolset`. + +```python +from google.adk.tools.mcp_tool import McpToolset +from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams + + +mcp_toolset = McpToolset( + connection_params=StreamableHTTPConnectionParams( + url="https://bigquery.googleapis.com/mcp" + ), + header_provider=helpers.get_auth_headers +) + + +root_agent = Agent( + model=helpers.MODEL, + name="root_agent", + description="A helpful assistant for Retail Bank performance related questions.", + instruction=f""" + You are an expert Retail Banking Data Analyst and answer questions by grounding them in data. + Use project {helpers.PROJECT_ID} and dataset {helpers.DATASET_ID} as your context. + """, + tools=[get_current_date, mcp_toolset] +) +``` + +## Challenge 4: Agent Runtime + +### Notes & Guidance + +First configure Agent identity through configuration file: + +```shell +echo '{ "identity_type": "AGENT_IDENTITY" }' > retail_bank_agent/.agent_engine_config.json +``` + +To deploy to Agent Runtime (Agent Platform): + +```shell +adk deploy agent_engine retail_bank_agent +``` + +> [!NOTE] +> If you need to redeploy your agent, provide the `--agent_engine_id` option so that it *replaces* your deployment (and doesn't create a new agent with a new identity) + +Typically users would navigate to the Console and retrieve the principal information and grant permissions, but for automation the following could be used (note that currently only REST APIs exist, no `gcloud` or ADK CLI commands are available): + +```shell +BASE_URL="https://$REGION-aiplatform.googleapis.com/v1" +# Retrieve the Agent Engine ID, assumes that there's only one +AGENT_ENGINE_ID=$(curl -s -X GET \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + "$BASE_URL/projects/$GOOGLE_CLOUD_PROJECT/locations/$REGION/reasoningEngines" | \ + jq -r '.reasoningEngines[0].name') +# Retrieve the Agent Identity details +AGENT_IDENTITY=$(curl -s -X GET \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + "$BASE_URL/$AGENT_ENGINE_ID" | \ + jq -r '.spec.effectiveIdentity') +# Grant the MCP Tool User role +gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ + --member="principal://$AGENT_IDENTITY" \ + --role="roles/mcp.toolUser" \ + --condition=None +# Grant the BigQuery Job User role to run queries +gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ + --member="principal://$AGENT_IDENTITY" \ + --role="roles/bigquery.jobUser" \ + --condition=None +# Grant the BigQuery Data Viewer role to access data (read-only) +gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ + --member="principal://$AGENT_IDENTITY" \ + --role="roles/bigquery.dataViewer" \ + --condition=None +``` + +## Challenge 5: Gemini Enterprise Integration + +### Notes & Guidance + +This should be rather straight-forward. Enabling Gemini Enterprise app is just a matter of navigating to the Gemini Enterprise (search bar helps), choosing the trial option, and picking a region (`global` is fine). After that choosing the identity provider from the landing page should be self-explanatory. + +Once those steps have been taken, you can register the agent in the Gemini Enterprise configuration page (Agents section) using the deployed Agent Runtime resource name, no authorization setup is needed. These [instuctions](https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent#register-an-adk-agent) should be easy to follow. + +We expect participants to use the Console, but for automation purposes you can use the following REST API commands. + +```shell +LOCATION=global +BASE_URL="https://$LOCATION-discoveryengine.googleapis.com/v1alpha" + +APP_URL=$(curl -s -X GET \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + -H "Content-Type: application/json" \ + -H "X-Goog-User-Project: $GOOGLE_CLOUD_PROJECT" \ + "$BASE_URL/projects/$GOOGLE_CLOUD_PROJECT/locations/$LOCATION/collections/default_collection/engines" | jq -r .engines[0].name +) + +curl -X POST \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + -H "Content-Type: application/json" \ + -H "X-Goog-User-Project: $GOOGLE_CLOUD_PROJECT" \ + "$BASE_URL/$APP_URL/assistants/default_assistant/agents" \ + -d "{ + \"displayName\": \"Retail Bank Agent\", + \"description\": \"A digital assistant for Retail Bank related questions.\", + \"adkAgentDefinition\": { + \"provisionedReasoningEngine\": { + \"reasoningEngine\": \"$AGENT_ENGINE_ID\" + } + } + }" +``` + +## Challenge 6: Visualizing Data (A2UI) + +### Notes & Guidance + +Since we've already provided most of the functionality in a function, all you have to do is to use the correct callback configuration, which is in this case `after_model_callback`. In addition we'll have to instruct the agent to generate the data in the right format so that it gets detected/parsed properly. + +```python +root_agent = Agent( + model=helpers.MODEL, + name="root_agent", + description="A helpful assistant for Retail Bank performance related questions.", + instruction=f""" + You are an expert Retail Banking Data Analyst and answer questions by grounding them in data. + Use project {helpers.PROJECT_ID} and dataset {helpers.DATASET_ID} as your context. + If and only if the user requests a bar chart, return the answer in csv format surrounded with + and . Include the names of the columns as a header in the csv. + """, + tools=[get_current_date, mcp_toolset], + after_model_callback=helpers.convert_to_a2ui +) +``` + +And in order to redeploy to Agent Runtime, make sure to include the `agent_engine_id` parameter, otherwise there will be a new instance with a new identity. The easiest way to get the agent engine id is through the Console (you can see it as resource name on the Deployments page), otherwise see the commands above to get it through REST APIs. + +```shell +adk deploy agent_engine --agent_engine_id="$AGENT_ENGINE_ID" retail_bank_agent +```