agent-starter-pack 0.11.1__py3-none-any.whl → 0.12.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.11.1.dist-info → agent_starter_pack-0.12.0.dist-info}/METADATA +1 -1
- {agent_starter_pack-0.11.1.dist-info → agent_starter_pack-0.12.0.dist-info}/RECORD +45 -70
- agents/adk_base/app/__init__.py +17 -0
- agents/adk_base/notebooks/adk_app_testing.ipynb +4 -1
- agents/adk_base/tests/integration/test_agent.py +1 -1
- agents/agentic_rag/app/__init__.py +17 -0
- agents/agentic_rag/app/agent.py +2 -2
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +4 -1
- agents/agentic_rag/tests/integration/test_agent.py +2 -2
- agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
- agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
- agents/live_api/tests/unit/test_server.py +6 -6
- llm.txt +15 -4
- src/base_template/Makefile +5 -5
- src/base_template/README.md +4 -4
- src/base_template/deployment/terraform/dev/variables.tf +3 -2
- src/base_template/deployment/terraform/iam.tf +10 -34
- src/base_template/deployment/terraform/variables.tf +3 -3
- src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +2 -2
- src/base_template/pyproject.toml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -1
- src/cli/commands/create.py +25 -2
- src/cli/commands/enhance.py +94 -15
- src/cli/commands/list.py +1 -1
- src/cli/commands/setup_cicd.py +50 -41
- src/cli/utils/remote_template.py +1 -1
- src/cli/utils/template.py +120 -41
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +3 -3
- src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py +10 -10
- src/deployment_targets/cloud_run/Dockerfile +2 -2
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +3 -3
- src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +2 -2
- {agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py +186 -7
- src/resources/docs/adk-cheatsheet.md +3 -3
- src/base_template/app/__init__.py +0 -3
- src/deployment_targets/cloud_run/app/server.py +0 -206
- src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
- src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
- src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
- src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
- src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
- src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
- src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
- src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
- src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
- src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
- src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
- src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
- src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
- src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
- src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
- {agent_starter_pack-0.11.1.dist-info → agent_starter_pack-0.12.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.11.1.dist-info → agent_starter_pack-0.12.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.11.1.dist-info → agent_starter_pack-0.12.0.dist-info}/licenses/LICENSE +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
src/cli/utils/template.py
CHANGED
@@ -34,9 +34,6 @@ from .remote_template import (
|
|
34
34
|
render_and_merge_makefiles,
|
35
35
|
)
|
36
36
|
|
37
|
-
ADK_FILES = ["app/__init__.py"]
|
38
|
-
NON_ADK_FILES: list[str] = []
|
39
|
-
|
40
37
|
|
41
38
|
@dataclass
|
42
39
|
class TemplateConfig:
|
@@ -72,7 +69,11 @@ class TemplateConfig:
|
|
72
69
|
raise ValueError(f"Error loading template config: {err}") from err
|
73
70
|
|
74
71
|
|
75
|
-
|
72
|
+
def get_overwrite_folders(agent_directory: str) -> list[str]:
|
73
|
+
"""Get folders to overwrite with configurable agent directory."""
|
74
|
+
return [agent_directory, "frontend", "tests", "notebooks"]
|
75
|
+
|
76
|
+
|
76
77
|
TEMPLATE_CONFIG_FILE = "templateconfig.yaml"
|
77
78
|
DEPLOYMENT_FOLDERS = ["cloud_run", "agent_engine"]
|
78
79
|
DEFAULT_FRONTEND = "streamlit"
|
@@ -446,6 +447,7 @@ def process_template(
|
|
446
447
|
remote_template_path: pathlib.Path | None = None,
|
447
448
|
remote_config: dict[str, Any] | None = None,
|
448
449
|
in_folder: bool = False,
|
450
|
+
cli_overrides: dict[str, Any] | None = None,
|
449
451
|
) -> None:
|
450
452
|
"""Process the template directory and create a new project.
|
451
453
|
|
@@ -462,12 +464,25 @@ def process_template(
|
|
462
464
|
remote_template_path: Optional path to remote template for overlay
|
463
465
|
remote_config: Optional remote template configuration
|
464
466
|
in_folder: Whether to template directly into the output directory instead of creating a subdirectory
|
467
|
+
cli_overrides: Optional CLI override values that should take precedence over template config
|
465
468
|
"""
|
466
469
|
logging.debug(f"Processing template from {template_dir}")
|
467
470
|
logging.debug(f"Project name: {project_name}")
|
468
471
|
logging.debug(f"Include pipeline: {datastore}")
|
469
472
|
logging.debug(f"Output directory: {output_dir}")
|
470
473
|
|
474
|
+
def get_agent_directory(
|
475
|
+
template_config: dict[str, Any], cli_overrides: dict[str, Any] | None = None
|
476
|
+
) -> str:
|
477
|
+
"""Get agent directory with CLI override support."""
|
478
|
+
if (
|
479
|
+
cli_overrides
|
480
|
+
and "settings" in cli_overrides
|
481
|
+
and "agent_directory" in cli_overrides["settings"]
|
482
|
+
):
|
483
|
+
return cli_overrides["settings"]["agent_directory"]
|
484
|
+
return template_config.get("settings", {}).get("agent_directory", "app")
|
485
|
+
|
471
486
|
# Handle remote vs local templates
|
472
487
|
is_remote = remote_template_path is not None
|
473
488
|
|
@@ -519,7 +534,21 @@ def process_template(
|
|
519
534
|
base_template_path = (
|
520
535
|
pathlib.Path(__file__).parent.parent.parent / "base_template"
|
521
536
|
)
|
522
|
-
|
537
|
+
# Get agent directory from config early for use in file copying
|
538
|
+
# Load config early to get agent_directory
|
539
|
+
if is_remote:
|
540
|
+
early_config = remote_config or {}
|
541
|
+
else:
|
542
|
+
template_path = pathlib.Path(template_dir)
|
543
|
+
early_config = load_template_config(template_path)
|
544
|
+
agent_directory = get_agent_directory(early_config, cli_overrides)
|
545
|
+
copy_files(
|
546
|
+
base_template_path,
|
547
|
+
project_template,
|
548
|
+
agent_name,
|
549
|
+
overwrite=True,
|
550
|
+
agent_directory=agent_directory,
|
551
|
+
)
|
523
552
|
logging.debug(f"1. Copied base template from {base_template_path}")
|
524
553
|
|
525
554
|
# 2. Process deployment target if specified
|
@@ -535,6 +564,7 @@ def process_template(
|
|
535
564
|
project_template,
|
536
565
|
agent_name=agent_name,
|
537
566
|
overwrite=True,
|
567
|
+
agent_directory=agent_directory,
|
538
568
|
)
|
539
569
|
logging.debug(
|
540
570
|
f"2. Processed deployment files for target: {deployment_target}"
|
@@ -556,27 +586,11 @@ def process_template(
|
|
556
586
|
copy_frontend_files(frontend_type, project_template)
|
557
587
|
logging.debug(f"4. Processed frontend files for type: {frontend_type}")
|
558
588
|
|
559
|
-
#
|
560
|
-
|
561
|
-
for folder in OVERWRITE_FOLDERS:
|
562
|
-
agent_folder = agent_path / folder
|
563
|
-
project_folder = project_template / folder
|
564
|
-
if agent_folder.exists():
|
565
|
-
logging.debug(f"5. Copying agent folder {folder} with override")
|
566
|
-
copy_files(
|
567
|
-
agent_folder, project_folder, agent_name, overwrite=True
|
568
|
-
)
|
569
|
-
|
570
|
-
# 6. Finally, overlay remote template files if present
|
589
|
+
# 6. Skip remote template files during cookiecutter processing
|
590
|
+
# Remote files will be copied after cookiecutter to avoid Jinja conflicts
|
571
591
|
if is_remote and remote_template_path:
|
572
592
|
logging.debug(
|
573
|
-
|
574
|
-
)
|
575
|
-
copy_files(
|
576
|
-
remote_template_path,
|
577
|
-
project_template,
|
578
|
-
agent_name=agent_name,
|
579
|
-
overwrite=True,
|
593
|
+
"6. Skipping remote template files during cookiecutter processing - will copy after templating"
|
580
594
|
)
|
581
595
|
|
582
596
|
# Load and validate template config first
|
@@ -602,6 +616,45 @@ def process_template(
|
|
602
616
|
# Use the already loaded config
|
603
617
|
template_config = config
|
604
618
|
|
619
|
+
# 5. Copy agent-specific files to override base template (using final config)
|
620
|
+
if agent_path.exists():
|
621
|
+
agent_directory = get_agent_directory(template_config, cli_overrides)
|
622
|
+
|
623
|
+
# Get the template's default agent directory (usually "app")
|
624
|
+
template_agent_directory = template_config.get("settings", {}).get(
|
625
|
+
"agent_directory", "app"
|
626
|
+
)
|
627
|
+
|
628
|
+
# Copy agent directory (always from "app" to target directory)
|
629
|
+
source_agent_folder = agent_path / template_agent_directory
|
630
|
+
target_agent_folder = project_template / agent_directory
|
631
|
+
if source_agent_folder.exists():
|
632
|
+
logging.debug(
|
633
|
+
f"5. Copying agent folder {template_agent_directory} -> {agent_directory} with override"
|
634
|
+
)
|
635
|
+
copy_files(
|
636
|
+
source_agent_folder,
|
637
|
+
target_agent_folder,
|
638
|
+
agent_name,
|
639
|
+
overwrite=True,
|
640
|
+
agent_directory=agent_directory,
|
641
|
+
)
|
642
|
+
|
643
|
+
# Copy other folders (frontend, tests, notebooks)
|
644
|
+
other_folders = ["frontend", "tests", "notebooks"]
|
645
|
+
for folder in other_folders:
|
646
|
+
agent_folder = agent_path / folder
|
647
|
+
project_folder = project_template / folder
|
648
|
+
if agent_folder.exists():
|
649
|
+
logging.debug(f"5. Copying {folder} folder with override")
|
650
|
+
copy_files(
|
651
|
+
agent_folder,
|
652
|
+
project_folder,
|
653
|
+
agent_name,
|
654
|
+
overwrite=True,
|
655
|
+
agent_directory=agent_directory,
|
656
|
+
)
|
657
|
+
|
605
658
|
# Check if data processing should be included
|
606
659
|
if include_data_ingestion and datastore:
|
607
660
|
logging.debug(
|
@@ -649,6 +702,7 @@ def process_template(
|
|
649
702
|
"extra_dependencies": [extra_deps],
|
650
703
|
"data_ingestion": include_data_ingestion,
|
651
704
|
"datastore_type": datastore if datastore else "",
|
705
|
+
"agent_directory": get_agent_directory(template_config, cli_overrides),
|
652
706
|
"adk_cheatsheet": adk_cheatsheet_content,
|
653
707
|
"llm_txt": llm_txt_content,
|
654
708
|
"_copy_without_render": [
|
@@ -664,7 +718,9 @@ def process_template(
|
|
664
718
|
"*templates.py", # Don't render templates files
|
665
719
|
"Makefile", # Don't render Makefile - handled by render_and_merge_makefiles
|
666
720
|
# Don't render agent.py unless it's agentic_rag
|
667
|
-
"
|
721
|
+
f"{get_agent_directory(template_config, cli_overrides)}/agent.py"
|
722
|
+
if agent_name != "agentic_rag"
|
723
|
+
else "",
|
668
724
|
],
|
669
725
|
}
|
670
726
|
|
@@ -690,6 +746,21 @@ def process_template(
|
|
690
746
|
)
|
691
747
|
logging.debug("Template processing completed successfully")
|
692
748
|
|
749
|
+
# Now overlay remote template files if present (after cookiecutter processing)
|
750
|
+
if is_remote and remote_template_path:
|
751
|
+
generated_project_dir = temp_path / project_name
|
752
|
+
logging.debug(
|
753
|
+
f"Copying remote template files from {remote_template_path} to {generated_project_dir}"
|
754
|
+
)
|
755
|
+
copy_files(
|
756
|
+
remote_template_path,
|
757
|
+
generated_project_dir,
|
758
|
+
agent_name=agent_name,
|
759
|
+
overwrite=True,
|
760
|
+
agent_directory=agent_directory,
|
761
|
+
)
|
762
|
+
logging.debug("Remote template files copied successfully")
|
763
|
+
|
693
764
|
# Move the generated project to the final destination
|
694
765
|
generated_project_dir = temp_path / project_name
|
695
766
|
|
@@ -842,15 +913,7 @@ def process_template(
|
|
842
913
|
)
|
843
914
|
|
844
915
|
# Delete appropriate files based on ADK tag
|
845
|
-
|
846
|
-
files_to_delete = [final_destination / f for f in NON_ADK_FILES]
|
847
|
-
else:
|
848
|
-
files_to_delete = [final_destination / f for f in ADK_FILES]
|
849
|
-
|
850
|
-
for file_path in files_to_delete:
|
851
|
-
if file_path.exists():
|
852
|
-
file_path.unlink()
|
853
|
-
logging.debug(f"Deleted {file_path}")
|
916
|
+
agent_directory = get_agent_directory(template_config, cli_overrides)
|
854
917
|
|
855
918
|
# Clean up unused_* files and directories created by conditional templates
|
856
919
|
import glob
|
@@ -923,11 +986,15 @@ def process_template(
|
|
923
986
|
os.chdir(original_dir)
|
924
987
|
|
925
988
|
|
926
|
-
def should_exclude_path(
|
989
|
+
def should_exclude_path(
|
990
|
+
path: pathlib.Path, agent_name: str, agent_directory: str = "app"
|
991
|
+
) -> bool:
|
927
992
|
"""Determine if a path should be excluded based on the agent type."""
|
928
993
|
if agent_name == "live_api":
|
929
|
-
# Exclude the unit test utils folder and
|
930
|
-
if "tests/unit/test_utils" in str(path) or "
|
994
|
+
# Exclude the unit test utils folder and agent utils folder for live_api
|
995
|
+
if "tests/unit/test_utils" in str(path) or f"{agent_directory}/utils" in str(
|
996
|
+
path
|
997
|
+
):
|
931
998
|
logging.debug(f"Excluding path for live_api: {path}")
|
932
999
|
return True
|
933
1000
|
return False
|
@@ -938,6 +1005,7 @@ def copy_files(
|
|
938
1005
|
dst: pathlib.Path,
|
939
1006
|
agent_name: str | None = None,
|
940
1007
|
overwrite: bool = False,
|
1008
|
+
agent_directory: str = "app",
|
941
1009
|
) -> None:
|
942
1010
|
"""
|
943
1011
|
Copy files with configurable behavior for exclusions and overwrites.
|
@@ -947,6 +1015,7 @@ def copy_files(
|
|
947
1015
|
dst: Destination path
|
948
1016
|
agent_name: Name of the agent (for agent-specific exclusions)
|
949
1017
|
overwrite: Whether to overwrite existing files (True) or skip them (False)
|
1018
|
+
agent_directory: Name of the agent directory (for agent-specific exclusions)
|
950
1019
|
"""
|
951
1020
|
|
952
1021
|
def should_skip(path: pathlib.Path) -> bool:
|
@@ -957,7 +1026,9 @@ def copy_files(
|
|
957
1026
|
return True
|
958
1027
|
if ".git" in path.parts:
|
959
1028
|
return True
|
960
|
-
if agent_name is not None and should_exclude_path(
|
1029
|
+
if agent_name is not None and should_exclude_path(
|
1030
|
+
path, agent_name, agent_directory
|
1031
|
+
):
|
961
1032
|
return True
|
962
1033
|
if path.is_dir() and path.name == ".template":
|
963
1034
|
return True
|
@@ -970,9 +1041,10 @@ def copy_files(
|
|
970
1041
|
if should_skip(item):
|
971
1042
|
logging.debug(f"Skipping file/directory: {item}")
|
972
1043
|
continue
|
1044
|
+
|
973
1045
|
d = dst / item.name
|
974
1046
|
if item.is_dir():
|
975
|
-
copy_files(item, d, agent_name, overwrite)
|
1047
|
+
copy_files(item, d, agent_name, overwrite, agent_directory)
|
976
1048
|
else:
|
977
1049
|
if overwrite or not d.exists():
|
978
1050
|
logging.debug(f"Copying file: {item} -> {d}")
|
@@ -1012,7 +1084,10 @@ def copy_frontend_files(frontend_type: str, project_template: pathlib.Path) -> N
|
|
1012
1084
|
|
1013
1085
|
|
1014
1086
|
def copy_deployment_files(
|
1015
|
-
deployment_target: str,
|
1087
|
+
deployment_target: str,
|
1088
|
+
agent_name: str,
|
1089
|
+
project_template: pathlib.Path,
|
1090
|
+
agent_directory: str = "app",
|
1016
1091
|
) -> None:
|
1017
1092
|
"""Copy files from the specified deployment target folder."""
|
1018
1093
|
if not deployment_target:
|
@@ -1028,7 +1103,11 @@ def copy_deployment_files(
|
|
1028
1103
|
logging.debug(f"Copying deployment files from {deployment_path}")
|
1029
1104
|
# Pass agent_name to respect agent-specific exclusions
|
1030
1105
|
copy_files(
|
1031
|
-
deployment_path,
|
1106
|
+
deployment_path,
|
1107
|
+
project_template,
|
1108
|
+
agent_name=agent_name,
|
1109
|
+
overwrite=True,
|
1110
|
+
agent_directory=agent_directory,
|
1032
1111
|
)
|
1033
1112
|
else:
|
1034
1113
|
logging.warning(f"Deployment target directory not found: {deployment_path}")
|
@@ -18,11 +18,11 @@ import pytest
|
|
18
18
|
{%- if "adk" in cookiecutter.tags %}
|
19
19
|
from google.adk.events.event import Event
|
20
20
|
|
21
|
-
from
|
22
|
-
from
|
21
|
+
from {{cookiecutter.agent_directory}}.agent import root_agent
|
22
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
23
23
|
{%- else %}
|
24
24
|
|
25
|
-
from
|
25
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
26
26
|
{%- endif %}
|
27
27
|
|
28
28
|
|
src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py
RENAMED
@@ -30,10 +30,10 @@ from opentelemetry.sdk.trace import TracerProvider, export
|
|
30
30
|
from vertexai import agent_engines
|
31
31
|
from vertexai.preview.reasoning_engines import AdkApp
|
32
32
|
|
33
|
-
from
|
34
|
-
from
|
35
|
-
from
|
36
|
-
from
|
33
|
+
from {{cookiecutter.agent_directory}}.agent import root_agent
|
34
|
+
from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
|
35
|
+
from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
|
36
|
+
from {{cookiecutter.agent_directory}}.utils.typing import Feedback
|
37
37
|
|
38
38
|
|
39
39
|
class AgentEngineApp(AdkApp):
|
@@ -95,9 +95,9 @@ from langchain_core.runnables import RunnableConfig
|
|
95
95
|
from traceloop.sdk import Instruments, Traceloop
|
96
96
|
from vertexai import agent_engines
|
97
97
|
|
98
|
-
from
|
99
|
-
from
|
100
|
-
from
|
98
|
+
from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
|
99
|
+
from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
|
100
|
+
from {{cookiecutter.agent_directory}}.utils.typing import Feedback, InputChat, dumpd, ensure_valid_config
|
101
101
|
|
102
102
|
|
103
103
|
class AgentEngineApp:
|
@@ -110,7 +110,7 @@ class AgentEngineApp:
|
|
110
110
|
def set_up(self) -> None:
|
111
111
|
"""The set_up method is used to define application initialization logic"""
|
112
112
|
# Lazy import agent at setup time to avoid deployment dependencies
|
113
|
-
from
|
113
|
+
from {{cookiecutter.agent_directory}}.agent import agent
|
114
114
|
|
115
115
|
logging_client = google_cloud_logging.Client(project=self.project_id)
|
116
116
|
self.logger = logging_client.logger(__name__)
|
@@ -206,7 +206,7 @@ def deploy_agent_engine_app(
|
|
206
206
|
location: str,
|
207
207
|
agent_name: str | None = None,
|
208
208
|
requirements_file: str = ".requirements.txt",
|
209
|
-
extra_packages: list[str] = ["./
|
209
|
+
extra_packages: list[str] = ["./{{cookiecutter.agent_directory}}"],
|
210
210
|
env_vars: dict[str, str] = {},
|
211
211
|
service_account: str | None = None,
|
212
212
|
) -> agent_engines.AgentEngine:
|
@@ -305,7 +305,7 @@ if __name__ == "__main__":
|
|
305
305
|
parser.add_argument(
|
306
306
|
"--extra-packages",
|
307
307
|
nargs="+",
|
308
|
-
default=["./
|
308
|
+
default=["./{{cookiecutter.agent_directory}}"],
|
309
309
|
help="Additional packages to include",
|
310
310
|
)
|
311
311
|
parser.add_argument(
|
@@ -20,7 +20,7 @@ WORKDIR /code
|
|
20
20
|
|
21
21
|
COPY ./pyproject.toml ./README.md ./uv.lock* ./
|
22
22
|
|
23
|
-
COPY ./
|
23
|
+
COPY ./{{cookiecutter.agent_directory}} ./{{cookiecutter.agent_directory}}
|
24
24
|
|
25
25
|
RUN uv sync --frozen
|
26
26
|
|
@@ -29,4 +29,4 @@ ENV COMMIT_SHA=${COMMIT_SHA}
|
|
29
29
|
|
30
30
|
EXPOSE 8080
|
31
31
|
|
32
|
-
CMD ["uv", "run", "uvicorn", "
|
32
|
+
CMD ["uv", "run", "uvicorn", "{{cookiecutter.agent_directory}}.server:app", "--host", "0.0.0.0", "--port", "8080"]
|
@@ -54,7 +54,7 @@ def start_server() -> subprocess.Popen[str]:
|
|
54
54
|
sys.executable,
|
55
55
|
"-m",
|
56
56
|
"uvicorn",
|
57
|
-
"
|
57
|
+
"{{cookiecutter.agent_directory}}.server:app",
|
58
58
|
"--host",
|
59
59
|
"0.0.0.0",
|
60
60
|
"--port",
|
@@ -129,7 +129,7 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
|
|
129
129
|
user_id = "test_user_123"
|
130
130
|
session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
|
131
131
|
|
132
|
-
session_url = f"{BASE_URL}/apps/
|
132
|
+
session_url = f"{BASE_URL}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
|
133
133
|
session_response = requests.post(
|
134
134
|
session_url,
|
135
135
|
headers=HEADERS,
|
@@ -142,7 +142,7 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
|
|
142
142
|
|
143
143
|
# Then send chat message
|
144
144
|
data = {
|
145
|
-
"app_name": "
|
145
|
+
"app_name": "{{cookiecutter.agent_directory}}",
|
146
146
|
"user_id": user_id,
|
147
147
|
"session_id": session_id,
|
148
148
|
"new_message": {
|
@@ -11,7 +11,7 @@ Follow these steps to execute load tests on your local machine:
|
|
11
11
|
Launch the FastAPI server in a separate terminal:
|
12
12
|
|
13
13
|
```bash
|
14
|
-
uv run uvicorn
|
14
|
+
uv run uvicorn {{cookiecutter.agent_directory}}.server:app --host 0.0.0.0 --port 8000 --reload
|
15
15
|
```
|
16
16
|
|
17
17
|
**2. (In another tab) Create virtual environment with Locust**
|
@@ -45,7 +45,7 @@ class ChatStreamUser(HttpUser):
|
|
45
45
|
user_id = f"user_{uuid.uuid4()}"
|
46
46
|
session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
|
47
47
|
|
48
|
-
session_url = f"{self.client.base_url}/apps/
|
48
|
+
session_url = f"{self.client.base_url}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
|
49
49
|
session_response = requests.post(
|
50
50
|
session_url,
|
51
51
|
headers=headers,
|
@@ -58,7 +58,7 @@ class ChatStreamUser(HttpUser):
|
|
58
58
|
|
59
59
|
# Send chat message
|
60
60
|
data = {
|
61
|
-
"app_name": "
|
61
|
+
"app_name": "{{cookiecutter.agent_directory}}",
|
62
62
|
"user_id": user_id,
|
63
63
|
"session_id": session_id,
|
64
64
|
"new_message": {
|
{agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py
RENAMED
@@ -11,7 +11,7 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
|
14
|
+
{% if cookiecutter.agent_name == "live_api" %}
|
15
15
|
import asyncio
|
16
16
|
import json
|
17
17
|
import logging
|
@@ -27,7 +27,7 @@ from google.genai.types import LiveServerToolCall
|
|
27
27
|
from pydantic import BaseModel
|
28
28
|
from websockets.exceptions import ConnectionClosedError
|
29
29
|
|
30
|
-
from
|
30
|
+
from .agent import MODEL_ID, genai_client, live_connect_config, tool_functions
|
31
31
|
|
32
32
|
app = FastAPI()
|
33
33
|
app.add_middleware(
|
@@ -197,16 +197,195 @@ class Feedback(BaseModel):
|
|
197
197
|
run_id: str
|
198
198
|
user_id: str | None
|
199
199
|
log_type: Literal["feedback"] = "feedback"
|
200
|
+
{% elif "adk" in cookiecutter.tags %}
|
201
|
+
import os
|
202
|
+
|
203
|
+
import google.auth
|
204
|
+
from fastapi import FastAPI
|
205
|
+
from google.adk.cli.fast_api import get_fast_api_app
|
206
|
+
from google.cloud import logging as google_cloud_logging
|
207
|
+
from opentelemetry import trace
|
208
|
+
from opentelemetry.sdk.trace import TracerProvider, export
|
209
|
+
{%- if cookiecutter.session_type == "agent_engine" %}
|
210
|
+
from vertexai import agent_engines
|
211
|
+
{%- endif %}
|
212
|
+
|
213
|
+
from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
|
214
|
+
from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
|
215
|
+
from {{cookiecutter.agent_directory}}.utils.typing import Feedback
|
200
216
|
|
217
|
+
_, project_id = google.auth.default()
|
218
|
+
logging_client = google_cloud_logging.Client()
|
219
|
+
logger = logging_client.logger(__name__)
|
220
|
+
allow_origins = (
|
221
|
+
os.getenv("ALLOW_ORIGINS", "").split(",") if os.getenv("ALLOW_ORIGINS") else None
|
222
|
+
)
|
223
|
+
|
224
|
+
bucket_name = f"gs://{project_id}-{{cookiecutter.project_name}}-logs-data"
|
225
|
+
create_bucket_if_not_exists(
|
226
|
+
bucket_name=bucket_name, project=project_id, location="us-central1"
|
227
|
+
)
|
228
|
+
|
229
|
+
provider = TracerProvider()
|
230
|
+
processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
|
231
|
+
provider.add_span_processor(processor)
|
232
|
+
trace.set_tracer_provider(provider)
|
233
|
+
|
234
|
+
AGENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
235
|
+
|
236
|
+
{%- if cookiecutter.session_type == "alloydb" %}
|
237
|
+
# AlloyDB session configuration
|
238
|
+
db_user = os.environ.get("DB_USER", "postgres")
|
239
|
+
db_name = os.environ.get("DB_NAME", "postgres")
|
240
|
+
db_pass = os.environ.get("DB_PASS")
|
241
|
+
db_host = os.environ.get("DB_HOST")
|
242
|
+
|
243
|
+
# Set session_service_uri if database credentials are available
|
244
|
+
session_service_uri = None
|
245
|
+
if db_host and db_pass:
|
246
|
+
session_service_uri = f"postgresql://{db_user}:{db_pass}@{db_host}:5432/{db_name}"
|
247
|
+
{%- elif cookiecutter.session_type == "agent_engine" %}
|
248
|
+
# Agent Engine session configuration
|
249
|
+
# Use environment variable for agent name, default to project name
|
250
|
+
agent_name = os.environ.get("AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}")
|
251
|
+
|
252
|
+
# Check if an agent with this name already exists
|
253
|
+
existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
|
254
|
+
|
255
|
+
if existing_agents:
|
256
|
+
# Use the existing agent
|
257
|
+
agent_engine = existing_agents[0]
|
258
|
+
else:
|
259
|
+
# Create a new agent if none exists
|
260
|
+
agent_engine = agent_engines.create(display_name=agent_name)
|
261
|
+
|
262
|
+
session_service_uri = f"agentengine://{agent_engine.resource_name}"
|
263
|
+
{%- else %}
|
264
|
+
# In-memory session configuration - no persistent storage
|
265
|
+
session_service_uri = None
|
266
|
+
{%- endif %}
|
267
|
+
|
268
|
+
app: FastAPI = get_fast_api_app(
|
269
|
+
agents_dir=AGENT_DIR,
|
270
|
+
web=True,
|
271
|
+
artifact_service_uri=bucket_name,
|
272
|
+
allow_origins=allow_origins,
|
273
|
+
session_service_uri=session_service_uri,
|
274
|
+
)
|
275
|
+
app.title = "{{cookiecutter.project_name}}"
|
276
|
+
app.description = "API for interacting with the Agent {{cookiecutter.project_name}}"
|
277
|
+
{% else %}
|
278
|
+
import logging
|
279
|
+
import os
|
280
|
+
from collections.abc import Generator
|
281
|
+
|
282
|
+
from fastapi import FastAPI
|
283
|
+
from fastapi.responses import RedirectResponse, StreamingResponse
|
284
|
+
from google.cloud import logging as google_cloud_logging
|
285
|
+
from langchain_core.runnables import RunnableConfig
|
286
|
+
from traceloop.sdk import Instruments, Traceloop
|
287
|
+
|
288
|
+
from {{cookiecutter.agent_directory}}.agent import agent
|
289
|
+
from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
|
290
|
+
from {{cookiecutter.agent_directory}}.utils.typing import Feedback, InputChat, Request, dumps, ensure_valid_config
|
291
|
+
|
292
|
+
# Initialize FastAPI app and logging
|
293
|
+
app = FastAPI(
|
294
|
+
title="{{cookiecutter.project_name}}",
|
295
|
+
description="API for interacting with the Agent {{cookiecutter.project_name}}",
|
296
|
+
)
|
297
|
+
logging_client = google_cloud_logging.Client()
|
298
|
+
logger = logging_client.logger(__name__)
|
299
|
+
|
300
|
+
# Initialize Telemetry
|
301
|
+
try:
|
302
|
+
Traceloop.init(
|
303
|
+
app_name=app.title,
|
304
|
+
disable_batch=False,
|
305
|
+
exporter=CloudTraceLoggingSpanExporter(),
|
306
|
+
instruments={Instruments.LANGCHAIN, Instruments.CREW},
|
307
|
+
)
|
308
|
+
except Exception as e:
|
309
|
+
logging.error("Failed to initialize Telemetry: %s", str(e))
|
310
|
+
|
311
|
+
|
312
|
+
def set_tracing_properties(config: RunnableConfig) -> None:
|
313
|
+
"""Sets tracing association properties for the current request.
|
314
|
+
|
315
|
+
Args:
|
316
|
+
config: Optional RunnableConfig containing request metadata
|
317
|
+
"""
|
318
|
+
Traceloop.set_association_properties(
|
319
|
+
{
|
320
|
+
"log_type": "tracing",
|
321
|
+
"run_id": str(config.get("run_id", "None")),
|
322
|
+
"user_id": config["metadata"].pop("user_id", "None"),
|
323
|
+
"session_id": config["metadata"].pop("session_id", "None"),
|
324
|
+
"commit_sha": os.environ.get("COMMIT_SHA", "None"),
|
325
|
+
}
|
326
|
+
)
|
327
|
+
|
328
|
+
|
329
|
+
def stream_messages(
|
330
|
+
input: InputChat,
|
331
|
+
config: RunnableConfig | None = None,
|
332
|
+
) -> Generator[str, None, None]:
|
333
|
+
"""Stream events in response to an input chat.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
input: The input chat messages
|
337
|
+
config: Optional configuration for the runnable
|
338
|
+
|
339
|
+
Yields:
|
340
|
+
JSON serialized event data
|
341
|
+
"""
|
342
|
+
config = ensure_valid_config(config=config)
|
343
|
+
set_tracing_properties(config)
|
344
|
+
input_dict = input.model_dump()
|
345
|
+
|
346
|
+
for data in agent.stream(input_dict, config=config, stream_mode="messages"): # type: ignore[arg-type]
|
347
|
+
yield dumps(data) + "\n"
|
348
|
+
|
349
|
+
|
350
|
+
# Routes
|
351
|
+
@app.get("/", response_class=RedirectResponse)
|
352
|
+
def redirect_root_to_docs() -> RedirectResponse:
|
353
|
+
"""Redirect the root URL to the API documentation."""
|
354
|
+
return RedirectResponse(url="/docs")
|
355
|
+
|
356
|
+
|
357
|
+
@app.post("/stream_messages")
|
358
|
+
def stream_chat_events(request: Request) -> StreamingResponse:
|
359
|
+
"""Stream chat events in response to an input request.
|
360
|
+
|
361
|
+
Args:
|
362
|
+
request: The chat request containing input and config
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
Streaming response of chat events
|
366
|
+
"""
|
367
|
+
return StreamingResponse(
|
368
|
+
stream_messages(input=request.input, config=request.config),
|
369
|
+
media_type="text/event-stream",
|
370
|
+
)
|
371
|
+
{% endif %}
|
201
372
|
|
202
373
|
@app.post("/feedback")
|
203
|
-
|
204
|
-
"""Collect and log feedback.
|
205
|
-
|
206
|
-
|
374
|
+
def collect_feedback(feedback: Feedback) -> dict[str, str]:
|
375
|
+
"""Collect and log feedback.
|
376
|
+
|
377
|
+
Args:
|
378
|
+
feedback: The feedback data to log
|
379
|
+
|
380
|
+
Returns:
|
381
|
+
Success message
|
382
|
+
"""
|
383
|
+
logger.log_struct(feedback.model_dump(), severity="INFO")
|
384
|
+
return {"status": "success"}
|
207
385
|
|
208
386
|
|
387
|
+
# Main execution
|
209
388
|
if __name__ == "__main__":
|
210
389
|
import uvicorn
|
211
390
|
|
212
|
-
uvicorn.run(app, host="0.0.0.0", port=8000
|
391
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|