agent-starter-pack 0.7.1__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/METADATA +7 -6
  2. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/RECORD +63 -59
  3. agents/README.md +7 -0
  4. agents/adk_base/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -1
  5. agents/adk_base/notebooks/adk_app_testing.ipynb +8 -6
  6. agents/adk_gemini_fullstack/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -2
  7. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +8 -6
  8. agents/agentic_rag/{template/.templateconfig.yaml → .template/templateconfig.yaml} +2 -1
  9. agents/agentic_rag/notebooks/adk_app_testing.ipynb +8 -6
  10. agents/crewai_coding_crew/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
  11. llm.txt +7 -0
  12. src/base_template/Makefile +5 -1
  13. src/base_template/README.md +2 -2
  14. src/base_template/deployment/cd/deploy-to-prod.yaml +1 -16
  15. src/base_template/deployment/cd/staging.yaml +4 -19
  16. src/base_template/deployment/terraform/apis.tf +2 -2
  17. src/base_template/deployment/terraform/build_triggers.tf +5 -5
  18. src/base_template/deployment/terraform/dev/apis.tf +8 -1
  19. src/base_template/deployment/terraform/dev/variables.tf +3 -1
  20. src/base_template/deployment/terraform/iam.tf +8 -8
  21. src/base_template/deployment/terraform/locals.tf +9 -2
  22. src/base_template/deployment/terraform/log_sinks.tf +2 -2
  23. src/base_template/deployment/terraform/service_accounts.tf +3 -3
  24. src/base_template/deployment/terraform/storage.tf +7 -7
  25. src/base_template/deployment/terraform/variables.tf +3 -0
  26. src/base_template/pyproject.toml +4 -3
  27. src/cli/commands/create.py +191 -41
  28. src/cli/commands/list.py +158 -0
  29. src/cli/commands/setup_cicd.py +2 -2
  30. src/cli/main.py +2 -0
  31. src/cli/utils/cicd.py +2 -2
  32. src/cli/utils/remote_template.py +254 -0
  33. src/cli/utils/template.py +134 -25
  34. src/deployment_targets/agent_engine/app/agent_engine_app.py +7 -7
  35. src/deployment_targets/agent_engine/tests/load_test/README.md +1 -6
  36. src/deployment_targets/agent_engine/tests/load_test/load_test.py +13 -3
  37. src/deployment_targets/cloud_run/Dockerfile +3 -0
  38. src/deployment_targets/cloud_run/app/server.py +18 -0
  39. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +231 -0
  40. src/deployment_targets/cloud_run/deployment/terraform/service.tf +360 -0
  41. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +8 -5
  42. src/deployment_targets/cloud_run/tests/load_test/README.md +2 -2
  43. src/deployment_targets/cloud_run/tests/load_test/load_test.py +21 -17
  44. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +2 -3
  45. src/resources/docs/adk-cheatsheet.md +1 -1
  46. src/resources/locks/uv-adk_base-agent_engine.lock +873 -236
  47. src/resources/locks/uv-adk_base-cloud_run.lock +1169 -283
  48. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +873 -236
  49. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +1169 -283
  50. src/resources/locks/uv-agentic_rag-agent_engine.lock +508 -373
  51. src/resources/locks/uv-agentic_rag-cloud_run.lock +668 -469
  52. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +582 -587
  53. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +791 -733
  54. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +587 -478
  55. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +799 -627
  56. src/resources/locks/uv-live_api-cloud_run.lock +803 -603
  57. src/resources/setup_cicd/github.tf +2 -2
  58. src/utils/lock_utils.py +1 -1
  59. src/deployment_targets/cloud_run/uv.lock +0 -6952
  60. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/WHEEL +0 -0
  61. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/entry_points.txt +0 -0
  62. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/licenses/LICENSE +0 -0
  63. /agents/langgraph_base_react/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
  64. /agents/live_api/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
