agent-starter-pack 0.18.2__py3-none-any.whl → 0.21.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 (114) hide show
  1. agent_starter_pack/agents/{langgraph_base_react → adk_a2a_base}/.template/templateconfig.yaml +5 -12
  2. agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
  3. agent_starter_pack/{frontends/streamlit/frontend/style/app_markdown.py → agents/adk_a2a_base/app/__init__.py} +3 -23
  4. agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
  5. agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
  6. agent_starter_pack/agents/{crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → adk_a2a_base/notebooks/evaluating_adk_agent.ipynb} +163 -199
  7. agent_starter_pack/agents/adk_a2a_base/tests/integration/test_agent.py +58 -0
  8. agent_starter_pack/agents/adk_base/app/__init__.py +2 -2
  9. agent_starter_pack/agents/adk_base/app/agent.py +3 -0
  10. agent_starter_pack/agents/adk_base/notebooks/adk_app_testing.ipynb +13 -28
  11. agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
  12. agent_starter_pack/agents/adk_live/app/agent.py +3 -0
  13. agent_starter_pack/agents/agentic_rag/app/__init__.py +2 -2
  14. agent_starter_pack/agents/agentic_rag/app/agent.py +3 -0
  15. agent_starter_pack/agents/agentic_rag/notebooks/adk_app_testing.ipynb +13 -28
  16. agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/.template/templateconfig.yaml +12 -9
  17. agent_starter_pack/agents/langgraph_base/README.md +30 -0
  18. agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
  19. agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/app/agent.py +4 -4
  20. agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/tests/integration/test_agent.py +1 -1
  21. agent_starter_pack/base_template/.gitignore +4 -2
  22. agent_starter_pack/base_template/Makefile +110 -16
  23. agent_starter_pack/base_template/README.md +97 -12
  24. agent_starter_pack/base_template/deployment/terraform/dev/apis.tf +4 -6
  25. agent_starter_pack/base_template/deployment/terraform/dev/providers.tf +5 -1
  26. agent_starter_pack/base_template/deployment/terraform/dev/variables.tf +5 -3
  27. agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
  28. agent_starter_pack/base_template/deployment/terraform/github.tf +16 -9
  29. agent_starter_pack/base_template/deployment/terraform/locals.tf +7 -7
  30. agent_starter_pack/base_template/deployment/terraform/providers.tf +5 -1
  31. agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
  32. agent_starter_pack/base_template/deployment/terraform/storage.tf +0 -9
  33. agent_starter_pack/base_template/deployment/terraform/variables.tf +15 -19
  34. agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +20 -22
  35. agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
  36. agent_starter_pack/base_template/pyproject.toml +5 -17
  37. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +19 -4
  38. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +36 -11
  39. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +24 -5
  40. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +44 -9
  41. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
  42. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/typing.py +4 -6
  43. agent_starter_pack/{agents/crewai_coding_crew/app/crew/config/agents.yaml → base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/__init__.py } +9 -23
  44. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/part_converter.py +138 -0
  45. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/__init__.py +13 -0
  46. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/a2a_agent_executor.py +265 -0
  47. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/task_result_aggregator.py +152 -0
  48. agent_starter_pack/cli/commands/create.py +40 -4
  49. agent_starter_pack/cli/commands/enhance.py +1 -1
  50. agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
  51. agent_starter_pack/cli/main.py +2 -0
  52. agent_starter_pack/cli/utils/cicd.py +20 -4
  53. agent_starter_pack/cli/utils/template.py +257 -25
  54. agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +113 -16
  55. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +2 -2
  56. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +178 -9
  57. agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
  58. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +193 -307
  59. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
  60. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/{utils → app_utils}/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +13 -14
  61. agent_starter_pack/deployment_targets/cloud_run/Dockerfile +4 -1
  62. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +85 -86
  63. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +139 -107
  64. agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +228 -12
  65. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +4 -4
  66. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +92 -12
  67. agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/{server.py → fast_api_app.py} +194 -121
  68. agent_starter_pack/frontends/adk_live_react/frontend/package-lock.json +18 -18
  69. agent_starter_pack/frontends/adk_live_react/frontend/src/multimodal-live-types.ts +5 -3
  70. agent_starter_pack/resources/docs/adk-cheatsheet.md +198 -41
  71. agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
  72. agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
  73. agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +1443 -709
  74. agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +1058 -874
  75. agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +1443 -709
  76. agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +1058 -874
  77. agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +1568 -749
  78. agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +1123 -929
  79. agent_starter_pack/resources/locks/{uv-langgraph_base_react-agent_engine.lock → uv-langgraph_base-agent_engine.lock} +1714 -1689
  80. agent_starter_pack/resources/locks/{uv-langgraph_base_react-cloud_run.lock → uv-langgraph_base-cloud_run.lock} +1285 -2374
  81. agent_starter_pack/utils/watch_and_rebuild.py +1 -1
  82. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/METADATA +3 -6
  83. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/RECORD +89 -93
  84. agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
  85. llm.txt +4 -5
  86. agent_starter_pack/agents/crewai_coding_crew/README.md +0 -34
  87. agent_starter_pack/agents/crewai_coding_crew/app/agent.py +0 -47
  88. agent_starter_pack/agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
  89. agent_starter_pack/agents/crewai_coding_crew/app/crew/crew.py +0 -71
  90. agent_starter_pack/agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
  91. agent_starter_pack/agents/langgraph_base_react/README.md +0 -9
  92. agent_starter_pack/agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -1574
  93. agent_starter_pack/base_template/deployment/terraform/dev/log_sinks.tf +0 -69
  94. agent_starter_pack/base_template/deployment/terraform/log_sinks.tf +0 -79
  95. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +0 -155
  96. agent_starter_pack/cli/utils/register_gemini_enterprise.py +0 -406
  97. agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +0 -82
  98. agent_starter_pack/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
  99. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +0 -99
  100. agent_starter_pack/frontends/streamlit/frontend/side_bar.py +0 -214
  101. agent_starter_pack/frontends/streamlit/frontend/streamlit_app.py +0 -265
  102. agent_starter_pack/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
  103. agent_starter_pack/frontends/streamlit/frontend/utils/local_chat_history.py +0 -127
  104. agent_starter_pack/frontends/streamlit/frontend/utils/message_editing.py +0 -59
  105. agent_starter_pack/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
  106. agent_starter_pack/frontends/streamlit/frontend/utils/stream_handler.py +0 -310
  107. agent_starter_pack/frontends/streamlit/frontend/utils/title_summary.py +0 -94
  108. agent_starter_pack/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6650
  109. agent_starter_pack/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -7825
  110. agent_starter_pack-0.18.2.dist-info/entry_points.txt +0 -3
  111. /agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  112. /agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/gcs.py +0 -0
  113. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
  114. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,414 @@
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
+ import asyncio
16
+ import datetime
17
+ import importlib
18
+ import inspect
19
+ import json
20
+ import logging
21
+ import warnings
22
+ from typing import Any
23
+
24
+ import click
25
+ import google.auth
26
+ import vertexai
27
+ from vertexai._genai import _agent_engines_utils
28
+ from vertexai._genai.types import AgentEngine, AgentEngineConfig{%- if cookiecutter.is_adk_live %}, AgentServerMode{%- endif %}
29
+ {%- if cookiecutter.is_adk_live %}
30
+
31
+ from {{cookiecutter.agent_directory}}.app_utils.gcs import create_bucket_if_not_exists
32
+ {%- endif %}
33
+
34
+ # Suppress google-cloud-storage version compatibility warning
35
+ warnings.filterwarnings(
36
+ "ignore", category=FutureWarning, module="google.cloud.aiplatform"
37
+ )
38
+
39
+
40
+ def generate_class_methods_from_agent(agent_instance: Any) -> list[dict[str, Any]]:
41
+ """Generate method specifications with schemas from agent's register_operations().
42
+
43
+ See: https://docs.cloud.google.com/agent-builder/agent-engine/use/custom#supported-operations
44
+ """
45
+ registered_operations = _agent_engines_utils._get_registered_operations(
46
+ agent=agent_instance
47
+ )
48
+ class_methods_spec = _agent_engines_utils._generate_class_methods_spec_or_raise(
49
+ agent=agent_instance,
50
+ operations=registered_operations,
51
+ )
52
+ class_methods_list = [
53
+ _agent_engines_utils._to_dict(method_spec) for method_spec in class_methods_spec
54
+ ]
55
+ return class_methods_list
56
+
57
+
58
+ def parse_key_value_pairs(kv_string: str | None) -> dict[str, str]:
59
+ """Parse key-value pairs from a comma-separated KEY=VALUE string."""
60
+ result = {}
61
+ if kv_string:
62
+ for pair in kv_string.split(","):
63
+ if "=" in pair:
64
+ key, value = pair.split("=", 1)
65
+ result[key.strip()] = value.strip()
66
+ else:
67
+ logging.warning(f"Skipping malformed key-value pair: {pair}")
68
+ return result
69
+
70
+
71
+ def write_deployment_metadata(
72
+ remote_agent: Any,
73
+ metadata_file: str = "deployment_metadata.json",
74
+ ) -> None:
75
+ """Write deployment metadata to file."""
76
+ metadata = {
77
+ "remote_agent_engine_id": remote_agent.api_resource.name,
78
+ "deployment_target": "agent_engine",
79
+ {%- if cookiecutter.is_a2a %}
80
+ "is_a2a": True,
81
+ {%- else %}
82
+ "is_a2a": False,
83
+ {%- endif %}
84
+ "deployment_timestamp": datetime.datetime.now().isoformat(),
85
+ }
86
+
87
+ with open(metadata_file, "w") as f:
88
+ json.dump(metadata, f, indent=2)
89
+
90
+ logging.info(f"Agent Engine ID written to {metadata_file}")
91
+
92
+
93
+ def print_deployment_success(
94
+ remote_agent: Any,
95
+ location: str,
96
+ project: str,
97
+ ) -> None:
98
+ """Print deployment success message with console URL."""
99
+ # Extract agent engine ID and project number for console URL
100
+ resource_name_parts = remote_agent.api_resource.name.split("/")
101
+ agent_engine_id = resource_name_parts[-1]
102
+ project_number = resource_name_parts[1]
103
+
104
+ {%- if cookiecutter.is_adk_live %}
105
+ print("\n✅ Deployment successful! Run your agent with: `make playground-remote`")
106
+ {%- elif cookiecutter.is_a2a %}
107
+ print(
108
+ "\n✅ Deployment successful! Test your agent: notebooks/adk_a2a_app_testing.ipynb"
109
+ )
110
+ {%- else %}
111
+ print("\n✅ Deployment successful!")
112
+ {%- endif %}
113
+ service_account = remote_agent.api_resource.spec.service_account
114
+ if service_account:
115
+ print(f"Service Account: {service_account}")
116
+ else:
117
+ default_sa = (
118
+ f"service-{project_number}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
119
+ )
120
+ print(f"Service Account: {default_sa}")
121
+ {%- if cookiecutter.is_adk and not cookiecutter.is_adk_live and not cookiecutter.is_a2a %}
122
+ playground_url = f"https://console.cloud.google.com/vertex-ai/agents/locations/{location}/agent-engines/{agent_engine_id}/playground?project={project}"
123
+ print(f"\n📊 Open Console Playground: {playground_url}\n")
124
+ {%- else %}
125
+ console_url = f"https://console.cloud.google.com/vertex-ai/agents/locations/{location}/agent-engines/{agent_engine_id}?project={project}"
126
+ print(f"\n📊 View in Console: {console_url}\n")
127
+ {%- endif %}
128
+
129
+
130
+ @click.command()
131
+ @click.option(
132
+ "--project",
133
+ default=None,
134
+ help="GCP project ID (defaults to application default credentials)",
135
+ )
136
+ @click.option(
137
+ "--location",
138
+ default="us-central1",
139
+ help="GCP region (defaults to us-central1)",
140
+ )
141
+ @click.option(
142
+ "--display-name",
143
+ default="{{cookiecutter.project_name}}",
144
+ help="Display name for the agent engine",
145
+ )
146
+ @click.option(
147
+ "--description",
148
+ default="{{cookiecutter.agent_description}}",
149
+ help="Description of the agent",
150
+ )
151
+ @click.option(
152
+ "--source-packages",
153
+ multiple=True,
154
+ default=["./{{cookiecutter.agent_directory}}"],
155
+ help="Source packages to deploy. Can be specified multiple times (e.g., --source-packages=./app --source-packages=./lib)",
156
+ )
157
+ @click.option(
158
+ "--entrypoint-module",
159
+ default="{{cookiecutter.agent_directory}}.agent_engine_app",
160
+ help="Python module path for the agent entrypoint (required)",
161
+ )
162
+ @click.option(
163
+ "--entrypoint-object",
164
+ default="agent_engine",
165
+ help="Name of the agent instance at module level (required)",
166
+ )
167
+ @click.option(
168
+ "--requirements-file",
169
+ default="{{cookiecutter.agent_directory}}/app_utils/.requirements.txt",
170
+ help="Path to requirements.txt file",
171
+ )
172
+ @click.option(
173
+ "--set-env-vars",
174
+ default=None,
175
+ help="Comma-separated list of environment variables in KEY=VALUE format",
176
+ )
177
+ @click.option(
178
+ "--labels",
179
+ default=None,
180
+ help="Comma-separated list of labels in KEY=VALUE format",
181
+ )
182
+ @click.option(
183
+ "--service-account",
184
+ default=None,
185
+ help="Service account email to use for the agent engine",
186
+ )
187
+ @click.option(
188
+ "--min-instances",
189
+ type=int,
190
+ default=1,
191
+ help="Minimum number of instances (default: 1)",
192
+ )
193
+ @click.option(
194
+ "--max-instances",
195
+ type=int,
196
+ default=10,
197
+ help="Maximum number of instances (default: 10)",
198
+ )
199
+ @click.option(
200
+ "--cpu",
201
+ default="4",
202
+ help="CPU limit (default: 4)",
203
+ )
204
+ @click.option(
205
+ "--memory",
206
+ default="8Gi",
207
+ help="Memory limit (default: 8Gi)",
208
+ )
209
+ @click.option(
210
+ "--container-concurrency",
211
+ type=int,
212
+ default=9,
213
+ help="Container concurrency (default: 9)",
214
+ )
215
+ @click.option(
216
+ "--num-workers",
217
+ type=int,
218
+ default=1,
219
+ help="Number of worker processes (default: 1)",
220
+ )
221
+ def deploy_agent_engine_app(
222
+ project: str | None,
223
+ location: str,
224
+ display_name: str,
225
+ description: str,
226
+ source_packages: tuple[str, ...],
227
+ entrypoint_module: str,
228
+ entrypoint_object: str,
229
+ requirements_file: str,
230
+ set_env_vars: str | None,
231
+ labels: str | None,
232
+ service_account: str | None,
233
+ min_instances: int,
234
+ max_instances: int,
235
+ cpu: str,
236
+ memory: str,
237
+ container_concurrency: int,
238
+ num_workers: int,
239
+ ) -> AgentEngine:
240
+ """Deploy the agent engine app to Vertex AI."""
241
+
242
+ logging.basicConfig(level=logging.INFO)
243
+ logging.getLogger("httpx").setLevel(logging.WARNING)
244
+
245
+ # Parse environment variables and labels if provided
246
+ env_vars = parse_key_value_pairs(set_env_vars)
247
+ labels_dict = parse_key_value_pairs(labels)
248
+
249
+ # Set GOOGLE_CLOUD_REGION to match deployment location
250
+ env_vars["GOOGLE_CLOUD_REGION"] = location
251
+
252
+ # Add NUM_WORKERS from CLI argument (can be overridden via --set-env-vars)
253
+ if "NUM_WORKERS" not in env_vars:
254
+ env_vars["NUM_WORKERS"] = str(num_workers)
255
+
256
+ # Enable telemetry by default for Agent Engine
257
+ if "GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY" not in env_vars:
258
+ env_vars["GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY"] = "true"
259
+ if "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" not in env_vars:
260
+ env_vars["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "true"
261
+
262
+ if not project:
263
+ _, project = google.auth.default()
264
+
265
+ print("""
266
+ ╔═══════════════════════════════════════════════════════════╗
267
+ ║ ║
268
+ ║ 🤖 DEPLOYING AGENT TO VERTEX AI AGENT ENGINE 🤖 ║
269
+ ║ ║
270
+ ╚═══════════════════════════════════════════════════════════╝
271
+ """)
272
+
273
+ # Log deployment parameters
274
+ click.echo("\n📋 Deployment Parameters:")
275
+ click.echo(f" Project: {project}")
276
+ click.echo(f" Location: {location}")
277
+ click.echo(f" Display Name: {display_name}")
278
+ click.echo(f" Min Instances: {min_instances}")
279
+ click.echo(f" Max Instances: {max_instances}")
280
+ click.echo(f" CPU: {cpu}")
281
+ click.echo(f" Memory: {memory}")
282
+ click.echo(f" Container Concurrency: {container_concurrency}")
283
+ if service_account:
284
+ click.echo(f" Service Account: {service_account}")
285
+ if env_vars:
286
+ click.echo("\n🌍 Environment Variables:")
287
+ for key, value in sorted(env_vars.items()):
288
+ click.echo(f" {key}: {value}")
289
+
290
+ source_packages_list = list(source_packages)
291
+
292
+ # Initialize vertexai client
293
+ client = vertexai.Client(
294
+ project=project,
295
+ location=location,
296
+ )
297
+ vertexai.init(project=project, location=location)
298
+
299
+ # Add agent garden labels if configured
300
+ {%- if cookiecutter.agent_garden %}
301
+ {%- if cookiecutter.agent_sample_id and cookiecutter.agent_sample_publisher %}
302
+ labels_dict["vertex-agent-sample-id"] = "{{cookiecutter.agent_sample_id}}"
303
+ labels_dict["vertex-agent-sample-publisher"] = "{{cookiecutter.agent_sample_publisher}}"
304
+ labels_dict["deployed-with"] = "agent-garden"
305
+ {%- endif %}
306
+ {%- endif %}
307
+
308
+ # Dynamically import the agent instance to generate class_methods
309
+ logging.info(f"Importing {entrypoint_module}.{entrypoint_object}")
310
+ module = importlib.import_module(entrypoint_module)
311
+ agent_instance = getattr(module, entrypoint_object)
312
+
313
+ # If the agent_instance is a coroutine, await it to get the actual instance
314
+ if inspect.iscoroutine(agent_instance):
315
+ logging.info(f"Detected coroutine, awaiting {entrypoint_object}...")
316
+ agent_instance = asyncio.run(agent_instance)
317
+
318
+ {%- if cookiecutter.is_adk_live %}
319
+ # For adk_live, use pickle-based deployment (source-based not yet supported with EXPERIMENTAL mode)
320
+ # Ensure staging bucket exists for pickle serialization
321
+ staging_bucket_uri = f"gs://{project}-agent-engine"
322
+
323
+ create_bucket_if_not_exists(
324
+ bucket_name=staging_bucket_uri, project=project, location=location
325
+ )
326
+
327
+ config = AgentEngineConfig(
328
+ display_name=display_name,
329
+ description=description,
330
+ env_vars=env_vars,
331
+ extra_packages=source_packages_list,
332
+ service_account=service_account,
333
+ requirements=requirements_file,
334
+ staging_bucket=staging_bucket_uri,
335
+ labels=labels_dict,
336
+ gcs_dir_name=display_name,
337
+ agent_server_mode=AgentServerMode.EXPERIMENTAL, # Enable bidi streaming
338
+ resource_limits={"cpu": cpu, "memory": memory},
339
+ )
340
+
341
+ agent_config = {
342
+ "agent": agent_instance,
343
+ "config": config,
344
+ }
345
+ {%- else %}
346
+ # Generate class methods spec from register_operations
347
+ class_methods_list = generate_class_methods_from_agent(agent_instance)
348
+
349
+ config = AgentEngineConfig(
350
+ display_name=display_name,
351
+ description=description,
352
+ source_packages=source_packages_list,
353
+ entrypoint_module=entrypoint_module,
354
+ entrypoint_object=entrypoint_object,
355
+ class_methods=class_methods_list,
356
+ env_vars=env_vars,
357
+ service_account=service_account,
358
+ requirements_file=requirements_file,
359
+ labels=labels_dict,
360
+ min_instances=min_instances,
361
+ max_instances=max_instances,
362
+ resource_limits={"cpu": cpu, "memory": memory},
363
+ container_concurrency=container_concurrency,
364
+ {%- if cookiecutter.is_adk %}
365
+ agent_framework="google-adk",
366
+ {%- endif %}
367
+ )
368
+ {%- endif %}
369
+
370
+ # Check if an agent with this name already exists
371
+ existing_agents = list(client.agent_engines.list())
372
+ matching_agents = [
373
+ agent
374
+ for agent in existing_agents
375
+ if agent.api_resource.display_name == display_name
376
+ ]
377
+
378
+ # Deploy the agent (create or update)
379
+ if matching_agents:
380
+ click.echo(f"\n📝 Updating existing agent: {display_name}")
381
+ else:
382
+ click.echo(f"\n🚀 Creating new agent: {display_name}")
383
+
384
+ click.echo("🚀 Deploying to Vertex AI Agent Engine (this can take 3-5 minutes)...")
385
+
386
+ {%- if cookiecutter.is_adk_live %}
387
+ if matching_agents:
388
+ remote_agent = client.agent_engines.update(
389
+ name=matching_agents[0].api_resource.name,
390
+ agent=agent_instance,
391
+ config=config,
392
+ )
393
+ else:
394
+ remote_agent = client.agent_engines.create(
395
+ agent=agent_instance,
396
+ config=config,
397
+ )
398
+ {%- else %}
399
+ if matching_agents:
400
+ remote_agent = client.agent_engines.update(
401
+ name=matching_agents[0].api_resource.name, config=config
402
+ )
403
+ else:
404
+ remote_agent = client.agent_engines.create(config=config)
405
+ {%- endif %}
406
+
407
+ write_deployment_metadata(remote_agent)
408
+ print_deployment_success(remote_agent, location, project)
409
+
410
+ return remote_agent
411
+
412
+
413
+ if __name__ == "__main__":
414
+ deploy_agent_engine_app()
@@ -15,6 +15,7 @@
15
15
  import asyncio
16
16
  import json
17
17
  import logging
18
+ import uuid
18
19
  from collections.abc import Callable
19
20
  from pathlib import Path
20
21
  from typing import Any, Literal
@@ -27,7 +28,7 @@ from fastapi.middleware.cors import CORSMiddleware
27
28
  from fastapi.responses import FileResponse
28
29
  from fastapi.staticfiles import StaticFiles
29
30
  from google.cloud import logging as google_cloud_logging
30
- from pydantic import BaseModel
31
+ from pydantic import BaseModel, Field
31
32
  from websockets.exceptions import ConnectionClosedError
32
33
 
33
34
  app = FastAPI()
@@ -60,7 +61,7 @@ app.state.config = {
60
61
  "project_id": None,
61
62
  "location": "us-central1",
62
63
  "local_agent_path": "..agent.root_agent",
63
- "agent_engine_class_path": "..agent_engine_app.AgentEngineApp",
64
+ "agent_engine_object_path": "..agent_engine_app.agent_engine",
64
65
  }
65
66
 
66
67
 
@@ -299,14 +300,12 @@ def get_connect_and_run_callable(
299
300
  )
300
301
  else:
301
302
  # Local agent engine mode
302
- # Dynamically import agent and engine class
303
- agent_callable = _dynamic_import(config["local_agent_path"])
303
+ # Dynamically import the pre-configured agent_engine object
304
+ agent_engine = _dynamic_import(config["agent_engine_object_path"])
304
305
  logging.info(
305
- f"Starting local agent engine with callable: {agent_callable.__name__ if hasattr(agent_callable, '__name__') else agent_callable}"
306
+ f"Starting local agent engine with object: {type(agent_engine).__name__}"
306
307
  )
307
308
 
308
- AgentEngineApp = _dynamic_import(config["agent_engine_class_path"])
309
- agent_engine = AgentEngineApp(agent=agent_callable)
310
309
  adapter = WebSocketToQueueAdapter(websocket, agent_engine)
311
310
 
312
311
  logging.info("Starting bidirectional communication with agent engine")
@@ -331,9 +330,9 @@ class Feedback(BaseModel):
331
330
 
332
331
  score: int | float
333
332
  text: str | None = ""
334
- run_id: str
335
- user_id: str | None
336
333
  log_type: Literal["feedback"] = "feedback"
334
+ user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
335
+ session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
337
336
 
338
337
 
339
338
  @app.post("/feedback")
@@ -417,10 +416,10 @@ if __name__ == "__main__":
417
416
  help="Python path to local agent callable (e.g., 'app.agent.root_agent')",
418
417
  )
