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.
- {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/METADATA +7 -6
- {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/RECORD +63 -59
- agents/README.md +7 -0
- agents/adk_base/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -1
- agents/adk_base/notebooks/adk_app_testing.ipynb +8 -6
- agents/adk_gemini_fullstack/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -2
- agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +8 -6
- agents/agentic_rag/{template/.templateconfig.yaml → .template/templateconfig.yaml} +2 -1
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +8 -6
- agents/crewai_coding_crew/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
- llm.txt +7 -0
- src/base_template/Makefile +5 -1
- src/base_template/README.md +2 -2
- src/base_template/deployment/cd/deploy-to-prod.yaml +1 -16
- src/base_template/deployment/cd/staging.yaml +4 -19
- src/base_template/deployment/terraform/apis.tf +2 -2
- src/base_template/deployment/terraform/build_triggers.tf +5 -5
- src/base_template/deployment/terraform/dev/apis.tf +8 -1
- src/base_template/deployment/terraform/dev/variables.tf +3 -1
- src/base_template/deployment/terraform/iam.tf +8 -8
- src/base_template/deployment/terraform/locals.tf +9 -2
- src/base_template/deployment/terraform/log_sinks.tf +2 -2
- src/base_template/deployment/terraform/service_accounts.tf +3 -3
- src/base_template/deployment/terraform/storage.tf +7 -7
- src/base_template/deployment/terraform/variables.tf +3 -0
- src/base_template/pyproject.toml +4 -3
- src/cli/commands/create.py +191 -41
- src/cli/commands/list.py +158 -0
- src/cli/commands/setup_cicd.py +2 -2
- src/cli/main.py +2 -0
- src/cli/utils/cicd.py +2 -2
- src/cli/utils/remote_template.py +254 -0
- src/cli/utils/template.py +134 -25
- src/deployment_targets/agent_engine/app/agent_engine_app.py +7 -7
- src/deployment_targets/agent_engine/tests/load_test/README.md +1 -6
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +13 -3
- src/deployment_targets/cloud_run/Dockerfile +3 -0
- src/deployment_targets/cloud_run/app/server.py +18 -0
- src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +231 -0
- src/deployment_targets/cloud_run/deployment/terraform/service.tf +360 -0
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +8 -5
- src/deployment_targets/cloud_run/tests/load_test/README.md +2 -2
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +21 -17
- src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +2 -3
- src/resources/docs/adk-cheatsheet.md +1 -1
- src/resources/locks/uv-adk_base-agent_engine.lock +873 -236
- src/resources/locks/uv-adk_base-cloud_run.lock +1169 -283
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +873 -236
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +1169 -283
- src/resources/locks/uv-agentic_rag-agent_engine.lock +508 -373
- src/resources/locks/uv-agentic_rag-cloud_run.lock +668 -469
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +582 -587
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +791 -733
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +587 -478
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +799 -627
- src/resources/locks/uv-live_api-cloud_run.lock +803 -603
- src/resources/setup_cicd/github.tf +2 -2
- src/utils/lock_utils.py +1 -1
- src/deployment_targets/cloud_run/uv.lock +0 -6952
- {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/licenses/LICENSE +0 -0
- /agents/langgraph_base_react/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
- /agents/live_api/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
src/cli/utils/template.py
CHANGED
@@ -29,6 +29,9 @@ from rich.prompt import IntPrompt, Prompt
|
|
29
29
|
from src.cli.utils.version import get_current_version
|
30
30
|
|
31
31
|
from .datastores import DATASTORES
|
32
|
+
from .remote_template import (
|
33
|
+
get_base_template_name,
|
34
|
+
)
|
32
35
|
|
33
36
|
ADK_FILES = ["app/__init__.py"]
|
34
37
|
NON_ADK_FILES: list[str] = []
|
@@ -69,7 +72,7 @@ class TemplateConfig:
|
|
69
72
|
|
70
73
|
|
71
74
|
OVERWRITE_FOLDERS = ["app", "frontend", "tests", "notebooks"]
|
72
|
-
TEMPLATE_CONFIG_FILE = "
|
75
|
+
TEMPLATE_CONFIG_FILE = "templateconfig.yaml"
|
73
76
|
DEPLOYMENT_FOLDERS = ["cloud_run", "agent_engine"]
|
74
77
|
DEFAULT_FRONTEND = "streamlit"
|
75
78
|
|
@@ -94,7 +97,7 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
|
|
94
97
|
|
95
98
|
for agent_dir in agents_dir.iterdir():
|
96
99
|
if agent_dir.is_dir() and not agent_dir.name.startswith("__"):
|
97
|
-
template_config_path = agent_dir / "template" / "
|
100
|
+
template_config_path = agent_dir / ".template" / "templateconfig.yaml"
|
98
101
|
if template_config_path.exists():
|
99
102
|
try:
|
100
103
|
with open(template_config_path) as f:
|
@@ -154,15 +157,20 @@ def load_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
|
|
154
157
|
return {}
|
155
158
|
|
156
159
|
|
157
|
-
def get_deployment_targets(
|
160
|
+
def get_deployment_targets(
|
161
|
+
agent_name: str, remote_config: dict[str, Any] | None = None
|
162
|
+
) -> list:
|
158
163
|
"""Get available deployment targets for the selected agent."""
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
if remote_config:
|
165
|
+
config = remote_config
|
166
|
+
else:
|
167
|
+
template_path = (
|
168
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
169
|
+
/ "agents"
|
170
|
+
/ agent_name
|
171
|
+
/ ".template"
|
172
|
+
)
|
173
|
+
config = load_template_config(template_path)
|
166
174
|
|
167
175
|
if not config:
|
168
176
|
return []
|
@@ -171,9 +179,11 @@ def get_deployment_targets(agent_name: str) -> list:
|
|
171
179
|
return targets if isinstance(targets, list) else [targets]
|
172
180
|
|
173
181
|
|
174
|
-
def prompt_deployment_target(
|
182
|
+
def prompt_deployment_target(
|
183
|
+
agent_name: str, remote_config: dict[str, Any] | None = None
|
184
|
+
) -> str:
|
175
185
|
"""Ask user to select a deployment target for the agent."""
|
176
|
-
targets = get_deployment_targets(agent_name)
|
186
|
+
targets = get_deployment_targets(agent_name, remote_config=remote_config)
|
177
187
|
|
178
188
|
# Define deployment target friendly names and descriptions
|
179
189
|
TARGET_INFO = {
|
@@ -206,6 +216,36 @@ def prompt_deployment_target(agent_name: str) -> str:
|
|
206
216
|
return targets[choice - 1]
|
207
217
|
|
208
218
|
|
219
|
+
def prompt_session_type_selection() -> str:
|
220
|
+
"""Ask user to select a session type for Cloud Run deployment."""
|
221
|
+
console = Console()
|
222
|
+
|
223
|
+
session_types = {
|
224
|
+
"in_memory": {
|
225
|
+
"display_name": "In-memory session",
|
226
|
+
"description": "Session data stored in memory - ideal for stateless applications",
|
227
|
+
},
|
228
|
+
"alloydb": {
|
229
|
+
"display_name": "AlloyDB",
|
230
|
+
"description": "Use AlloyDB for session management. Comes with terraform resources for deployment.",
|
231
|
+
},
|
232
|
+
}
|
233
|
+
|
234
|
+
console.print("\n> Please select a session type:")
|
235
|
+
for idx, (_key, info) in enumerate(session_types.items(), 1):
|
236
|
+
console.print(
|
237
|
+
f"{idx}. [bold]{info['display_name']}[/] - [dim]{info['description']}[/]"
|
238
|
+
)
|
239
|
+
|
240
|
+
choice = IntPrompt.ask(
|
241
|
+
"\nEnter the number of your session type choice",
|
242
|
+
default=1,
|
243
|
+
show_default=True,
|
244
|
+
)
|
245
|
+
|
246
|
+
return list(session_types.keys())[choice - 1]
|
247
|
+
|
248
|
+
|
209
249
|
def prompt_datastore_selection(
|
210
250
|
agent_name: str, from_cli_flag: bool = False
|
211
251
|
) -> str | None:
|
@@ -242,7 +282,7 @@ def prompt_datastore_selection(
|
|
242
282
|
pathlib.Path(__file__).parent.parent.parent.parent
|
243
283
|
/ "agents"
|
244
284
|
/ agent_name
|
245
|
-
/ "template"
|
285
|
+
/ ".template"
|
246
286
|
)
|
247
287
|
config = load_template_config(template_path)
|
248
288
|
|
@@ -319,7 +359,7 @@ def prompt_datastore_selection(
|
|
319
359
|
def get_template_path(agent_name: str, debug: bool = False) -> pathlib.Path:
|
320
360
|
"""Get the absolute path to the agent template directory."""
|
321
361
|
current_dir = pathlib.Path(__file__).parent.parent.parent.parent
|
322
|
-
template_path = current_dir / "agents" / agent_name / "template"
|
362
|
+
template_path = current_dir / "agents" / agent_name / ".template"
|
323
363
|
if debug:
|
324
364
|
logging.debug(f"Looking for template in: {template_path}")
|
325
365
|
logging.debug(f"Template exists: {template_path.exists()}")
|
@@ -365,7 +405,10 @@ def process_template(
|
|
365
405
|
deployment_target: str | None = None,
|
366
406
|
include_data_ingestion: bool = False,
|
367
407
|
datastore: str | None = None,
|
408
|
+
session_type: str | None = None,
|
368
409
|
output_dir: pathlib.Path | None = None,
|
410
|
+
remote_template_path: pathlib.Path | None = None,
|
411
|
+
remote_config: dict[str, Any] | None = None,
|
369
412
|
) -> None:
|
370
413
|
"""Process the template directory and create a new project.
|
371
414
|
|
@@ -375,15 +418,33 @@ def process_template(
|
|
375
418
|
project_name: Name of the project to create
|
376
419
|
deployment_target: Optional deployment target (agent_engine or cloud_run)
|
377
420
|
include_data_ingestion: Whether to include data pipeline components
|
421
|
+
datastore: Optional datastore type for data ingestion
|
422
|
+
session_type: Optional session type for cloud_run deployment
|
378
423
|
output_dir: Optional output directory path, defaults to current directory
|
424
|
+
remote_template_path: Optional path to remote template for overlay
|
425
|
+
remote_config: Optional remote template configuration
|
379
426
|
"""
|
380
427
|
logging.debug(f"Processing template from {template_dir}")
|
381
428
|
logging.debug(f"Project name: {project_name}")
|
382
429
|
logging.debug(f"Include pipeline: {datastore}")
|
383
430
|
logging.debug(f"Output directory: {output_dir}")
|
384
431
|
|
385
|
-
#
|
386
|
-
|
432
|
+
# Handle remote vs local templates
|
433
|
+
is_remote = remote_template_path is not None
|
434
|
+
|
435
|
+
if is_remote:
|
436
|
+
# For remote templates, determine the base template
|
437
|
+
base_template_name = get_base_template_name(remote_config or {})
|
438
|
+
agent_path = (
|
439
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
440
|
+
/ "agents"
|
441
|
+
/ base_template_name
|
442
|
+
)
|
443
|
+
logging.debug(f"Remote template using base: {base_template_name}")
|
444
|
+
else:
|
445
|
+
# For local templates, use the existing logic
|
446
|
+
agent_path = template_dir.parent # Get parent of template dir
|
447
|
+
|
387
448
|
logging.debug(f"agent path: {agent_path}")
|
388
449
|
logging.debug(f"agent path exists: {agent_path.exists()}")
|
389
450
|
logging.debug(
|
@@ -456,7 +517,7 @@ def process_template(
|
|
456
517
|
copy_frontend_files(frontend_type, project_template)
|
457
518
|
logging.debug(f"4. Processed frontend files for type: {frontend_type}")
|
458
519
|
|
459
|
-
# 5.
|
520
|
+
# 5. Copy agent-specific files to override base template
|
460
521
|
if agent_path.exists():
|
461
522
|
for folder in OVERWRITE_FOLDERS:
|
462
523
|
agent_folder = agent_path / folder
|
@@ -467,11 +528,27 @@ def process_template(
|
|
467
528
|
agent_folder, project_folder, agent_name, overwrite=True
|
468
529
|
)
|
469
530
|
|
531
|
+
# 6. Finally, overlay remote template files if present
|
532
|
+
if is_remote and remote_template_path:
|
533
|
+
logging.debug(
|
534
|
+
f"6. Overlaying remote template files from {remote_template_path}"
|
535
|
+
)
|
536
|
+
copy_files(
|
537
|
+
remote_template_path,
|
538
|
+
project_template,
|
539
|
+
agent_name=agent_name,
|
540
|
+
overwrite=True,
|
541
|
+
)
|
542
|
+
|
470
543
|
# Load and validate template config first
|
471
|
-
|
472
|
-
|
544
|
+
if is_remote:
|
545
|
+
config = remote_config or {}
|
546
|
+
else:
|
547
|
+
template_path = pathlib.Path(template_dir)
|
548
|
+
config = load_template_config(template_path)
|
549
|
+
|
473
550
|
if not config:
|
474
|
-
raise ValueError(
|
551
|
+
raise ValueError("Could not load template config")
|
475
552
|
|
476
553
|
# Validate deployment target
|
477
554
|
available_targets = config.get("settings", {}).get("deployment_targets", [])
|
@@ -483,8 +560,8 @@ def process_template(
|
|
483
560
|
f"Invalid deployment target '{deployment_target}'. Available targets: {available_targets}"
|
484
561
|
)
|
485
562
|
|
486
|
-
#
|
487
|
-
template_config =
|
563
|
+
# Use the already loaded config
|
564
|
+
template_config = config
|
488
565
|
|
489
566
|
# Check if data processing should be included
|
490
567
|
if include_data_ingestion and datastore:
|
@@ -527,6 +604,7 @@ def process_template(
|
|
527
604
|
"settings": settings,
|
528
605
|
"tags": tags,
|
529
606
|
"deployment_target": deployment_target or "",
|
607
|
+
"session_type": session_type or "",
|
530
608
|
"frontend_type": frontend_type,
|
531
609
|
"extra_dependencies": [extra_deps],
|
532
610
|
"data_ingestion": include_data_ingestion,
|
@@ -591,9 +669,36 @@ def process_template(
|
|
591
669
|
file_path.unlink()
|
592
670
|
logging.debug(f"Deleted {file_path}")
|
593
671
|
|
594
|
-
#
|
595
|
-
if
|
596
|
-
#
|
672
|
+
# Handle pyproject.toml and uv.lock files
|
673
|
+
if is_remote and remote_template_path:
|
674
|
+
# For remote templates, use their pyproject.toml and uv.lock if they exist
|
675
|
+
remote_pyproject = remote_template_path / "pyproject.toml"
|
676
|
+
remote_uv_lock = remote_template_path / "uv.lock"
|
677
|
+
|
678
|
+
if remote_pyproject.exists():
|
679
|
+
shutil.copy2(
|
680
|
+
remote_pyproject, final_destination / "pyproject.toml"
|
681
|
+
)
|
682
|
+
logging.debug("Used pyproject.toml from remote template")
|
683
|
+
|
684
|
+
if remote_uv_lock.exists():
|
685
|
+
shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
|
686
|
+
logging.debug("Used uv.lock from remote template")
|
687
|
+
elif deployment_target:
|
688
|
+
# Fallback to base template lock file
|
689
|
+
base_template_name = get_base_template_name(remote_config or {})
|
690
|
+
lock_path = (
|
691
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
692
|
+
/ "src"
|
693
|
+
/ "resources"
|
694
|
+
/ "locks"
|
695
|
+
/ f"uv-{base_template_name}-{deployment_target}.lock"
|
696
|
+
)
|
697
|
+
if lock_path.exists():
|
698
|
+
shutil.copy2(lock_path, final_destination / "uv.lock")
|
699
|
+
logging.debug(f"Used fallback lock file from {lock_path}")
|
700
|
+
elif deployment_target:
|
701
|
+
# For local templates, use the existing logic
|
597
702
|
lock_path = (
|
598
703
|
pathlib.Path(__file__).parent.parent.parent.parent
|
599
704
|
/ "src"
|
@@ -672,8 +777,12 @@ def copy_files(
|
|
672
777
|
return True
|
673
778
|
if "__pycache__" in str(path) or path.name == "__pycache__":
|
674
779
|
return True
|
780
|
+
if ".git" in path.parts:
|
781
|
+
return True
|
675
782
|
if agent_name is not None and should_exclude_path(path, agent_name):
|
676
783
|
return True
|
784
|
+
if path.is_dir() and path.name == ".template":
|
785
|
+
return True
|
677
786
|
return False
|
678
787
|
|
679
788
|
if src.is_dir():
|
@@ -12,14 +12,13 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
# mypy: disable-error-code="attr-defined"
|
15
|
+
# mypy: disable-error-code="attr-defined,arg-type"
|
16
16
|
{%- if "adk" in cookiecutter.tags %}
|
17
17
|
import copy
|
18
18
|
import datetime
|
19
19
|
import json
|
20
20
|
import logging
|
21
21
|
import os
|
22
|
-
from collections.abc import Mapping, Sequence
|
23
22
|
from typing import Any
|
24
23
|
|
25
24
|
import google.auth
|
@@ -57,7 +56,7 @@ class AgentEngineApp(AdkApp):
|
|
57
56
|
feedback_obj = Feedback.model_validate(feedback)
|
58
57
|
self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
|
59
58
|
|
60
|
-
def register_operations(self) ->
|
59
|
+
def register_operations(self) -> dict[str, list[str]]:
|
61
60
|
"""Registers the operations of the Agent.
|
62
61
|
|
63
62
|
Extends the base operations to include feedback registration functionality.
|
@@ -69,9 +68,10 @@ class AgentEngineApp(AdkApp):
|
|
69
68
|
def clone(self) -> "AgentEngineApp":
|
70
69
|
"""Returns a clone of the ADK application."""
|
71
70
|
template_attributes = self._tmpl_attrs
|
71
|
+
|
72
72
|
return self.__class__(
|
73
|
-
agent=copy.deepcopy(template_attributes
|
74
|
-
enable_tracing=template_attributes.get("enable_tracing"),
|
73
|
+
agent=copy.deepcopy(template_attributes["agent"]),
|
74
|
+
enable_tracing=bool(template_attributes.get("enable_tracing", False)),
|
75
75
|
session_service_builder=template_attributes.get("session_service_builder"),
|
76
76
|
artifact_service_builder=template_attributes.get(
|
77
77
|
"artifact_service_builder"
|
@@ -83,7 +83,7 @@ import datetime
|
|
83
83
|
import json
|
84
84
|
import logging
|
85
85
|
import os
|
86
|
-
from collections.abc import Iterable, Mapping
|
86
|
+
from collections.abc import Iterable, Mapping
|
87
87
|
from typing import (
|
88
88
|
Any,
|
89
89
|
)
|
@@ -182,7 +182,7 @@ class AgentEngineApp:
|
|
182
182
|
feedback_obj = Feedback.model_validate(feedback)
|
183
183
|
self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
|
184
184
|
|
185
|
-
def register_operations(self) ->
|
185
|
+
def register_operations(self) -> dict[str, list[str]]:
|
186
186
|
"""Registers the operations of the Agent.
|
187
187
|
|
188
188
|
This mapping defines how different operation modes (e.g., "", "stream")
|
@@ -18,12 +18,7 @@ Follow these steps to execute load tests:
|
|
18
18
|
It's recommended to use a separate terminal tab and create a virtual environment for Locust to avoid conflicts with your application's Python environment.
|
19
19
|
|
20
20
|
```bash
|
21
|
-
|
22
|
-
python3 -m venv .locust_env
|
23
|
-
source .locust_env/bin/activate
|
24
|
-
|
25
|
-
# Install required packages
|
26
|
-
pip install locust==2.31.1 "google-cloud-aiplatform[langchain,reasoningengine]>=1.77.0"
|
21
|
+
python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1
|
27
22
|
```
|
28
23
|
|
29
24
|
**3. Execute the Load Test:**
|
@@ -91,15 +91,25 @@ class ChatStreamUser(HttpUser):
|
|
91
91
|
events = []
|
92
92
|
for line in response.iter_lines():
|
93
93
|
if line:
|
94
|
-
|
95
|
-
events.append(
|
94
|
+
line_str = line.decode("utf-8")
|
95
|
+
events.append(line_str)
|
96
|
+
|
97
|
+
if "429 Too Many Requests" in line_str:
|
98
|
+
self.environment.events.request.fire(
|
99
|
+
request_type="POST",
|
100
|
+
name=f"{url_path} rate_limited 429s",
|
101
|
+
response_time=0,
|
102
|
+
response_length=len(line),
|
103
|
+
response=response,
|
104
|
+
context={},
|
105
|
+
)
|
96
106
|
end_time = time.time()
|
97
107
|
total_time = end_time - start_time
|
98
108
|
self.environment.events.request.fire(
|
99
109
|
request_type="POST",
|
100
110
|
name="/stream_messages end",
|
101
111
|
response_time=total_time * 1000, # Convert to milliseconds
|
102
|
-
response_length=len(
|
112
|
+
response_length=len(events),
|
103
113
|
response=response,
|
104
114
|
context={},
|
105
115
|
)
|
@@ -43,11 +43,29 @@ provider.add_span_processor(processor)
|
|
43
43
|
trace.set_tracer_provider(provider)
|
44
44
|
|
45
45
|
AGENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
46
|
+
|
47
|
+
{%- if cookiecutter.session_type == "alloydb" %}
|
48
|
+
# AlloyDB session configuration
|
49
|
+
db_user = os.environ.get("DB_USER", "postgres")
|
50
|
+
db_name = os.environ.get("DB_NAME", "postgres")
|
51
|
+
db_pass = os.environ.get("DB_PASS")
|
52
|
+
db_host = os.environ.get("DB_HOST")
|
53
|
+
|
54
|
+
# Set session_service_uri if database credentials are available
|
55
|
+
session_service_uri = None
|
56
|
+
if db_host and db_pass:
|
57
|
+
session_service_uri = f"postgresql://{db_user}:{db_pass}@{db_host}:5432/{db_name}"
|
58
|
+
{%- else %}
|
59
|
+
# In-memory session configuration - no persistent storage
|
60
|
+
session_service_uri = None
|
61
|
+
{%- endif %}
|
62
|
+
|
46
63
|
app: FastAPI = get_fast_api_app(
|
47
64
|
agents_dir=AGENT_DIR,
|
48
65
|
web=True,
|
49
66
|
artifact_service_uri=bucket_name,
|
50
67
|
allow_origins=allow_origins,
|
68
|
+
session_service_uri=session_service_uri,
|
51
69
|
)
|
52
70
|
app.title = "{{cookiecutter.project_name}}"
|
53
71
|
app.description = "API for interacting with the Agent {{cookiecutter.project_name}}"
|
@@ -0,0 +1,231 @@
|
|
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
|
+
project_id = var.dev_project_id
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
{%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
|
22
|
+
|
23
|
+
# VPC Network for AlloyDB
|
24
|
+
resource "google_compute_network" "default" {
|
25
|
+
name = "${var.project_name}-alloydb-network"
|
26
|
+
project = var.dev_project_id
|
27
|
+
auto_create_subnetworks = false
|
28
|
+
depends_on = [resource.google_project_service.services]
|
29
|
+
}
|
30
|
+
|
31
|
+
# Subnet for AlloyDB
|
32
|
+
resource "google_compute_subnetwork" "default" {
|
33
|
+
name = "${var.project_name}-alloydb-network"
|
34
|
+
ip_cidr_range = "10.0.0.0/24"
|
35
|
+
region = var.region
|
36
|
+
network = google_compute_network.default.id
|
37
|
+
project = var.dev_project_id
|
38
|
+
|
39
|
+
# This is required for Cloud Run VPC connectors
|
40
|
+
purpose = "PRIVATE"
|
41
|
+
|
42
|
+
private_ip_google_access = true
|
43
|
+
}
|
44
|
+
|
45
|
+
# Private IP allocation for AlloyDB
|
46
|
+
resource "google_compute_global_address" "private_ip_alloc" {
|
47
|
+
name = "${var.project_name}-private-ip"
|
48
|
+
project = var.dev_project_id
|
49
|
+
address_type = "INTERNAL"
|
50
|
+
purpose = "VPC_PEERING"
|
51
|
+
prefix_length = 16
|
52
|
+
network = google_compute_network.default.id
|
53
|
+
|
54
|
+
depends_on = [resource.google_project_service.services]
|
55
|
+
}
|
56
|
+
|
57
|
+
# VPC connection for AlloyDB
|
58
|
+
resource "google_service_networking_connection" "vpc_connection" {
|
59
|
+
network = google_compute_network.default.id
|
60
|
+
service = "servicenetworking.googleapis.com"
|
61
|
+
reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name]
|
62
|
+
}
|
63
|
+
|
64
|
+
# AlloyDB Cluster
|
65
|
+
resource "google_alloydb_cluster" "session_db_cluster" {
|
66
|
+
project = var.dev_project_id
|
67
|
+
cluster_id = "${var.project_name}-alloydb-cluster"
|
68
|
+
location = var.region
|
69
|
+
|
70
|
+
network_config {
|
71
|
+
network = google_compute_network.default.id
|
72
|
+
}
|
73
|
+
|
74
|
+
depends_on = [
|
75
|
+
google_service_networking_connection.vpc_connection
|
76
|
+
]
|
77
|
+
}
|
78
|
+
|
79
|
+
# AlloyDB Instance
|
80
|
+
resource "google_alloydb_instance" "session_db_instance" {
|
81
|
+
cluster = google_alloydb_cluster.session_db_cluster.name
|
82
|
+
instance_id = "${var.project_name}-alloydb-instance"
|
83
|
+
instance_type = "PRIMARY"
|
84
|
+
|
85
|
+
availability_type = "REGIONAL" # Regional redundancy
|
86
|
+
|
87
|
+
machine_config {
|
88
|
+
cpu_count = 2
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
# Generate a random password for the database user
|
93
|
+
resource "random_password" "db_password" {
|
94
|
+
length = 16
|
95
|
+
special = true
|
96
|
+
override_special = "!#$%&*()-_=+[]{}<>:?"
|
97
|
+
}
|
98
|
+
|
99
|
+
# Store the password in Secret Manager
|
100
|
+
resource "google_secret_manager_secret" "db_password" {
|
101
|
+
project = var.dev_project_id
|
102
|
+
secret_id = "${var.project_name}-db-password"
|
103
|
+
|
104
|
+
replication {
|
105
|
+
auto {}
|
106
|
+
}
|
107
|
+
|
108
|
+
depends_on = [resource.google_project_service.services]
|
109
|
+
}
|
110
|
+
|
111
|
+
resource "google_secret_manager_secret_version" "db_password" {
|
112
|
+
secret = google_secret_manager_secret.db_password.id
|
113
|
+
secret_data = random_password.db_password.result
|
114
|
+
}
|
115
|
+
|
116
|
+
resource "google_alloydb_user" "db_user" {
|
117
|
+
cluster = google_alloydb_cluster.session_db_cluster.name
|
118
|
+
user_id = "postgres"
|
119
|
+
user_type = "ALLOYDB_BUILT_IN"
|
120
|
+
password = random_password.db_password.result
|
121
|
+
database_roles = ["alloydbsuperuser"]
|
122
|
+
|
123
|
+
depends_on = [google_alloydb_instance.session_db_instance]
|
124
|
+
}
|
125
|
+
|
126
|
+
{%- endif %}
|
127
|
+
|
128
|
+
|
129
|
+
resource "google_cloud_run_v2_service" "app" {
|
130
|
+
name = var.project_name
|
131
|
+
location = var.region
|
132
|
+
project = var.dev_project_id
|
133
|
+
deletion_protection = false
|
134
|
+
ingress = "INGRESS_TRAFFIC_ALL"
|
135
|
+
|
136
|
+
template {
|
137
|
+
containers {
|
138
|
+
image = "us-docker.pkg.dev/cloudrun/container/hello"
|
139
|
+
|
140
|
+
resources {
|
141
|
+
limits = {
|
142
|
+
cpu = "4"
|
143
|
+
memory = "8Gi"
|
144
|
+
}
|
145
|
+
}
|
146
|
+
{%- if cookiecutter.data_ingestion %}
|
147
|
+
{%- if cookiecutter.datastore_type == "vertex_ai_search" %}
|
148
|
+
|
149
|
+
env {
|
150
|
+
name = "DATA_STORE_ID"
|
151
|
+
value = resource.google_discovery_engine_data_store.data_store_dev.data_store_id
|
152
|
+
}
|
153
|
+
|
154
|
+
env {
|
155
|
+
name = "DATA_STORE_REGION"
|
156
|
+
value = var.data_store_region
|
157
|
+
}
|
158
|
+
{%- elif cookiecutter.datastore_type == "vertex_ai_vector_search" %}
|
159
|
+
env {
|
160
|
+
name = "VECTOR_SEARCH_INDEX"
|
161
|
+
value = resource.google_vertex_ai_index.vector_search_index.id
|
162
|
+
}
|
163
|
+
|
164
|
+
env {
|
165
|
+
name = "VECTOR_SEARCH_INDEX_ENDPOINT"
|
166
|
+
value = resource.google_vertex_ai_index_endpoint.vector_search_index_endpoint.id
|
167
|
+
}
|
168
|
+
|
169
|
+
env {
|
170
|
+
name = "VECTOR_SEARCH_BUCKET"
|
171
|
+
value = "gs://${resource.google_storage_bucket.data_ingestion_PIPELINE_GCS_ROOT.name}"
|
172
|
+
}
|
173
|
+
{%- endif %}
|
174
|
+
{%- endif %}
|
175
|
+
|
176
|
+
{%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
|
177
|
+
|
178
|
+
env {
|
179
|
+
name = "DB_HOST"
|
180
|
+
value = google_alloydb_instance.session_db_instance.ip_address
|
181
|
+
}
|
182
|
+
|
183
|
+
env {
|
184
|
+
name = "DB_PASS"
|
185
|
+
value_source {
|
186
|
+
secret_key_ref {
|
187
|
+
secret = google_secret_manager_secret.db_password.secret_id
|
188
|
+
version = "latest"
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
{%- endif %}
|
193
|
+
}
|
194
|
+
|
195
|
+
service_account = google_service_account.cloud_run_app_sa.email
|
196
|
+
max_instance_request_concurrency = 40
|
197
|
+
|
198
|
+
scaling {
|
199
|
+
min_instance_count = 1
|
200
|
+
max_instance_count = 10
|
201
|
+
}
|
202
|
+
|
203
|
+
session_affinity = true
|
204
|
+
|
205
|
+
{%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
|
206
|
+
# VPC access for AlloyDB connectivity
|
207
|
+
vpc_access {
|
208
|
+
network_interfaces {
|
209
|
+
network = google_compute_network.default.id
|
210
|
+
subnetwork = google_compute_subnetwork.default.id
|
211
|
+
}
|
212
|
+
}
|
213
|
+
{%- endif %}
|
214
|
+
}
|
215
|
+
|
216
|
+
traffic {
|
217
|
+
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
|
218
|
+
percent = 100
|
219
|
+
}
|
220
|
+
|
221
|
+
# This lifecycle block prevents Terraform from overwriting the container image when it's
|
222
|
+
# updated by Cloud Run deployments outside of Terraform (e.g., via CI/CD pipelines)
|
223
|
+
lifecycle {
|
224
|
+
ignore_changes = [
|
225
|
+
template[0].containers[0].image,
|
226
|
+
]
|
227
|
+
}
|
228
|
+
|
229
|
+
# Make dependencies conditional to avoid errors.
|
230
|
+
depends_on = [resource.google_project_service.services]
|
231
|
+
}
|