@@ -0,0 +1,360 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Get project information to access the project number
16
+ data "google_project" "project" {
17
+ for_each = local.deploy_project_ids
18
+
19
+ project_id = local.deploy_project_ids[each.key]
20
+ }
21
+
22
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
23
+
24
+ # VPC Network for AlloyDB
25
+ resource "google_compute_network" "default" {
26
+ for_each = local.deploy_project_ids
27
+
28
+ name = "${var.project_name}-alloydb-network"
29
+ project = local.deploy_project_ids[each.key]
30
+ auto_create_subnetworks = false
31
+
32
+ depends_on = [google_project_service.deploy_project_services]
33
+ }
34
+
35
+ # Subnet for AlloyDB
36
+ resource "google_compute_subnetwork" "default" {
37
+ for_each = local.deploy_project_ids
38
+
39
+ name = "${var.project_name}-alloydb-network"
40
+ ip_cidr_range = "10.0.0.0/24"
41
+ region = var.region
42
+ network = google_compute_network.default[each.key].id
43
+ project = local.deploy_project_ids[each.key]
44
+
45
+ # This is required for Cloud Run VPC connectors
46
+ purpose = "PRIVATE"
47
+
48
+ private_ip_google_access = true
49
+ }
50
+
51
+ # Private IP allocation for AlloyDB
52
+ resource "google_compute_global_address" "private_ip_alloc" {
53
+ for_each = local.deploy_project_ids
54
+
55
+ name = "${var.project_name}-private-ip"
56
+ project = local.deploy_project_ids[each.key]
57
+ address_type = "INTERNAL"
58
+ purpose = "VPC_PEERING"
59
+ prefix_length = 16
60
+ network = google_compute_network.default[each.key].id
61
+
62
+ depends_on = [google_project_service.deploy_project_services]
63
+ }
64
+
65
+ # VPC connection for AlloyDB
66
+ resource "google_service_networking_connection" "vpc_connection" {
67
+ for_each = local.deploy_project_ids
68
+
69
+ network = google_compute_network.default[each.key].id
70
+ service = "servicenetworking.googleapis.com"
71
+ reserved_peering_ranges = [google_compute_global_address.private_ip_alloc[each.key].name]
72
+ }
73
+
74
+ # AlloyDB Cluster
75
+ resource "google_alloydb_cluster" "session_db_cluster" {
76
+ for_each = local.deploy_project_ids
77
+
78
+ project = local.deploy_project_ids[each.key]
79
+ cluster_id = "${var.project_name}-alloydb-cluster"
80
+ location = var.region
81
+
82
+ network_config {
83
+ network = google_compute_network.default[each.key].id
84
+ }
85
+
86
+ depends_on = [
87
+ google_service_networking_connection.vpc_connection
88
+ ]
89
+ }
90
+
91
+ # AlloyDB Instance
92
+ resource "google_alloydb_instance" "session_db_instance" {
93
+ for_each = local.deploy_project_ids
94
+
95
+ cluster = google_alloydb_cluster.session_db_cluster[each.key].name
96
+ instance_id = "${var.project_name}-alloydb-instance"
97
+ instance_type = "PRIMARY"
98
+
99
+ availability_type = "REGIONAL" # Regional redundancy
100
+
101
+ machine_config {
102
+ cpu_count = 2
103
+ }
104
+ }
105
+
106
+ # Generate a random password for the database user
107
+ resource "random_password" "db_password" {
108
+ for_each = local.deploy_project_ids
109
+
110
+ length = 16
111
+ special = true
112
+ override_special = "!#$%&*()-_=+[]{}<>:?"
113
+ }
114
+
115
+ # Store the password in Secret Manager
116
+ resource "google_secret_manager_secret" "db_password" {
117
+ for_each = local.deploy_project_ids
118
+
119
+ project = local.deploy_project_ids[each.key]
120
+ secret_id = "${var.project_name}-db-password"
121
+
122
+ replication {
123
+ auto {}
124
+ }
125
+
126
+ depends_on = [google_project_service.deploy_project_services]
127
+ }
128
+
129
+ resource "google_secret_manager_secret_version" "db_password" {
130
+ for_each = local.deploy_project_ids
131
+
132
+ secret = google_secret_manager_secret.db_password[each.key].id
133
+ secret_data = random_password.db_password[each.key].result
134
+ }
135
+
136
+ resource "google_alloydb_user" "db_user" {
137
+ for_each = local.deploy_project_ids
138
+
139
+ cluster = google_alloydb_cluster.session_db_cluster[each.key].name
140
+ user_id = "postgres"
141
+ user_type = "ALLOYDB_BUILT_IN"
142
+ password = random_password.db_password[each.key].result
143
+ database_roles = ["alloydbsuperuser"]
144
+
145
+ depends_on = [google_alloydb_instance.session_db_instance]
146
+ }
147
+
148
+ {%- endif %}
149
+
150
+ resource "google_cloud_run_v2_service" "app_staging" {
151
+ name = var.project_name
152
+ location = var.region
153
+ project = var.staging_project_id
154
+ deletion_protection = false
155
+ ingress = "INGRESS_TRAFFIC_ALL"
156
+
157
+ template {
158
+ containers {
159
+ # Placeholder, will be replaced by the CI/CD pipeline
160
+ image = "us-docker.pkg.dev/cloudrun/container/hello"
161
+
162
+ resources {
163
+ limits = {
164
+ cpu = "4"
165
+ memory = "8Gi"
166
+ }
167
+ cpu_idle = false
168
+ }
169
+ {%- if cookiecutter.data_ingestion %}
170
+ {%- if cookiecutter.datastore_type == "vertex_ai_search" %}
171
+
172
+ env {
173
+ name = "DATA_STORE_ID"
174
+ value = resource.google_discovery_engine_data_store.data_store_staging.data_store_id
175
+ }
176
+
177
+ env {
178
+ name = "DATA_STORE_REGION"
179
+ value = var.data_store_region
180
+ }
181
+ {%- elif cookiecutter.datastore_type == "vertex_ai_vector_search" %}
182
+ env {
183
+ name = "VECTOR_SEARCH_INDEX"
184
+ value = resource.google_vertex_ai_index.vector_search_index_staging.id
185
+ }
186
+
187
+ env {
188
+ name = "VECTOR_SEARCH_INDEX_ENDPOINT"
189
+ value = resource.google_vertex_ai_index_endpoint.vector_search_index_endpoint_staging.id
190
+ }
191
+
192
+ env {
193
+ name = "VECTOR_SEARCH_BUCKET"
194
+ value = resource.google_storage_bucket.vector_search_data_bucket["staging"].url
195
+ }
196
+ {%- endif %}
197
+ {%- endif %}
198
+
199
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
200
+
201
+ env {
202
+ name = "DB_HOST"
203
+ value = google_alloydb_instance.session_db_instance["staging"].ip_address
204
+ }
205
+
206
+ env {
207
+ name = "DB_PASS"
208
+ value_source {
209
+ secret_key_ref {
210
+ secret = google_secret_manager_secret.db_password["staging"].secret_id
211
+ version = "latest"
212
+ }
213
+ }
214
+ }
215
+ {%- endif %}
216
+ }
217
+
218
+ service_account = google_service_account.cloud_run_app_sa["staging"].email
219
+ max_instance_request_concurrency = 40
220
+
221
+ scaling {
222
+ min_instance_count = 1
223
+ max_instance_count = 10
224
+ }
225
+
226
+ session_affinity = true
227
+
228
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
229
+ # VPC access for AlloyDB connectivity
230
+ vpc_access {
231
+ network_interfaces {
232
+ network = google_compute_network.default["staging"].id
233
+ subnetwork = google_compute_subnetwork.default["staging"].id
234
+ }
235
+ }
236
+ {%- endif %}
237
+ }
238
+
239
+ traffic {
240
+ type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
241
+ percent = 100
242
+ }
243
+
244
+ # This lifecycle block prevents Terraform from overwriting the container image when it's
245
+ # updated by Cloud Run deployments outside of Terraform (e.g., via CI/CD pipelines)
246
+ lifecycle {
247
+ ignore_changes = [
248
+ template[0].containers[0].image,
249
+ ]
250
+ }
251
+
252
+ # Make dependencies conditional to avoid errors.
253
+ depends_on = [google_project_service.deploy_project_services]
254
+ }
255
+
256
+ resource "google_cloud_run_v2_service" "app_prod" {
257
+ name = var.project_name
258
+ location = var.region
259
+ project = var.prod_project_id
260
+ deletion_protection = false
261
+ ingress = "INGRESS_TRAFFIC_ALL"
262
+
263
+ template {
264
+ containers {
265
+ # Placeholder, will be replaced by the CI/CD pipeline
266
+ image = "us-docker.pkg.dev/cloudrun/container/hello"
267
+
268
+ resources {
269
+ limits = {
270
+ cpu = "4"
271
+ memory = "8Gi"
272
+ }
273
+ cpu_idle = false
274
+ }
275
+ {%- if cookiecutter.data_ingestion %}
276
+ {%- if cookiecutter.datastore_type == "vertex_ai_search" %}
277
+
278
+ env {
279
+ name = "DATA_STORE_ID"
280
+ value = resource.google_discovery_engine_data_store.data_store_prod.data_store_id
281
+ }
282
+
283
+ env {
284
+ name = "DATA_STORE_REGION"
285
+ value = var.data_store_region
286
+ }
287
+ {%- elif cookiecutter.datastore_type == "vertex_ai_vector_search" %}
288
+ env {
289
+ name = "VECTOR_SEARCH_INDEX"
290
+ value = resource.google_vertex_ai_index.vector_search_index_prod.id
291
+ }
292
+
293
+ env {
294
+ name = "VECTOR_SEARCH_INDEX_ENDPOINT"
295
+ value = resource.google_vertex_ai_index_endpoint.vector_search_index_endpoint_prod.id
296
+ }
297
+
298
+ env {
299
+ name = "VECTOR_SEARCH_BUCKET"
300
+ value = resource.google_storage_bucket.vector_search_data_bucket["prod"].url
301
+ }
302
+ {%- endif %}
303
+ {%- endif %}
304
+
305
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
306
+
307
+ env {
308
+ name = "DB_HOST"
309
+ value = google_alloydb_instance.session_db_instance["prod"].ip_address
310
+ }
311
+
312
+ env {
313
+ name = "DB_PASS"
314
+ value_source {
315
+ secret_key_ref {
316
+ secret = google_secret_manager_secret.db_password["prod"].secret_id
317
+ version = "latest"
318
+ }
319
+ }
320
+ }
321
+ {%- endif %}
322
+ }
323
+
324
+ service_account = google_service_account.cloud_run_app_sa["prod"].email
325
+ max_instance_request_concurrency = 40
326
+
327
+ scaling {
328
+ min_instance_count = 1
329
+ max_instance_count = 10
330
+ }
331
+
332
+ session_affinity = true
333
+
334
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
335
+ # VPC access for AlloyDB connectivity
336
+ vpc_access {
337
+ network_interfaces {
338
+ network = google_compute_network.default["prod"].id
339
+ subnetwork = google_compute_subnetwork.default["prod"].id
340
+ }
341
+ }
342
+ {%- endif %}
343
+ }
344
+
345
+ traffic {
346
+ type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
347
+ percent = 100
348
+ }
349
+
350
+ # This lifecycle block prevents Terraform from overwriting the container image when it's
351
+ # updated by Cloud Run deployments outside of Terraform (e.g., via CI/CD pipelines)
352
+ lifecycle {
353
+ ignore_changes = [
354
+ template[0].containers[0].image,
355
+ ]
356
+ }
357
+
358
+ # Make dependencies conditional to avoid errors.
359
+ depends_on = [google_project_service.deploy_project_services]
360
+ }
@@ -122,16 +122,19 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
122
122
  logger.info("Starting chat stream test")
