agent-starter-pack 0.2.3__py3-none-any.whl → 0.3.1__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.

Potentially problematic release.


This version of agent-starter-pack might be problematic. Click here for more details.

Files changed (64) hide show
  1. {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.1.dist-info}/METADATA +8 -4
  2. {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.1.dist-info}/RECORD +61 -46
  3. agents/adk_base/README.md +14 -0
  4. agents/adk_base/app/agent.py +66 -0
  5. agents/adk_base/notebooks/adk_app_testing.ipynb +305 -0
  6. agents/adk_base/template/.templateconfig.yaml +21 -0
  7. agents/adk_base/tests/integration/test_agent.py +58 -0
  8. agents/agentic_rag/README.md +1 -0
  9. agents/agentic_rag/app/agent.py +44 -89
  10. agents/agentic_rag/app/templates.py +0 -25
  11. agents/agentic_rag/notebooks/adk_app_testing.ipynb +305 -0
  12. agents/agentic_rag/template/.templateconfig.yaml +3 -1
  13. agents/agentic_rag/tests/integration/test_agent.py +34 -27
  14. agents/langgraph_base_react/README.md +1 -1
  15. agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
  16. src/base_template/Makefile +9 -0
  17. src/base_template/README.md +1 -1
  18. src/base_template/app/__init__.py +3 -0
  19. src/base_template/app/utils/tracing.py +12 -2
  20. src/base_template/app/utils/typing.py +54 -4
  21. src/base_template/deployment/terraform/dev/variables.tf +4 -0
  22. src/base_template/deployment/terraform/dev/vars/env.tfvars +0 -3
  23. src/base_template/deployment/terraform/variables.tf +4 -0
  24. src/base_template/deployment/terraform/vars/env.tfvars +0 -4
  25. src/base_template/pyproject.toml +5 -3
  26. src/{deployment_targets/agent_engine → base_template}/tests/unit/test_dummy.py +2 -1
  27. src/cli/commands/create.py +10 -2
  28. src/cli/commands/setup_cicd.py +3 -0
  29. src/cli/utils/gcp.py +1 -1
  30. src/cli/utils/template.py +32 -25
  31. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +2 -1
  32. src/deployment_targets/agent_engine/app/agent_engine_app.py +62 -11
  33. src/deployment_targets/agent_engine/app/utils/gcs.py +1 -1
  34. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +63 -0
  35. src/deployment_targets/agent_engine/tests/load_test/load_test.py +9 -2
  36. src/deployment_targets/cloud_run/app/server.py +41 -15
  37. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +60 -3
  38. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  39. src/deployment_targets/cloud_run/tests/load_test/load_test.py +57 -24
  40. src/frontends/live_api_react/frontend/package-lock.json +3 -3
  41. src/frontends/streamlit_adk/frontend/side_bar.py +214 -0
  42. src/frontends/streamlit_adk/frontend/streamlit_app.py +314 -0
  43. src/frontends/streamlit_adk/frontend/style/app_markdown.py +37 -0
  44. src/frontends/streamlit_adk/frontend/utils/chat_utils.py +84 -0
  45. src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +110 -0
  46. src/frontends/streamlit_adk/frontend/utils/message_editing.py +61 -0
  47. src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +223 -0
  48. src/frontends/streamlit_adk/frontend/utils/stream_handler.py +311 -0
  49. src/frontends/streamlit_adk/frontend/utils/title_summary.py +129 -0
  50. src/resources/locks/uv-adk_base-agent_engine.lock +5335 -0
  51. src/resources/locks/uv-adk_base-cloud_run.lock +5927 -0
  52. src/resources/locks/uv-agentic_rag-agent_engine.lock +882 -676
  53. src/resources/locks/uv-agentic_rag-cloud_run.lock +1014 -835
  54. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +712 -606
  55. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +770 -672
  56. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +602 -529
  57. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +763 -665
  58. src/resources/locks/uv-live_api-cloud_run.lock +760 -662
  59. agents/agentic_rag/notebooks/evaluating_langgraph_agent.ipynb +0 -1561
  60. src/base_template/tests/unit/test_utils/test_tracing_exporter.py +0 -140
  61. src/deployment_targets/cloud_run/tests/unit/test_server.py +0 -124
  62. {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.1.dist-info}/WHEEL +0 -0
  63. {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.1.dist-info}/entry_points.txt +0 -0
  64. {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- description: "A agent implementing a base ReAct agent using LangGraph"
15
+ description: "An agent implementing a base ReAct agent using LangGraph"
16
16
  settings:
17
17
  requires_data_ingestion: false
18
18
  deployment_targets: ["agent_engine", "cloud_run"]
@@ -6,6 +6,14 @@ test:
6
6
  uv run pytest tests/unit && uv run pytest tests/integration
7
7
 
8
8
  playground:
9
+ {%- if "adk" in cookiecutter.tags %}
10
+ @echo "+-----------------------------------------------------------------------------+"
11
+ @echo "| Starting ADK Web Server via 'adk web' command. |"
12
+ @echo "| |"
13
+ @echo "| Please select the app folder to start interacting with your agent. |"
14
+ @echo "+-----------------------------------------------------------------------------+"
15
+ uv run adk web
16
+ {%- else %}
9
17
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
10
18
  uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload &
11
19
  {%- endif %}
@@ -14,6 +22,7 @@ playground:
14
22
  {%- else %}
15
23
  {% if cookiecutter.deployment_target == 'agent_engine' %}PYTHONPATH=. {% endif %}uv run streamlit run frontend/streamlit_app.py --browser.serverAddress=localhost --server.enableCORS=false --server.enableXsrfProtection=false
16
24
  {%- endif %}
25
+ {%- endif %}
17
26
 
18
27
  backend:
19
28
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
@@ -47,7 +47,7 @@ make install && make playground
47
47
  | -------------------- | ------------------------------------------------------------------------------------------- |
48
48
  | `make install` | Install all required dependencies using uv |
49
49
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
50
- | `make playground` | Launch local development environment with backend and frontend |
50
+ | `make playground` | Launch local development environment with backend and frontend{%- if "adk" in cookiecutter.tags %} - leveraging `adk web` command. {%- endif %}|
51
51
  | `make backend` | Start backend server only |
52
52
  | `make ui` | Launch Streamlit frontend without local backend |
53
53
  {%- elif cookiecutter.deployment_target == 'agent_engine' %}
@@ -0,0 +1,3 @@
1
+ from app.agent import root_agent
2
+
3
+ __all__ = ["root_agent"]
@@ -17,8 +17,8 @@ import logging
17
17
  from collections.abc import Sequence
18
18
  from typing import Any
19
19
 
20
+ import google.cloud.storage as storage
20
21
  from google.cloud import logging as google_cloud_logging
21
- from google.cloud import storage
22
22
  from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
23
23
  from opentelemetry.sdk.trace import ReadableSpan
24
24
  from opentelemetry.sdk.trace.export import SpanExportResult
@@ -86,8 +86,18 @@ class CloudTraceLoggingSpanExporter(CloudTraceSpanExporter):
86
86
  print(span_dict)
87
87
 
88
88
  # Log the span data to Google Cloud Logging
89
+ {%- if "adk" in cookiecutter.tags %}
90
+ self.logger.log_struct(
91
+ span_dict,
92
+ labels={
93
+ "type": "agent_telemetry",
94
+ "service_name": "{{cookiecutter.project_name}}",
95
+ },
96
+ severity="INFO",
97
+ )
98
+ {%- else %}
89
99
  self.logger.log_struct(span_dict, severity="INFO")
90
-
100
+ {%- endif %}
91
101
  # Export spans to Google Cloud Trace using the parent class method
92
102
  return super().export(spans)
93
103
 
@@ -12,6 +12,29 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ {%- if "adk" in cookiecutter.tags %}
16
+ {%- if cookiecutter.deployment_target == 'cloud_run' %}
17
+ import uuid
18
+ from typing import (
19
+ Literal,
20
+ )
21
+
22
+ from google.adk.events.event import Event
23
+ from google.genai.types import Content
24
+ from pydantic import (
25
+ BaseModel,
26
+ Field,
27
+ )
28
+ {%- else %}
29
+ from typing import (
30
+ Literal,
31
+ )
32
+
33
+ from pydantic import (
34
+ BaseModel,
35
+ )
36
+ {%- endif %}
37
+ {%- else %}
15
38
  import json
16
39
  import uuid
17
40
  from typing import (
@@ -31,6 +54,24 @@ from pydantic import (
31
54
  BaseModel,
32
55
  Field,
33
56
  )
57
+ {%- endif %}
58
+
59
+
60
+ {%- if "adk" in cookiecutter.tags %}
61
+ {%- if cookiecutter.deployment_target == 'cloud_run' %}
62
+
63
+
64
+ class Request(BaseModel):
65
+ """Represents the input for a chat request with optional configuration."""
66
+
67
+ message: Content
68
+ events: list[Event]
69
+ user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
70
+ session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
71
+
72
+ model_config = {"extra": "allow"}
73
+ {%- endif %}
74
+ {%- else %}
34
75
 
35
76
 
36
77
  class InputChat(BaseModel):
@@ -42,7 +83,7 @@ class InputChat(BaseModel):
42
83
  ..., description="The chat messages representing the current conversation."
43
84
  )
44
85
 
45
- {% if cookiecutter.deployment_target == 'cloud_run' %}
86
+
46
87
  class Request(BaseModel):
47
88
  """Represents the input for a chat request with optional configuration.
48
89
 
@@ -54,16 +95,23 @@ class Request(BaseModel):
54
95
  input: InputChat
55
96
  config: RunnableConfig | None = None
56
97
 
57
- {% endif %}
98
+ {%- endif %}
99
+
100
+
58
101
  class Feedback(BaseModel):
59
102
  """Represents feedback for a conversation."""
60
103
 
61
104
  score: int | float
62
105
  text: str | None = ""
106
+ {%- if "adk" in cookiecutter.tags %}
107
+ invocation_id: str
108
+ {%- else %}
63
109
  run_id: str
110
+ {%- endif %}
64
111
  log_type: Literal["feedback"] = "feedback"
65
112
  service_name: Literal["{{cookiecutter.project_name}}"] = "{{cookiecutter.project_name}}"
66
-
113
+ user_id: str = ""
114
+ {% if "adk" not in cookiecutter.tags %}
67
115
 
68
116
  def ensure_valid_config(config: RunnableConfig | None) -> RunnableConfig:
69
117
  """Ensures a valid RunnableConfig by setting defaults for missing fields."""
@@ -99,7 +147,8 @@ def dumps(obj: Any) -> str:
99
147
  JSON string representation of the object
100
148
  """
101
149
  return json.dumps(obj, default=default_serialization)
102
- {% if cookiecutter.deployment_target == 'agent_engine' %}
150
+ {%- if cookiecutter.deployment_target == 'agent_engine' %}
151
+
103
152
 
104
153
  def dumpd(obj: Any) -> Any:
105
154
  """
@@ -113,4 +162,5 @@ def dumpd(obj: Any) -> Any:
113
162
  Dict/list representation of the object that can be JSON serialized
114
163
  """
115
164
  return json.loads(dumps(obj))
165
+ {%- endif %}
116
166
  {% endif %}
@@ -32,7 +32,11 @@ variable "region" {
32
32
  variable "telemetry_logs_filter" {
33
33
  type = string
34
34
  description = "Log Sink filter for capturing telemetry data. Captures logs with the `traceloop.association.properties.log_type` attribute set to `tracing`."
35
+ {%- if "adk" in cookiecutter.tags %}
36
+ default = "labels.service_name=\"{{cookiecutter.project_name}}\" labels.type=\"agent_telemetry\""
37
+ {%- else %}
35
38
  default = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
39
+ {%- endif %}
36
40
  }
37
41
 
38
42
  variable "feedback_logs_filter" {
@@ -7,9 +7,6 @@ dev_project_id = "your-dev-project-id"
7
7
  # The Google Cloud region you will use to deploy the infrastructure
8
8
  region = "us-central1"
9
9
 
10
- telemetry_logs_filter = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
11
- feedback_logs_filter = "jsonPayload.log_type=\"feedback\""
12
-
13
10
  {%- if cookiecutter.data_ingestion %}
14
11
  {%- if cookiecutter.datastore_type == "vertex_ai_search" %}
15
12
  # The value can only be one of "global", "us" and "eu".
@@ -52,7 +52,11 @@ variable "repository_name" {
52
52
  variable "telemetry_logs_filter" {
53
53
  type = string
54
54
  description = "Log Sink filter for capturing telemetry data. Captures logs with the `traceloop.association.properties.log_type` attribute set to `tracing`."
55
+ {%- if "adk" in cookiecutter.tags %}
56
+ default = "labels.service_name=\"{{cookiecutter.project_name}}\" labels.type=\"agent_telemetry\""
57
+ {%- else %}
55
58
  default = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
59
+ {%- endif %}
56
60
  }
57
61
 
58
62
  variable "feedback_logs_filter" {
@@ -19,10 +19,6 @@ repository_name = "repo-{{cookiecutter.project_name}}"
19
19
  # The Google Cloud region you will use to deploy the infrastructure
20
20
  region = "us-central1"
21
21
 
22
- telemetry_logs_filter = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
23
-
24
- feedback_logs_filter = "jsonPayload.log_type=\"feedback\""
25
-
26
22
  {%- if cookiecutter.data_ingestion %}
27
23
  pipeline_cron_schedule = "0 0 * * 0"
28
24
 
@@ -9,16 +9,18 @@ dependencies = [
9
9
  {%- for dep in cookiecutter.extra_dependencies %}
10
10
  "{{ dep }}",
11
11
  {%- endfor %}
12
- "langchain-core~=0.3.9",
13
12
  "opentelemetry-exporter-gcp-trace~=1.9.0",
13
+ {%- if "adk" not in cookiecutter.tags %}
14
+ "langchain-core~=0.3.9",
14
15
  "traceloop-sdk~=0.38.7",
16
+ {%- endif %}
15
17
  "google-cloud-logging~=3.11.4",
16
18
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
17
- "google-cloud-aiplatform[evaluation]~=1.87.0",
19
+ "google-cloud-aiplatform[evaluation]~=1.88.0",
18
20
  "fastapi~=0.115.8",
19
21
  "uvicorn~=0.34.0"
20
22
  {%- elif cookiecutter.deployment_target == 'agent_engine' %}
21
- "google-cloud-aiplatform[evaluation,reasoningengine]~=1.87.0"
23
+ "google-cloud-aiplatform[evaluation,agent-engines]~=1.88.0"
22
24
  {%- endif %}
23
25
  ]
24
26
  {% if cookiecutter.deployment_target == 'cloud_run' %}
@@ -11,9 +11,10 @@
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
-
15
14
  """
16
15
  You can add your unit tests here.
16
+ This is where you test your business logic, including agent functionality,
17
+ data processing, and other core components of your application.
17
18
  """
18
19
 
19
20
 
@@ -306,10 +306,10 @@ def create(
306
306
  f"This agent uses a datastore for grounded responses.\n"
307
307
  f"The agent will work without data, but for optimal results:\n"
308
308
  f"1. Set up dev environment:\n"
309
- f" [white italic]`export PROJECT_ID={project_id} && cd {cd_path} && make setup-dev-env`[/white italic]\n\n"
309
+ f" [white italic]export PROJECT_ID={project_id} && cd {cd_path} && make setup-dev-env[/white italic]\n\n"
310
310
  f" See deployment/README.md for more info\n"
311
311
  f"2. Run the data ingestion pipeline:\n"
312
- f" [white italic]`export PROJECT_ID={project_id} && cd {cd_path} && make data-ingestion`[/white italic]\n\n"
312
+ f" [white italic]export PROJECT_ID={project_id} && cd {cd_path} && make data-ingestion[/white italic]\n\n"
313
313
  f" See data_ingestion/README.md for more info\n"
314
314
  f"[bold white]=================================[/bold white]\n"
315
315
  )
@@ -647,6 +647,14 @@ def replace_region_in_files(
647
647
  "_DATA_STORE_REGION: us", f"_DATA_STORE_REGION: {data_store_region}"
648
648
  )
649
649
  modified = True
650
+ elif '"DATA_STORE_REGION", "us"' in content:
651
+ if debug:
652
+ logging.debug(f"Replacing DATA_STORE_REGION in {file_path}")
653
+ content = content.replace(
654
+ '"DATA_STORE_REGION", "us"',
655
+ f'"DATA_STORE_REGION", "{data_store_region}"',
656
+ )
657
+ modified = True
650
658
 
651
659
  if modified:
652
660
  file_path.write_text(content)
@@ -815,6 +815,9 @@ def setup_cicd(
815
815
 
816
816
  repo_url = f"https://github.com/{github_username}/{config.repository_name}"
817
817
  cloud_build_url = f"https://console.cloud.google.com/cloud-build/builds?project={config.cicd_project_id}"
818
+ # Sleep to allow resources to propagate
819
+ console.print("\n⏳ Waiting for resources to propagate...")
820
+ time.sleep(10)
818
821
 
819
822
  # Print final summary
820
823
  print_cicd_summary(config, github_username, repo_url, cloud_build_url)
src/cli/utils/gcp.py CHANGED
@@ -45,7 +45,7 @@ def get_dummy_request(project_id: str, location: str) -> CountTokensRequest:
45
45
  """Creates a simple test request for Gemini."""
46
46
  return CountTokensRequest(
47
47
  contents=[{"role": "user", "parts": [{"text": "Hi"}]}],
48
- endpoint=f"projects/{project_id}/locations/{location}/publishers/google/models/gemini-1.5-flash-002",
48
+ endpoint=f"projects/{project_id}/locations/{location}/publishers/google/models/gemini-2.0-flash",
49
49
  )
50
50
 
51
51
 
src/cli/utils/template.py CHANGED
@@ -29,6 +29,9 @@ from src.cli.utils.version import get_current_version
29
29
 
30
30
  from .datastores import DATASTORES
31
31
 
32
+ ADK_FILES = ["app/__init__.py"]
33
+ NON_ADK_FILES: list[str] = []
34
+
32
35
 
33
36
  @dataclass
34
37
  class TemplateConfig:
@@ -77,12 +80,10 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
77
80
  deployment_target: Optional deployment target to filter agents
78
81
  """
79
82
  # Define priority agents that should appear first
80
- PRIORITY_AGENTS = [
81
- "langgraph_base_react" # Add other priority agents here as needed
82
- ]
83
+ PRIORITY_AGENTS = ["adk_base", "agentic_rag", "langgraph_base_react"]
83
84
 
84
85
  agents_list = []
85
- priority_agents = []
86
+ priority_agents_dict = dict.fromkeys(PRIORITY_AGENTS) # Track priority agents
86
87
  agents_dir = pathlib.Path(__file__).parent.parent.parent.parent / "agents"
87
88
 
88
89
  for agent_dir in agents_dir.iterdir():
@@ -109,16 +110,21 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
109
110
 
110
111
  # Add to priority list or regular list based on agent name
111
112
  if agent_name in PRIORITY_AGENTS:
112
- priority_agents.append(agent_info)
113
+ priority_agents_dict[agent_name] = agent_info
113
114
  else:
114
115
  agents_list.append(agent_info)
115
116
  except Exception as e:
116
117
  logging.warning(f"Could not load agent from {agent_dir}: {e}")
117
118
 
118
- # Only sort the non-priority agents
119
+ # Sort the non-priority agents
119
120
  agents_list.sort(key=lambda x: x["name"])
120
121
 
121
- # Combine priority agents with regular agents (no sorting of priority_agents)
122
+ # Create priority agents list in the exact order specified
123
+ priority_agents = [
124
+ info for name, info in priority_agents_dict.items() if info is not None
125
+ ]
126
+
127
+ # Combine priority agents with regular agents
122
128
  combined_agents = priority_agents + agents_list
123
129
 
124
130
  # Convert to numbered dictionary starting from 1
@@ -497,29 +503,27 @@ def process_template(
497
503
  extra_deps = template_config.get("settings", {}).get(
498
504
  "extra_dependencies", []
499
505
  )
500
- otel_instrumentations = get_otel_instrumentations(dependencies=extra_deps)
501
-
502
506
  # Get frontend type from template config
503
507
  frontend_type = template_config.get("settings", {}).get(
504
508
  "frontend_type", DEFAULT_FRONTEND
505
509
  )
506
-
510
+ tags = template_config.get("settings", {}).get("tags", ["None"])
507
511
  cookiecutter_config = {
508
512
  "project_name": "my-project",
509
513
  "agent_name": agent_name,
510
514
  "package_version": get_current_version(),
511
515
  "agent_description": template_config.get("description", ""),
516
+ "tags": tags,
512
517
  "deployment_target": deployment_target or "",
513
518
  "frontend_type": frontend_type,
514
519
  "extra_dependencies": [extra_deps],
515
- "otel_instrumentations": otel_instrumentations,
516
520
  "data_ingestion": include_data_ingestion, # Use explicit flag for cookiecutter
517
521
  "datastore_type": datastore if datastore else "",
518
522
  "_copy_without_render": [
519
523
  "*.ipynb", # Don't render notebooks
520
524
  "*.json", # Don't render JSON files
521
525
  "frontend/*", # Don't render frontend directory
522
- "tests/*", # Don't render tests directory
526
+ # "tests/*", # Don't render tests directory
523
527
  "notebooks/*", # Don't render notebooks directory
524
528
  ".git/*", # Don't render git directory
525
529
  "__pycache__/*", # Don't render cache
@@ -566,6 +570,17 @@ def process_template(
566
570
  shutil.copytree(output_dir, final_destination, dirs_exist_ok=True)
567
571
  logging.debug(f"Project successfully created at {final_destination}")
568
572
 
573
+ # Delete appropriate files based on ADK tag
574
+ if "adk" in tags:
575
+ files_to_delete = [final_destination / f for f in NON_ADK_FILES]
576
+ else:
577
+ files_to_delete = [final_destination / f for f in ADK_FILES]
578
+
579
+ for file_path in files_to_delete:
580
+ if file_path.exists():
581
+ file_path.unlink()
582
+ logging.debug(f"Deleted {file_path}")
583
+
569
584
  # After copying template files, handle the lock file
570
585
  if deployment_target:
571
586
  # Get the source lock file path
@@ -675,6 +690,11 @@ def copy_files(
675
690
 
676
691
  def copy_frontend_files(frontend_type: str, project_template: pathlib.Path) -> None:
677
692
  """Copy files from the specified frontend folder directly to project root."""
693
+ # Skip copying if frontend_type is "None"
694
+ if frontend_type == "None":
695
+ logging.debug("Frontend type is 'None', skipping frontend files")
696
+ return
697
+
678
698
  # Use default frontend if none specified
679
699
  frontend_type = frontend_type or DEFAULT_FRONTEND
680
700
 
@@ -715,16 +735,3 @@ def copy_deployment_files(
715
735
  )
716
736
  else:
717
737
  logging.warning(f"Deployment target directory not found: {deployment_path}")
718
-
719
-
720
- def get_otel_instrumentations(dependencies: list) -> list[list[str]]:
721
- """Returns OpenTelemetry instrumentation statements for enabled dependencies."""
722
- otel_deps = {
723
- "langgraph": "Instruments.LANGCHAIN",
724
- "crewai": "Instruments.CREW",
725
- }
726
- imports = []
727
- for dep in dependencies:
728
- if any(otel_dep in dep for otel_dep in otel_deps):
729
- imports.append(otel_deps[next(key for key in otel_deps if key in dep)])
730
- return [imports]
@@ -109,7 +109,8 @@ def ingest_data(
109
109
  operation = schema_client.update_schema(
110
110
  request=discoveryengine.UpdateSchemaRequest(
111
111
  schema=new_schema, allow_missing=True
112
- )
112
+ ),
113
+ timeout=1800,
113
114
  )
114
115
  logging.info(f"Waiting for schema update operation: {operation.operation.name}")
115
116
  operation.result()
@@ -12,6 +12,53 @@
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"
16
+ {%- if "adk" in cookiecutter.tags %}
17
+ import datetime
18
+ import json
19
+ import logging
20
+ from collections.abc import Mapping, Sequence
21
+ from typing import Any
22
+
23
+ import google.auth
24
+ import vertexai
25
+ from google.cloud import logging as google_cloud_logging
26
+ from opentelemetry import trace
27
+ from opentelemetry.sdk.trace import TracerProvider, export
28
+ from vertexai import agent_engines
29
+ from vertexai.preview.reasoning_engines import AdkApp
30
+
31
+ from app.agent import root_agent
32
+ from app.utils.gcs import create_bucket_if_not_exists
33
+ from app.utils.tracing import CloudTraceLoggingSpanExporter
34
+ from app.utils.typing import Feedback
35
+
36
+
37
+ class AgentEngineApp(AdkApp):
38
+ def set_up(self) -> None:
39
+ """Set up logging and tracing for the agent engine app."""
40
+ super().set_up()
41
+ logging_client = google_cloud_logging.Client()
42
+ self.logger = logging_client.logger(__name__)
43
+ provider = TracerProvider()
44
+ processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
45
+ provider.add_span_processor(processor)
46
+ trace.set_tracer_provider(provider)
47
+
48
+ def register_feedback(self, feedback: dict[str, Any]) -> None:
49
+ """Collect and log feedback."""
50
+ feedback_obj = Feedback.model_validate(feedback)
51
+ self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
52
+
53
+ def register_operations(self) -> Mapping[str, Sequence]:
54
+ """Registers the operations of the Agent.
55
+
56
+ Extends the base operations to include feedback registration functionality.
57
+ """
58
+ operations = super().register_operations()
59
+ operations[""] = operations[""] + ["register_feedback"]
60
+ return operations
61
+ {%- else %}
15
62
  import datetime
16
63
  import json
17
64
  import logging
@@ -66,7 +113,6 @@ class AgentEngineApp:
66
113
  )
67
114
  except Exception as e:
68
115
  logging.error("Failed to initialize Telemetry: %s", str(e))
69
-
70
116
  self.runnable = agent
71
117
 
72
118
  # Add any additional variables here that should be included in the tracing logs
@@ -107,11 +153,6 @@ class AgentEngineApp:
107
153
  dumped_chunk = dumpd(chunk)
108
154
  yield dumped_chunk
109
155
 
110
- def register_feedback(self, feedback: dict[str, Any]) -> None:
111
- """Collect and log feedback."""
112
- feedback_obj = Feedback.model_validate(feedback)
113
- self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
114
-
115
156
  def query(
116
157
  self,
117
158
  *,
@@ -124,6 +165,11 @@ class AgentEngineApp:
124
165
  self.set_tracing_properties(config=config)
125
166
  return dumpd(self.runnable.invoke(input=input, config=config, **kwargs))
126
167
 
168
+ def register_feedback(self, feedback: dict[str, Any]) -> None:
169
+ """Collect and log feedback."""
170
+ feedback_obj = Feedback.model_validate(feedback)
171
+ self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
172
+
127
173
  def register_operations(self) -> Mapping[str, Sequence]:
128
174
  """Registers the operations of the Agent.
129
175
 
@@ -140,6 +186,7 @@ class AgentEngineApp:
140
186
  "": ["query", "register_feedback"],
141
187
  "stream": ["stream_query"],
142
188
  }
189
+ {%- endif %}
143
190
 
144
191
 
145
192
  def deploy_agent_engine_app(
@@ -162,14 +209,18 @@ def deploy_agent_engine_app(
162
209
  # Read requirements
163
210
  with open(requirements_file) as f:
164
211
  requirements = f.read().strip().split("\n")
165
-
166
- agent = AgentEngineApp(project_id=project, env_vars=env_vars)
167
-
212
+ {% if "adk" in cookiecutter.tags %}
213
+ agent_engine = AgentEngineApp(
214
+ agent=root_agent, env_vars=env_vars, enable_tracing=True
215
+ )
216
+ {% else %}
217
+ agent_engine = AgentEngineApp(project_id=project, env_vars=env_vars)
218
+ {% endif %}
168
219
  # Common configuration for both create and update operations
169
220
  agent_config = {
170
- "agent_engine": agent,
221
+ "agent_engine": agent_engine,
171
222
  "display_name": agent_name,
172
- "description": "This is a sample custom application in Agent Engine that uses LangGraph",
223
+ "description": "{{cookiecutter.agent_description}}",
173
224
  "extra_packages": extra_packages,
174
225
  }
175
226
  logging.info(f"Agent config: {agent_config}")
@@ -14,8 +14,8 @@
14
14
 
15
15
  import logging
16
16
 
17
+ import google.cloud.storage as storage
17
18
  from google.api_core import exceptions
18
- from google.cloud import storage
19
19
 
20
20
 
21
21
  def create_bucket_if_not_exists(bucket_name: str, project: str, location: str) -> None:
@@ -15,18 +15,80 @@
15
15
  import logging
16
16
 
17
17
  import pytest
18
+ {%- if "adk" in cookiecutter.tags %}
19
+ from google.adk.events.event import Event
18
20
 
21
+ from app.agent import root_agent
19
22
  from app.agent_engine_app import AgentEngineApp
23
+ {%- else %}
24
+
25
+ from app.agent_engine_app import AgentEngineApp
26
+ {%- endif %}
20
27
 
21
28
 
22
29
  @pytest.fixture
23
30
  def agent_app() -> AgentEngineApp:
24
31
  """Fixture to create and set up AgentEngineApp instance"""
32
+ {%- if "adk" in cookiecutter.tags %}
33
+ app = AgentEngineApp(agent=root_agent)
34
+ {%- else %}
25
35
  app = AgentEngineApp()
36
+ {%- endif %}
26
37
  app.set_up()
27
38
  return app
28
39
 
40
+ {% if "adk" in cookiecutter.tags %}
41
+ def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
42
+ """
43
+ Integration test for the agent stream query functionality.
44
+ Tests that the agent returns valid streaming responses.
45
+ """
46
+ # Create message and events for the stream_query
47
+ message = "What's the weather in San Francisco?"
48
+ events = list(agent_app.stream_query(message=message, user_id="test"))
49
+ assert len(events) > 0, "Expected at least one chunk in response"
50
+
51
+ # Check for valid content in the response
52
+ has_text_content = False
53
+ for event in events:
54
+ validated_event = Event.model_validate(event)
55
+ content = validated_event.content
56
+ if (
57
+ content is not None
58
+ and content.parts
59
+ and any(part.text for part in content.parts)
60
+ ):
61
+ has_text_content = True
62
+ break
63
+
64
+ assert has_text_content, "Expected at least one event with text content"
29
65
 
66
+
67
+ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
68
+ """
69
+ Integration test for the agent feedback functionality.
70
+ Tests that feedback can be registered successfully.
71
+ """
72
+ feedback_data = {
73
+ "score": 5,
74
+ "text": "Great response!",
75
+ "invocation_id": "test-run-123",
76
+ }
77
+
78
+ # Should not raise any exceptions
79
+ agent_app.register_feedback(feedback_data)
80
+
81
+ # Test invalid feedback
82
+ with pytest.raises(ValueError):
83
+ invalid_feedback = {
84
+ "score": "invalid", # Score must be numeric
85
+ "text": "Bad feedback",
86
+ "invocation_id": "test-run-123",
87
+ }
88
+ agent_app.register_feedback(invalid_feedback)
89
+
90
+ logging.info("All assertions passed for agent feedback test")
91
+ {% else %}
30
92
  def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
31
93
  """
32
94
  Integration test for the agent stream query functionality.
@@ -118,3 +180,4 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
118
180
  agent_app.register_feedback(invalid_feedback)
119
181
 
120
182
  logging.info("All assertions passed for agent feedback test")
183
+ {% endif %}