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.
- agent_starter_pack/agents/{langgraph_base_react → adk_a2a_base}/.template/templateconfig.yaml +5 -12
- agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
- agent_starter_pack/{frontends/streamlit/frontend/style/app_markdown.py → agents/adk_a2a_base/app/__init__.py} +3 -23
- agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
- agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
- agent_starter_pack/agents/{crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → adk_a2a_base/notebooks/evaluating_adk_agent.ipynb} +163 -199
- agent_starter_pack/agents/adk_a2a_base/tests/integration/test_agent.py +58 -0
- agent_starter_pack/agents/adk_base/app/__init__.py +2 -2
- agent_starter_pack/agents/adk_base/app/agent.py +3 -0
- agent_starter_pack/agents/adk_base/notebooks/adk_app_testing.ipynb +13 -28
- agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
- agent_starter_pack/agents/adk_live/app/agent.py +3 -0
- agent_starter_pack/agents/agentic_rag/app/__init__.py +2 -2
- agent_starter_pack/agents/agentic_rag/app/agent.py +3 -0
- agent_starter_pack/agents/agentic_rag/notebooks/adk_app_testing.ipynb +13 -28
- agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/.template/templateconfig.yaml +12 -9
- agent_starter_pack/agents/langgraph_base/README.md +30 -0
- agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
- agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/app/agent.py +4 -4
- agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/tests/integration/test_agent.py +1 -1
- agent_starter_pack/base_template/.gitignore +4 -2
- agent_starter_pack/base_template/Makefile +110 -16
- agent_starter_pack/base_template/README.md +97 -12
- agent_starter_pack/base_template/deployment/terraform/dev/apis.tf +4 -6
- agent_starter_pack/base_template/deployment/terraform/dev/providers.tf +5 -1
- agent_starter_pack/base_template/deployment/terraform/dev/variables.tf +5 -3
- agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
- agent_starter_pack/base_template/deployment/terraform/github.tf +16 -9
- agent_starter_pack/base_template/deployment/terraform/locals.tf +7 -7
- agent_starter_pack/base_template/deployment/terraform/providers.tf +5 -1
- agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
- agent_starter_pack/base_template/deployment/terraform/storage.tf +0 -9
- agent_starter_pack/base_template/deployment/terraform/variables.tf +15 -19
- 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
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
- agent_starter_pack/base_template/pyproject.toml +5 -17
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +19 -4
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +36 -11
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +24 -5
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +44 -9
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/typing.py +4 -6
- 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
- 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
- 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
- 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
- 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
- agent_starter_pack/cli/commands/create.py +40 -4
- agent_starter_pack/cli/commands/enhance.py +1 -1
- agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
- agent_starter_pack/cli/main.py +2 -0
- agent_starter_pack/cli/utils/cicd.py +20 -4
- agent_starter_pack/cli/utils/template.py +257 -25
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +113 -16
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +2 -2
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +178 -9
- agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +193 -307
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
- 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
- agent_starter_pack/deployment_targets/cloud_run/Dockerfile +4 -1
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +85 -86
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +139 -107
- agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +228 -12
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +4 -4
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +92 -12
- agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/{server.py → fast_api_app.py} +194 -121
- agent_starter_pack/frontends/adk_live_react/frontend/package-lock.json +18 -18
- agent_starter_pack/frontends/adk_live_react/frontend/src/multimodal-live-types.ts +5 -3
- agent_starter_pack/resources/docs/adk-cheatsheet.md +198 -41
- agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
- agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +1443 -709
- agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +1058 -874
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +1443 -709
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +1058 -874
- agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +1568 -749
- agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +1123 -929
- agent_starter_pack/resources/locks/{uv-langgraph_base_react-agent_engine.lock → uv-langgraph_base-agent_engine.lock} +1714 -1689
- agent_starter_pack/resources/locks/{uv-langgraph_base_react-cloud_run.lock → uv-langgraph_base-cloud_run.lock} +1285 -2374
- agent_starter_pack/utils/watch_and_rebuild.py +1 -1
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/METADATA +3 -6
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/RECORD +89 -93
- agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
- llm.txt +4 -5
- agent_starter_pack/agents/crewai_coding_crew/README.md +0 -34
- agent_starter_pack/agents/crewai_coding_crew/app/agent.py +0 -47
- agent_starter_pack/agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
- agent_starter_pack/agents/crewai_coding_crew/app/crew/crew.py +0 -71
- agent_starter_pack/agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
- agent_starter_pack/agents/langgraph_base_react/README.md +0 -9
- agent_starter_pack/agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -1574
- agent_starter_pack/base_template/deployment/terraform/dev/log_sinks.tf +0 -69
- agent_starter_pack/base_template/deployment/terraform/log_sinks.tf +0 -79
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +0 -155
- agent_starter_pack/cli/utils/register_gemini_enterprise.py +0 -406
- agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +0 -82
- agent_starter_pack/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +0 -99
- agent_starter_pack/frontends/streamlit/frontend/side_bar.py +0 -214
- agent_starter_pack/frontends/streamlit/frontend/streamlit_app.py +0 -265
- agent_starter_pack/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
- agent_starter_pack/frontends/streamlit/frontend/utils/local_chat_history.py +0 -127
- agent_starter_pack/frontends/streamlit/frontend/utils/message_editing.py +0 -59
- agent_starter_pack/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
- agent_starter_pack/frontends/streamlit/frontend/utils/stream_handler.py +0 -310
- agent_starter_pack/frontends/streamlit/frontend/utils/title_summary.py +0 -94
- agent_starter_pack/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6650
- agent_starter_pack/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -7825
- agent_starter_pack-0.18.2.dist-info/entry_points.txt +0 -3
- /agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +0 -0
- /agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/gcs.py +0 -0
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
agent_starter_pack/cli/main.py
CHANGED
|
@@ -21,6 +21,7 @@ from rich.console import Console
|
|
|
21
21
|
from .commands.create import create
|
|
22
22
|
from .commands.enhance import enhance
|
|
23
23
|
from .commands.list import list_agents
|
|
24
|
+
from .commands.register_gemini_enterprise import register_gemini_enterprise
|
|
24
25
|
from .commands.setup_cicd import setup_cicd
|
|
25
26
|
from .utils import display_update_message
|
|
26
27
|
|
|
@@ -57,6 +58,7 @@ def cli() -> None:
|
|
|
57
58
|
# Register commands
|
|
58
59
|
cli.add_command(create)
|
|
59
60
|
cli.add_command(enhance)
|
|
61
|
+
cli.add_command(register_gemini_enterprise)
|
|
60
62
|
cli.add_command(setup_cicd)
|
|
61
63
|
cli.add_command(list_agents, name="list")
|
|
62
64
|
|
|
@@ -103,9 +103,10 @@ def create_github_connection(
|
|
|
103
103
|
"""
|
|
104
104
|
console.print("\n🔗 Creating GitHub connection...")
|
|
105
105
|
|
|
106
|
-
# First, ensure
|
|
107
|
-
console.print("🔧 Ensuring
|
|
106
|
+
# First, ensure required APIs are enabled
|
|
107
|
+
console.print("🔧 Ensuring required APIs are enabled...")
|
|
108
108
|
try:
|
|
109
|
+
# Enable Cloud Build API
|
|
109
110
|
run_command(
|
|
110
111
|
[
|
|
111
112
|
"gcloud",
|
|
@@ -120,13 +121,28 @@ def create_github_connection(
|
|
|
120
121
|
)
|
|
121
122
|
console.print("✅ Cloud Build API enabled")
|
|
122
123
|
|
|
123
|
-
#
|
|
124
|
+
# Enable Secret Manager API
|
|
125
|
+
run_command(
|
|
126
|
+
[
|
|
127
|
+
"gcloud",
|
|
128
|
+
"services",
|
|
129
|
+
"enable",
|
|
130
|
+
"secretmanager.googleapis.com",
|
|
131
|
+
"--project",
|
|
132
|
+
project_id,
|
|
133
|
+
],
|
|
134
|
+
capture_output=True,
|
|
135
|
+
check=False, # Don't fail if already enabled
|
|
136
|
+
)
|
|
137
|
+
console.print("✅ Secret Manager API enabled")
|
|
138
|
+
|
|
139
|
+
# Wait for the APIs to fully initialize and create the service account
|
|
124
140
|
console.print(
|
|
125
141
|
"⏳ Waiting for Cloud Build service account to be created (this typically takes 5-10 seconds)..."
|
|
126
142
|
)
|
|
127
143
|
time.sleep(10)
|
|
128
144
|
except subprocess.CalledProcessError as e:
|
|
129
|
-
console.print(f"⚠️ Could not enable
|
|
145
|
+
console.print(f"⚠️ Could not enable required APIs: {e}", style="yellow")
|
|
130
146
|
|
|
131
147
|
# Get the Cloud Build service account and grant permissions with retry logic
|
|
132
148
|
try:
|
|
@@ -16,7 +16,10 @@ import json
|
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
18
|
import pathlib
|
|
19
|
+
import re
|
|
19
20
|
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
20
23
|
import tempfile
|
|
21
24
|
from dataclasses import dataclass
|
|
22
25
|
from typing import Any
|
|
@@ -24,7 +27,7 @@ from typing import Any
|
|
|
24
27
|
import yaml
|
|
25
28
|
from cookiecutter.main import cookiecutter
|
|
26
29
|
from rich.console import Console
|
|
27
|
-
from rich.prompt import IntPrompt, Prompt
|
|
30
|
+
from rich.prompt import Confirm, IntPrompt, Prompt
|
|
28
31
|
|
|
29
32
|
from agent_starter_pack.cli.utils.version import get_current_version
|
|
30
33
|
|
|
@@ -35,6 +38,105 @@ from .remote_template import (
|
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
def add_base_template_dependencies_interactively(
|
|
42
|
+
project_path: pathlib.Path,
|
|
43
|
+
base_dependencies: list[str],
|
|
44
|
+
base_template_name: str,
|
|
45
|
+
auto_approve: bool = False,
|
|
46
|
+
) -> bool:
|
|
47
|
+
"""Interactively add base template dependencies using uv add.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
project_path: Path to the project directory
|
|
51
|
+
base_dependencies: List of dependencies from base template's extra_dependencies
|
|
52
|
+
base_template_name: Name of the base template being used
|
|
53
|
+
auto_approve: Whether to skip confirmation and auto-install
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
True if dependencies were added successfully, False otherwise
|
|
57
|
+
"""
|
|
58
|
+
if not base_dependencies:
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
console = Console()
|
|
62
|
+
|
|
63
|
+
# Construct dependency string once for reuse
|
|
64
|
+
deps_str = " ".join(f"'{dep}'" for dep in base_dependencies)
|
|
65
|
+
|
|
66
|
+
# Show what dependencies will be added
|
|
67
|
+
console.print(
|
|
68
|
+
f"\n✓ Base template override: Using '{base_template_name}' as foundation",
|
|
69
|
+
style="bold cyan",
|
|
70
|
+
)
|
|
71
|
+
console.print(" This requires adding the following dependencies:", style="white")
|
|
72
|
+
for dep in base_dependencies:
|
|
73
|
+
console.print(f" • {dep}", style="yellow")
|
|
74
|
+
|
|
75
|
+
# Ask for confirmation unless auto-approve
|
|
76
|
+
should_add = True
|
|
77
|
+
if not auto_approve:
|
|
78
|
+
should_add = Confirm.ask(
|
|
79
|
+
"\n? Add these dependencies automatically?", default=True
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if not should_add:
|
|
83
|
+
console.print("\n⚠️ Skipped dependency installation.", style="yellow")
|
|
84
|
+
console.print(" To add them manually later, run:", style="dim")
|
|
85
|
+
console.print(f" cd {project_path.name}", style="dim")
|
|
86
|
+
console.print(f" uv add {deps_str}\n", style="dim")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Run uv add
|
|
90
|
+
try:
|
|
91
|
+
if auto_approve:
|
|
92
|
+
console.print(
|
|
93
|
+
f"✓ Auto-installing dependencies: {', '.join(base_dependencies)}",
|
|
94
|
+
style="bold cyan",
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
console.print(f"\n✓ Running: uv add {deps_str}", style="bold cyan")
|
|
98
|
+
|
|
99
|
+
# Run uv add in the project directory
|
|
100
|
+
cmd = ["uv", "add"] + base_dependencies
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
cmd,
|
|
103
|
+
cwd=project_path,
|
|
104
|
+
capture_output=True,
|
|
105
|
+
text=True,
|
|
106
|
+
check=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Show success message
|
|
110
|
+
if not auto_approve:
|
|
111
|
+
# Show a summary line from uv output
|
|
112
|
+
output_lines = result.stderr.strip().split("\n")
|
|
113
|
+
for line in output_lines:
|
|
114
|
+
if "Resolved" in line or "Installed" in line:
|
|
115
|
+
console.print(f" {line}", style="dim")
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
console.print("✓ Dependencies added successfully\n", style="bold green")
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
except subprocess.CalledProcessError as e:
|
|
122
|
+
console.print(
|
|
123
|
+
f"\n✗ Failed to add dependencies: {e.stderr.strip()}", style="bold red"
|
|
124
|
+
)
|
|
125
|
+
console.print(" You can add them manually:", style="yellow")
|
|
126
|
+
console.print(f" cd {project_path.name}", style="dim")
|
|
127
|
+
console.print(f" uv add {deps_str}\n", style="dim")
|
|
128
|
+
return False
|
|
129
|
+
except FileNotFoundError:
|
|
130
|
+
console.print(
|
|
131
|
+
"\n✗ uv command not found. Please install uv first.", style="bold red"
|
|
132
|
+
)
|
|
133
|
+
console.print(" Install from: https://docs.astral.sh/uv/", style="dim")
|
|
134
|
+
console.print("\n To add dependencies manually:", style="yellow")
|
|
135
|
+
console.print(f" cd {project_path.name}", style="dim")
|
|
136
|
+
console.print(f" uv add {deps_str}\n", style="dim")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
38
140
|
def validate_agent_directory_name(agent_dir: str) -> None:
|
|
39
141
|
"""Validate that an agent directory name is a valid Python identifier.
|
|
40
142
|
|
|
@@ -112,9 +214,10 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
|
|
|
112
214
|
# Define priority agents that should appear first
|
|
113
215
|
PRIORITY_AGENTS = [
|
|
114
216
|
"adk_base",
|
|
217
|
+
"adk_a2a_base",
|
|
115
218
|
"adk_live",
|
|
116
219
|
"agentic_rag",
|
|
117
|
-
"
|
|
220
|
+
"langgraph_base",
|
|
118
221
|
]
|
|
119
222
|
|
|
120
223
|
agents_list = []
|
|
@@ -251,9 +354,9 @@ def prompt_session_type_selection() -> str:
|
|
|
251
354
|
"display_name": "In-memory session",
|
|
252
355
|
"description": "Session data stored in memory - ideal for stateless applications",
|
|
253
356
|
},
|
|
254
|
-
"
|
|
255
|
-
"display_name": "
|
|
256
|
-
"description": "
|
|
357
|
+
"cloud_sql": {
|
|
358
|
+
"display_name": "Cloud SQL (PostgreSQL)",
|
|
359
|
+
"description": "Managed PostgreSQL database for robust session persistence",
|
|
257
360
|
},
|
|
258
361
|
"agent_engine": {
|
|
259
362
|
"display_name": "Vertex AI Agent Engine",
|
|
@@ -458,6 +561,113 @@ def copy_data_ingestion_files(
|
|
|
458
561
|
)
|
|
459
562
|
|
|
460
563
|
|
|
564
|
+
def _extract_agent_garden_labels(
|
|
565
|
+
agent_garden: bool,
|
|
566
|
+
remote_spec: Any | None,
|
|
567
|
+
remote_template_path: pathlib.Path | None,
|
|
568
|
+
) -> tuple[str | None, str | None]:
|
|
569
|
+
"""Extract agent sample ID and publisher for Agent Garden labeling.
|
|
570
|
+
|
|
571
|
+
This function supports two mechanisms for extracting label information:
|
|
572
|
+
1. From remote_spec metadata (for ADK samples)
|
|
573
|
+
2. Fallback to pyproject.toml parsing (for version-locked templates)
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
agent_garden: Whether this deployment is from Agent Garden
|
|
577
|
+
remote_spec: Remote template spec with ADK samples metadata
|
|
578
|
+
remote_template_path: Path to remote template directory
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
Tuple of (agent_sample_id, agent_sample_publisher) or (None, None) if no labels found
|
|
582
|
+
"""
|
|
583
|
+
if not agent_garden:
|
|
584
|
+
return None, None
|
|
585
|
+
|
|
586
|
+
agent_sample_id = None
|
|
587
|
+
agent_sample_publisher = None
|
|
588
|
+
|
|
589
|
+
# Handle remote specs with ADK samples metadata
|
|
590
|
+
if (
|
|
591
|
+
remote_spec
|
|
592
|
+
and hasattr(remote_spec, "is_adk_samples")
|
|
593
|
+
and remote_spec.is_adk_samples
|
|
594
|
+
):
|
|
595
|
+
# For ADK samples, template_path is like "python/agents/sample-name"
|
|
596
|
+
agent_sample_id = pathlib.Path(remote_spec.template_path).name
|
|
597
|
+
# For ADK samples, publisher is always "google"
|
|
598
|
+
agent_sample_publisher = "google"
|
|
599
|
+
logging.debug(f"Detected ADK sample from remote_spec: {agent_sample_id}")
|
|
600
|
+
return agent_sample_id, agent_sample_publisher
|
|
601
|
+
|
|
602
|
+
# Fallback: Detect ADK samples from pyproject.toml (for version-locked templates)
|
|
603
|
+
if remote_template_path:
|
|
604
|
+
pyproject_path = remote_template_path / "pyproject.toml"
|
|
605
|
+
if pyproject_path.exists():
|
|
606
|
+
try:
|
|
607
|
+
if sys.version_info >= (3, 11):
|
|
608
|
+
import tomllib
|
|
609
|
+
else:
|
|
610
|
+
import tomli as tomllib
|
|
611
|
+
|
|
612
|
+
with open(pyproject_path, "rb") as toml_file:
|
|
613
|
+
pyproject_data = tomllib.load(toml_file)
|
|
614
|
+
|
|
615
|
+
# Extract project name from pyproject.toml
|
|
616
|
+
project_name_from_toml = pyproject_data.get("project", {}).get("name")
|
|
617
|
+
|
|
618
|
+
if project_name_from_toml:
|
|
619
|
+
agent_sample_id = project_name_from_toml
|
|
620
|
+
agent_sample_publisher = "google" # ADK samples are from Google
|
|
621
|
+
logging.debug(
|
|
622
|
+
f"Detected ADK sample from pyproject.toml: {agent_sample_id}"
|
|
623
|
+
)
|
|
624
|
+
except Exception as e:
|
|
625
|
+
logging.debug(f"Failed to read pyproject.toml: {e}")
|
|
626
|
+
|
|
627
|
+
return agent_sample_id, agent_sample_publisher
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _inject_app_object_if_missing(
|
|
631
|
+
agent_py_path: pathlib.Path, agent_directory: str, console: Console
|
|
632
|
+
) -> None:
|
|
633
|
+
"""Inject app object into agent.py if missing (backward compatibility for ADK).
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
agent_py_path: Path to the agent.py file
|
|
637
|
+
agent_directory: Name of the agent directory for logging
|
|
638
|
+
console: Rich console for user feedback
|
|
639
|
+
"""
|
|
640
|
+
try:
|
|
641
|
+
content = agent_py_path.read_text(encoding="utf-8")
|
|
642
|
+
# Check for app object (assignment, function definition, or import)
|
|
643
|
+
app_patterns = [
|
|
644
|
+
r"^\s*app\s*=", # assignment: app = ...
|
|
645
|
+
r"^\s*def\s+app\(", # function: def app(...)
|
|
646
|
+
r"from\s+.*\s+import\s+.*\bapp\b", # import: from ... import app
|
|
647
|
+
]
|
|
648
|
+
has_app = any(
|
|
649
|
+
re.search(pattern, content, re.MULTILINE) for pattern in app_patterns
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
if not has_app:
|
|
653
|
+
console.print(
|
|
654
|
+
f"ℹ️ Adding 'app' object to [cyan]{agent_directory}/agent.py[/cyan] for backward compatibility",
|
|
655
|
+
style="dim",
|
|
656
|
+
)
|
|
657
|
+
# Add import and app object at the end of the file
|
|
658
|
+
content = content.rstrip()
|
|
659
|
+
if "from google.adk.apps.app import App" not in content:
|
|
660
|
+
content += "\n\nfrom google.adk.apps.app import App\n"
|
|
661
|
+
content += '\napp = App(root_agent=root_agent, name="app")\n'
|
|
662
|
+
|
|
663
|
+
# Write the modified content back
|
|
664
|
+
agent_py_path.write_text(content, encoding="utf-8")
|
|
665
|
+
except Exception as e:
|
|
666
|
+
logging.warning(
|
|
667
|
+
f"Could not inject app object into {agent_directory}/agent.py: {type(e).__name__}: {e}"
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
|
|
461
671
|
def process_template(
|
|
462
672
|
agent_name: str,
|
|
463
673
|
template_dir: pathlib.Path,
|
|
@@ -498,6 +708,9 @@ def process_template(
|
|
|
498
708
|
logging.debug(f"Include pipeline: {datastore}")
|
|
499
709
|
logging.debug(f"Output directory: {output_dir}")
|
|
500
710
|
|
|
711
|
+
# Create console for user feedback
|
|
712
|
+
console = Console()
|
|
713
|
+
|
|
501
714
|
def get_agent_directory(
|
|
502
715
|
template_config: dict[str, Any], cli_overrides: dict[str, Any] | None = None
|
|
503
716
|
) -> str:
|
|
@@ -559,13 +772,9 @@ def process_template(
|
|
|
559
772
|
os.chdir(temp_path) # Change to temp directory
|
|
560
773
|
|
|
561
774
|
# Extract agent sample info for labeling when using agent garden with remote templates
|
|
562
|
-
agent_sample_id =
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
# For ADK samples, template_path is like "python/agents/sample-name"
|
|
566
|
-
agent_sample_id = pathlib.Path(remote_spec.template_path).name
|
|
567
|
-
# For ADK samples, publisher is always "google"
|
|
568
|
-
agent_sample_publisher = "google"
|
|
775
|
+
agent_sample_id, agent_sample_publisher = _extract_agent_garden_labels(
|
|
776
|
+
agent_garden, remote_spec, remote_template_path
|
|
777
|
+
)
|
|
569
778
|
|
|
570
779
|
# Create the cookiecutter template structure
|
|
571
780
|
cookiecutter_template = temp_path / "template"
|
|
@@ -717,14 +926,14 @@ def process_template(
|
|
|
717
926
|
/ "docs"
|
|
718
927
|
/ "adk-cheatsheet.md"
|
|
719
928
|
)
|
|
720
|
-
with open(adk_cheatsheet_path, encoding="utf-8") as
|
|
721
|
-
adk_cheatsheet_content =
|
|
929
|
+
with open(adk_cheatsheet_path, encoding="utf-8") as md_file:
|
|
930
|
+
adk_cheatsheet_content = md_file.read()
|
|
722
931
|
|
|
723
932
|
llm_txt_path = (
|
|
724
933
|
pathlib.Path(__file__).parent.parent.parent.parent / "llm.txt"
|
|
725
934
|
)
|
|
726
|
-
with open(llm_txt_path, encoding="utf-8") as
|
|
727
|
-
llm_txt_content =
|
|
935
|
+
with open(llm_txt_path, encoding="utf-8") as txt_file:
|
|
936
|
+
llm_txt_content = txt_file.read()
|
|
728
937
|
|
|
729
938
|
cookiecutter_config = {
|
|
730
939
|
"project_name": project_name,
|
|
@@ -738,6 +947,7 @@ def process_template(
|
|
|
738
947
|
"tags": tags,
|
|
739
948
|
"is_adk": "adk" in tags,
|
|
740
949
|
"is_adk_live": "adk_live" in tags,
|
|
950
|
+
"is_a2a": "a2a" in tags,
|
|
741
951
|
"deployment_target": deployment_target or "",
|
|
742
952
|
"cicd_runner": cicd_runner or "google_cloud_build",
|
|
743
953
|
"session_type": session_type or "",
|
|
@@ -777,8 +987,8 @@ def process_template(
|
|
|
777
987
|
|
|
778
988
|
with open(
|
|
779
989
|
cookiecutter_template / "cookiecutter.json", "w", encoding="utf-8"
|
|
780
|
-
) as
|
|
781
|
-
json.dump(cookiecutter_config,
|
|
990
|
+
) as json_file:
|
|
991
|
+
json.dump(cookiecutter_config, json_file, indent=4)
|
|
782
992
|
|
|
783
993
|
logging.debug(f"Template structure created at {cookiecutter_template}")
|
|
784
994
|
logging.debug(
|
|
@@ -859,6 +1069,19 @@ def process_template(
|
|
|
859
1069
|
)
|
|
860
1070
|
logging.debug("Remote template files copied successfully")
|
|
861
1071
|
|
|
1072
|
+
# Inject app object if missing (backward compatibility for ADK remote templates)
|
|
1073
|
+
# Only inject for ADK agents with agent_engine deployment
|
|
1074
|
+
is_adk = "adk" in tags
|
|
1075
|
+
agent_py_path = generated_project_dir / agent_directory / "agent.py"
|
|
1076
|
+
if (
|
|
1077
|
+
is_adk
|
|
1078
|
+
and agent_py_path.exists()
|
|
1079
|
+
and deployment_target == "agent_engine"
|
|
1080
|
+
):
|
|
1081
|
+
_inject_app_object_if_missing(
|
|
1082
|
+
agent_py_path, agent_directory, console
|
|
1083
|
+
)
|
|
1084
|
+
|
|
862
1085
|
# Move the generated project to the final destination
|
|
863
1086
|
generated_project_dir = temp_path / project_name
|
|
864
1087
|
|
|
@@ -927,8 +1150,12 @@ def process_template(
|
|
|
927
1150
|
file_template_dir / "cookiecutter.json",
|
|
928
1151
|
"w",
|
|
929
1152
|
encoding="utf-8",
|
|
930
|
-
) as
|
|
931
|
-
json.dump(
|
|
1153
|
+
) as config_file:
|
|
1154
|
+
json.dump(
|
|
1155
|
+
cookiecutter_config,
|
|
1156
|
+
config_file,
|
|
1157
|
+
indent=4,
|
|
1158
|
+
)
|
|
932
1159
|
|
|
933
1160
|
# Process the file template
|
|
934
1161
|
cookiecutter(
|
|
@@ -1089,13 +1316,13 @@ def process_template(
|
|
|
1089
1316
|
|
|
1090
1317
|
# Replace cookiecutter project name with actual project name in lock file
|
|
1091
1318
|
lock_file_path = final_destination / "uv.lock"
|
|
1092
|
-
with open(lock_file_path, "r+", encoding="utf-8") as
|
|
1093
|
-
content =
|
|
1094
|
-
|
|
1095
|
-
|
|
1319
|
+
with open(lock_file_path, "r+", encoding="utf-8") as lock_file:
|
|
1320
|
+
content = lock_file.read()
|
|
1321
|
+
lock_file.seek(0)
|
|
1322
|
+
lock_file.write(
|
|
1096
1323
|
content.replace("{{cookiecutter.project_name}}", project_name)
|
|
1097
1324
|
)
|
|
1098
|
-
|
|
1325
|
+
lock_file.truncate()
|
|
1099
1326
|
logging.debug(f"Updated project name in lock file at {lock_file_path}")
|
|
1100
1327
|
|
|
1101
1328
|
except Exception as e:
|
|
@@ -1185,6 +1412,11 @@ def copy_frontend_files(frontend_type: str, project_template: pathlib.Path) -> N
|
|
|
1185
1412
|
logging.debug("Frontend type is 'None' or empty, skipping frontend files")
|
|
1186
1413
|
return
|
|
1187
1414
|
|
|
1415
|
+
# Skip copying if frontend_type is "inspector" - it's installed at runtime via make inspector
|
|
1416
|
+
if frontend_type == "inspector":
|
|
1417
|
+
logging.debug("Frontend type is 'inspector', skipping (installed at runtime)")
|
|
1418
|
+
return
|
|
1419
|
+
|
|
1188
1420
|
# Get the frontends directory path
|
|
1189
1421
|
frontends_path = (
|
|
1190
1422
|
pathlib.Path(__file__).parent.parent.parent / "frontends" / frontend_type
|
agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py
CHANGED
|
@@ -47,7 +47,7 @@ def start_server() -> subprocess.Popen[str]:
|
|
|
47
47
|
sys.executable,
|
|
48
48
|
"-m",
|
|
49
49
|
"uvicorn",
|
|
50
|
-
"app.
|
|
50
|
+
"app.app_utils.expose_app:app",
|
|
51
51
|
"--host",
|
|
52
52
|
"0.0.0.0",
|
|
53
53
|
"--port",
|
|
@@ -202,8 +202,8 @@ def test_feedback_endpoint(server_fixture: subprocess.Popen[str]) -> None:
|
|
|
202
202
|
feedback_data = {
|
|
203
203
|
"score": 5,
|
|
204
204
|
"text": "Great response!",
|
|
205
|
-
"
|
|
206
|
-
"
|
|
205
|
+
"user_id": "test-user-123",
|
|
206
|
+
"session_id": "test-session-123",
|
|
207
207
|
"log_type": "feedback",
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -213,32 +213,124 @@ def test_feedback_endpoint(server_fixture: subprocess.Popen[str]) -> None:
|
|
|
213
213
|
logger.info("Feedback endpoint test passed")
|
|
214
214
|
{% else %}
|
|
215
215
|
|
|
216
|
+
# mypy: disable-error-code="arg-type"
|
|
217
|
+
{%- if cookiecutter.is_a2a %}
|
|
218
|
+
|
|
219
|
+
import os
|
|
220
|
+
|
|
221
|
+
import pytest
|
|
222
|
+
|
|
223
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
|
224
|
+
from tests.helpers import (
|
|
225
|
+
build_get_request,
|
|
226
|
+
build_post_request,
|
|
227
|
+
poll_task_completion,
|
|
228
|
+
)
|
|
229
|
+
{%- elif cookiecutter.is_adk %}
|
|
230
|
+
|
|
216
231
|
import logging
|
|
217
232
|
|
|
218
233
|
import pytest
|
|
219
|
-
{%- if cookiecutter.is_adk %}
|
|
220
234
|
from google.adk.events.event import Event
|
|
221
235
|
|
|
222
|
-
from {{cookiecutter.agent_directory}}.agent import root_agent
|
|
223
236
|
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
|
224
237
|
{%- else %}
|
|
225
238
|
|
|
239
|
+
import logging
|
|
240
|
+
|
|
241
|
+
import pytest
|
|
242
|
+
|
|
226
243
|
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
|
227
244
|
{%- endif %}
|
|
245
|
+
{%- if cookiecutter.is_a2a %}
|
|
228
246
|
|
|
229
247
|
|
|
230
248
|
@pytest.fixture
|
|
231
249
|
def agent_app() -> AgentEngineApp:
|
|
232
250
|
"""Fixture to create and set up AgentEngineApp instance"""
|
|
233
|
-
{
|
|
234
|
-
|
|
251
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import agent_engine
|
|
252
|
+
|
|
253
|
+
agent_engine.set_up()
|
|
254
|
+
return agent_engine
|
|
235
255
|
{%- else %}
|
|
236
|
-
app = AgentEngineApp()
|
|
237
|
-
{%- endif %}
|
|
238
|
-
app.set_up()
|
|
239
|
-
return app
|
|
240
256
|
|
|
241
|
-
|
|
257
|
+
|
|
258
|
+
@pytest.fixture
|
|
259
|
+
def agent_app() -> AgentEngineApp:
|
|
260
|
+
"""Fixture to create and set up AgentEngineApp instance"""
|
|
261
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import agent_engine
|
|
262
|
+
|
|
263
|
+
agent_engine.set_up()
|
|
264
|
+
return agent_engine
|
|
265
|
+
{% endif %}
|
|
266
|
+
{%- if cookiecutter.is_a2a %}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.mark.asyncio
|
|
270
|
+
async def test_agent_on_message_send(agent_app: AgentEngineApp) -> None:
|
|
271
|
+
"""Test complete A2A message workflow from send to task completion with artifacts."""
|
|
272
|
+
# Send message
|
|
273
|
+
message_data = {
|
|
274
|
+
"message": {
|
|
275
|
+
"messageId": f"msg-{os.urandom(8).hex()}",
|
|
276
|
+
"content": [{"text": "What is the capital of France?"}],
|
|
277
|
+
"role": "ROLE_USER",
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
response = await agent_app.on_message_send(
|
|
281
|
+
request=build_post_request(message_data),
|
|
282
|
+
context=None,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Verify task creation
|
|
286
|
+
assert "task" in response and "id" in response["task"], (
|
|
287
|
+
"Expected task with ID in response"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Poll for completion
|
|
291
|
+
final_response = await poll_task_completion(agent_app, response["task"]["id"])
|
|
292
|
+
|
|
293
|
+
# Verify artifacts
|
|
294
|
+
assert final_response.get("artifacts"), "Expected artifacts in completed task"
|
|
295
|
+
artifact = final_response["artifacts"][0]
|
|
296
|
+
assert artifact.get("parts") and artifact["parts"][0].get("text"), (
|
|
297
|
+
"Expected artifact with text content"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@pytest.mark.asyncio
|
|
302
|
+
async def test_agent_card(agent_app: AgentEngineApp) -> None:
|
|
303
|
+
"""Test agent card retrieval and validation of required A2A fields."""
|
|
304
|
+
response = await agent_app.handle_authenticated_agent_card(
|
|
305
|
+
request=build_get_request(None),
|
|
306
|
+
context=None,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Verify core agent card fields
|
|
310
|
+
assert response.get("name") == "root_agent", "Expected agent name 'root_agent'"
|
|
311
|
+
assert response.get("protocolVersion") == "0.3.0", "Expected protocol version 0.3.0"
|
|
312
|
+
assert response.get("preferredTransport") == "HTTP+JSON", (
|
|
313
|
+
"Expected HTTP+JSON transport"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Verify capabilities
|
|
317
|
+
capabilities = response.get("capabilities", {})
|
|
318
|
+
assert capabilities.get("streaming") is False, "Expected streaming disabled"
|
|
319
|
+
|
|
320
|
+
# Verify skills
|
|
321
|
+
skills = response.get("skills", [])
|
|
322
|
+
assert len(skills) > 0, "Expected at least one skill"
|
|
323
|
+
for skill in skills:
|
|
324
|
+
assert all(key in skill for key in ["id", "name", "description"]), (
|
|
325
|
+
"Expected id, name, and description in each skill"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Verify extended card support
|
|
329
|
+
assert response.get("supportsAuthenticatedExtendedCard") is True, (
|
|
330
|
+
"Expected supportsAuthenticatedExtendedCard to be True"
|
|
331
|
+
)
|
|
332
|
+
{% elif cookiecutter.is_adk %}
|
|
333
|
+
|
|
242
334
|
@pytest.mark.asyncio
|
|
243
335
|
async def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
|
|
244
336
|
"""
|
|
@@ -276,7 +368,8 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
|
276
368
|
feedback_data = {
|
|
277
369
|
"score": 5,
|
|
278
370
|
"text": "Great response!",
|
|
279
|
-
"
|
|
371
|
+
"user_id": "test-user-456",
|
|
372
|
+
"session_id": "test-session-456",
|
|
280
373
|
}
|
|
281
374
|
|
|
282
375
|
# Should not raise any exceptions
|
|
@@ -287,12 +380,14 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
|
287
380
|
invalid_feedback = {
|
|
288
381
|
"score": "invalid", # Score must be numeric
|
|
289
382
|
"text": "Bad feedback",
|
|
290
|
-
"
|
|
383
|
+
"user_id": "test-user-789",
|
|
384
|
+
"session_id": "test-session-789",
|
|
291
385
|
}
|
|
292
386
|
agent_app.register_feedback(invalid_feedback)
|
|
293
387
|
|
|
294
388
|
logging.info("All assertions passed for agent feedback test")
|
|
295
389
|
{% else %}
|
|
390
|
+
|
|
296
391
|
def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
|
|
297
392
|
"""
|
|
298
393
|
Integration test for the agent stream query functionality.
|
|
@@ -368,7 +463,8 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
|
368
463
|
feedback_data = {
|
|
369
464
|
"score": 5,
|
|
370
465
|
"text": "Great response!",
|
|
371
|
-
"
|
|
466
|
+
"user_id": "test-user-456",
|
|
467
|
+
"session_id": "test-session-456",
|
|
372
468
|
}
|
|
373
469
|
|
|
374
470
|
# Should not raise any exceptions
|
|
@@ -379,7 +475,8 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
|
379
475
|
invalid_feedback = {
|
|
380
476
|
"score": "invalid", # Score must be numeric
|
|
381
477
|
"text": "Bad feedback",
|
|
382
|
-
"
|
|
478
|
+
"user_id": "test-user-789",
|
|
479
|
+
"session_id": "test-session-789",
|
|
383
480
|
}
|
|
384
481
|
agent_app.register_feedback(invalid_feedback)
|
|
385
482
|
|
|
@@ -16,13 +16,13 @@ The load test simulates realistic user interactions by:
|
|
|
16
16
|
Launch the expose app server in a separate terminal, pointing to your deployed agent engine:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
uv run python -m app.
|
|
19
|
+
uv run python -m app.app_utils.expose_app --mode remote --remote-id <your-agent-engine-id>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Or if you have `deployment_metadata.json` in your project root:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
uv run python -m app.
|
|
25
|
+
uv run python -m app.app_utils.expose_app --mode remote
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
**2. Execute the Load Test:**
|