123
123
  {% if "adk" in cookiecutter.tags %}
124
124
  # Create session first
125
- user_id = "user_123"
126
- session_id = "session_abc"
127
- session_data = {"state": {"preferred_language": "English", "visit_count": 5}}
125
+ user_id = "test_user_123"
126
+ session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
127
+
128
+ session_url = f"{BASE_URL}/apps/app/users/{user_id}/sessions"
128
129
  session_response = requests.post(
129
- f"{BASE_URL}/apps/app/users/{user_id}/sessions/{session_id}",
130
+ session_url,
130
131
  headers=HEADERS,
131
132
  json=session_data,
132
- timeout=10,
133
+ timeout=60,
133
134
  )
134
135
  assert session_response.status_code == 200
136
+ logger.info(f"Session creation response: {session_response.json()}")
137
+ session_id = session_response.json()["id"]
135
138
 
136
139
  # Then send chat message
137
140
  data = {
@@ -18,7 +18,7 @@ uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload
18
18
  Using another terminal tab, This is suggested to avoid conflicts with the existing application python environment.
19
19
 
20
20
  ```bash
21
- python3 -m venv .locust_env && source locust_env/bin/activate && pip install locust==2.31.1
21
+ python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1
22
22
  ```
23
23
 
24
24
  **3. Execute the Load Test:**
@@ -69,7 +69,7 @@ export _ID_TOKEN=$(gcloud auth print-identity-token -q)
69
69
  **3. Execute the Load Test:**
70
70
  Create virtual environment with Locust:
71
71
  ```bash
72
- python3 -m venv .locust_env && source locust_env/bin/activate && pip install locust==2.31.1
72
+ python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1
73
73
  ```
74
74
 
75
75
  Execute load tests. The following command executes the same load test parameters as the local test but targets your remote Cloud Run instance.
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import json
16
15
  import os
17
16
  import time
18
17
  {%- if "adk" in cookiecutter.tags %}
@@ -44,15 +43,19 @@ class ChatStreamUser(HttpUser):
44
43
  {%- if "adk" in cookiecutter.tags %}
45
44
  # Create session first
46
45
  user_id = f"user_{uuid.uuid4()}"
47
- session_id = f"session_{uuid.uuid4()}"
48
- session_data = {"state": {"preferred_language": "English", "visit_count": 5}}
49
- requests.post(
50
- f"{self.client.base_url}/apps/app/users/{user_id}/sessions/{session_id}",
46
+ session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
47
+
48
+ session_url = f"{self.client.base_url}/apps/app/users/{user_id}/sessions"
49
+ session_response = requests.post(
50
+ session_url,
51
51
  headers=headers,
52
52
  json=session_data,
53
53
  timeout=10,
54
54
  )
55
55
 
56
+ # Get session_id from response
57
+ session_id = session_response.json()["id"]
58
+
56
59
  # Send chat message
57
60
  data = {
58
61
  "app_name": "app",
@@ -60,7 +63,7 @@ class ChatStreamUser(HttpUser):
60
63
  "session_id": session_id,
61
64
  "new_message": {
62
65
  "role": "user",
63
- "parts": [{"text": "What's the weather in San Francisco?"}],
66
+ "parts": [{"text": "Hello! Weather in New york?"}],
64
67
  },
65
68
  "streaming": True,
66
69
  }
@@ -93,24 +96,25 @@ class ChatStreamUser(HttpUser):
93
96
  events = []
94
97
  for line in response.iter_lines():
95
98
  if line:
96
- {%- if "adk" in cookiecutter.tags %}
97
- # SSE format is "data: {json}"
98
99
  line_str = line.decode("utf-8")
99
- if line_str.startswith("data: "):
100
- event_json = line_str[6:] # Remove "data: " prefix
101
- event = json.loads(event_json)
102
- events.append(event)
103
- {%- else %}
104
- event = json.loads(line)
105
- events.append(event)
106
- {%- endif %}
100
+ events.append(line_str)
101
+
102
+ if "429 Too Many Requests" in line_str:
103
+ self.environment.events.request.fire(
104
+ request_type="POST",
105
+ name=f"{ENDPOINT} rate_limited 429s",
106
+ response_time=0,
107
+ response_length=len(line),
108
+ response=response,
109
+ context={},
110
+ )
107
111
  end_time = time.time()
108
112
  total_time = end_time - start_time
109
113
  self.environment.events.request.fire(
110
114
  request_type="POST",
111
115
  name=f"{ENDPOINT} end",
112
116
  response_time=total_time * 1000, # Convert to milliseconds
113
- response_length=len(json.dumps(events)),
117
+ response_length=len(events),
114
118
  response=response,
115
119
  context={},
116
120
  )
@@ -81,12 +81,11 @@ export default function App() {
81
81
  };
82
82
 
83
83
  const createSession = async (): Promise<{userId: string, sessionId: string, appName: string}> => {
84
- const generatedSessionId = uuidv4();
85
- const response = await fetch(`/api/apps/app/users/u_999/sessions/${generatedSessionId}`, {
84
+ const response = await fetch(`/api/apps/app/users/u_999/sessions`, {
86
85
  method: "POST",
87
86
  headers: {
88
87
  "Content-Type": "application/json"
89
- }
88
+ },
90
89
  });
91
90
 
92
91
  if (!response.ok) {
@@ -994,7 +994,7 @@ Serverless container platform for custom web applications.
994
994
  # Ensure your agent_folder (e.g., 'my_first_agent') is in the same directory as main.py
995
995
  app: FastAPI = get_fast_api_app(
996
996
  agents_dir=os.path.dirname(os.path.abspath(__file__)),
997
- session_db_url="sqlite:///./sessions.db", # In-container SQLite, for simple cases
997
+ session_service_uri="sqlite:///./sessions.db", # In-container SQLite, for simple cases
998
998
  # For production: use a persistent DB (Cloud SQL) or VertexAiSessionService
999
999
  allow_origins=["*"],
1000
1000
  web=True # Serve ADK UI