419
418
  parser.add_argument(
420
- "--agent-engine-class",
419
+ "--agent-engine-object",
421
420
  type=str,
422
- default="..agent_engine_app.AgentEngineApp",
423
- help="Python path to agent engine class",
421
+ default="..agent_engine_app.agent_engine",
422
+ help="Python path to agent engine object instance",
424
423
  )
425
424
  parser.add_argument(
426
425
  "--port",
@@ -444,7 +443,7 @@ if __name__ == "__main__":
444
443
  "project_id": None,
445
444
  "location": "us-central1",
446
445
  "local_agent_path": args.local_agent,
447
- "agent_engine_class_path": args.agent_engine_class,
446
+ "agent_engine_object_path": args.agent_engine_object,
448
447
  }
449
448
 
450
449
  if args.mode == "remote":
@@ -512,7 +511,7 @@ if __name__ == "__main__":
512
511
  else:
513
512
  print("Starting server in LOCAL mode")
514
513
  print(f" Using agent: {config['local_agent_path']}")
515
- print(f" Using agent engine class: {config['agent_engine_class_path']}")
514
+ print(f" Using agent engine object: {config['agent_engine_object_path']}")
516
515
 
517
516
  # Store configuration in app state
518
517
  app.state.config = config
@@ -43,6 +43,9 @@ RUN uv sync --frozen
43
43
  ARG COMMIT_SHA=""
44
44
  ENV COMMIT_SHA=${COMMIT_SHA}
45
45
 
46
+ ARG AGENT_VERSION=0.0.0
47
+ ENV AGENT_VERSION=${AGENT_VERSION}
48
+
46
49
  EXPOSE 8080
47
50
 
48
- CMD ["uv", "run", "uvicorn", "{{cookiecutter.agent_directory}}.server:app", "--host", "0.0.0.0", "--port", "8080"]
51
+ CMD ["uv", "run", "uvicorn", "{{cookiecutter.agent_directory}}.fast_api_app:app", "--host", "0.0.0.0", "--port", "8080"]