agent-starter-pack 0.9.2__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.0.dist-info}/METADATA +2 -2
  2. {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.0.dist-info}/RECORD +53 -51
  3. agents/adk_base/.template/templateconfig.yaml +1 -1
  4. agents/adk_gemini_fullstack/.template/templateconfig.yaml +1 -1
  5. agents/agentic_rag/.template/templateconfig.yaml +1 -1
  6. agents/agentic_rag/README.md +1 -1
  7. agents/live_api/tests/integration/test_server_e2e.py +7 -1
  8. llm.txt +3 -2
  9. src/base_template/Makefile +4 -3
  10. src/base_template/README.md +7 -2
  11. src/base_template/deployment/README.md +6 -121
  12. src/base_template/deployment/terraform/github.tf +284 -0
  13. src/base_template/deployment/terraform/providers.tf +5 -0
  14. src/base_template/deployment/terraform/variables.tf +40 -1
  15. src/base_template/deployment/terraform/vars/env.tfvars +7 -1
  16. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
  17. src/base_template/deployment/terraform/{build_triggers.tf → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} } +33 -18
  18. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +114 -0
  19. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
  20. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +170 -0
  21. src/base_template/{deployment/cd/deploy-to-prod.yaml → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml } +7 -7
  22. src/base_template/{deployment/cd/staging.yaml → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml } +7 -7
  23. src/cli/commands/create.py +120 -4
  24. src/cli/commands/list.py +1 -1
  25. src/cli/commands/setup_cicd.py +292 -298
  26. src/cli/utils/cicd.py +19 -7
  27. src/cli/utils/remote_template.py +4 -4
  28. src/cli/utils/template.py +67 -19
  29. src/deployment_targets/cloud_run/app/server.py +19 -0
  30. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -3
  31. src/deployment_targets/cloud_run/deployment/terraform/service.tf +4 -3
  32. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +35 -0
  33. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  34. src/frontends/live_api_react/frontend/package-lock.json +19 -16
  35. src/frontends/streamlit/frontend/side_bar.py +1 -1
  36. src/frontends/streamlit/frontend/utils/chat_utils.py +1 -1
  37. src/frontends/streamlit/frontend/utils/local_chat_history.py +2 -2
  38. src/resources/docs/adk-cheatsheet.md +1 -1
  39. src/resources/locks/uv-adk_base-agent_engine.lock +164 -131
  40. src/resources/locks/uv-adk_base-cloud_run.lock +177 -144
  41. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +164 -131
  42. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +177 -144
  43. src/resources/locks/uv-agentic_rag-agent_engine.lock +223 -190
  44. src/resources/locks/uv-agentic_rag-cloud_run.lock +251 -218
  45. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +315 -485
  46. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +358 -531
  47. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +281 -249
  48. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +323 -290
  49. src/resources/locks/uv-live_api-cloud_run.lock +350 -327
  50. src/resources/setup_cicd/cicd_variables.tf +0 -41
  51. src/resources/setup_cicd/github.tf +0 -87
  52. src/resources/setup_cicd/providers.tf +0 -39
  53. {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.0.dist-info}/WHEEL +0 -0
  54. {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.0.dist-info}/entry_points.txt +0 -0
  55. {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.0.dist-info}/licenses/LICENSE +0 -0
  56. /src/base_template/{deployment/ci → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}}/pr_checks.yaml +0 -0
src/cli/utils/cicd.py CHANGED
@@ -125,6 +125,7 @@ def create_github_connection(
125
125
  stdout=subprocess.PIPE,
126
126
  stderr=subprocess.PIPE,
127
127
  text=True,
128
+ encoding="utf-8",
128
129
  )
129
130
 
130
131
  # Send 'y' followed by enter key to handle both the API enablement prompt and any other prompts
@@ -263,12 +264,12 @@ class ProjectConfig:
263
264
  cicd_project_id: str
264
265
  agent: str
265
266
  deployment_target: str
267
+ repository_name: str
268
+ repository_owner: str
266
269
  region: str = "us-central1"
267
270
  dev_project_id: str | None = None
268
271
  project_name: str | None = None
269
- repository_name: str | None = None
270
- repository_owner: str | None = None
271
- repository_exists: bool | None = None
272
+ create_repository: bool | None = None
272
273
  host_connection_name: str | None = None
273
274
  github_pat: str | None = None
274
275
  github_app_installation_id: str | None = None
@@ -405,6 +406,7 @@ def run_command(
405
406
  capture_output: bool = False,
406
407
  shell: bool = False,
407
408
  input: str | None = None,
409
+ env_vars: dict[str, str] | None = None,
408
410
  ) -> subprocess.CompletedProcess:
409
411
  """Run a command and display it to the user"""
410
412
  # Format command for display
@@ -413,6 +415,14 @@ def run_command(
413
415
  if cwd:
414
416
  print(f"📂 In directory: {cwd}")
415
417
 
418
+ # Prepare environment variables
419
+ env = None
420
+ if env_vars:
421
+ import os
422
+
423
+ env = os.environ.copy()
424
+ env.update(env_vars)
425
+
416
426
  # Run the command
417
427
  result = subprocess.run(
418
428
  cmd,
@@ -422,6 +432,7 @@ def run_command(
422
432
  text=True,
423
433
  shell=shell,
424
434
  input=input,
435
+ env=env,
425
436
  )
426
437
 
427
438
  # Display output if captured
@@ -475,6 +486,7 @@ def handle_github_authentication() -> None:
475
486
  stdout=subprocess.PIPE,
476
487
  stderr=subprocess.PIPE,
477
488
  text=True,
489
+ encoding="utf-8",
478
490
  )
479
491
  stdout, stderr = process.communicate(input=token + "\n")
480
492
 
@@ -565,7 +577,7 @@ class E2EDeployment:
565
577
  project_dir / "deployment" / "terraform" / "dev" / "vars" / "env.tfvars"
566
578
  )
567
579
 
568
- with open(tf_vars_path) as f:
580
+ with open(tf_vars_path, encoding="utf-8") as f:
569
581
  content = f.read()
570
582
 
571
583
  # Replace dev project ID
@@ -580,7 +592,7 @@ class E2EDeployment:
580
592
  project_dir / "deployment" / "terraform" / "vars" / "env.tfvars"
581
593
  )
582
594
 
583
- with open(tf_vars_path) as f:
595
+ with open(tf_vars_path, encoding="utf-8") as f:
584
596
  content = f.read()
585
597
 
586
598
  # Replace all project IDs
@@ -613,7 +625,7 @@ class E2EDeployment:
613
625
  )
614
626
 
615
627
  # Write updated content
616
- with open(tf_vars_path, "w") as f:
628
+ with open(tf_vars_path, "w", encoding="utf-8") as f:
617
629
  f.write(content)
618
630
 
619
631
  def setup_terraform_state(self, project_dir: Path, env: Environment) -> None:
@@ -667,7 +679,7 @@ class E2EDeployment:
667
679
  state_prefix = "dev" if is_dev_dir else "prod"
668
680
 
669
681
  backend_file = tf_dir / "backend.tf"
670
- with open(backend_file, "w") as f:
682
+ with open(backend_file, "w", encoding="utf-8") as f:
671
683
  f.write(f'''terraform {{
672
684
  backend "gcs" {{
673
685
  bucket = "{bucket_name}"
@@ -198,7 +198,7 @@ def load_remote_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
198
198
  return {}
199
199
 
200
200
  try:
201
- with open(config_path) as f:
201
+ with open(config_path, encoding="utf-8") as f:
202
202
  config = yaml.safe_load(f)
203
203
  return config if config else {}
204
204
  except Exception as e:
@@ -265,7 +265,7 @@ def render_and_merge_makefiles(
265
265
  # Render the base Makefile
266
266
  base_makefile_path = base_template_path / "Makefile"
267
267
  if base_makefile_path.exists():
268
- with open(base_makefile_path) as f:
268
+ with open(base_makefile_path, encoding="utf-8") as f:
269
269
  base_template = env.from_string(f.read())
270
270
  rendered_base_makefile = base_template.render(cookiecutter=cookiecutter_config)
271
271
  else:
@@ -276,7 +276,7 @@ def render_and_merge_makefiles(
276
276
  if remote_template_path:
277
277
  remote_makefile_path = remote_template_path / "Makefile"
278
278
  if remote_makefile_path.exists():
279
- with open(remote_makefile_path) as f:
279
+ with open(remote_makefile_path, encoding="utf-8") as f:
280
280
  remote_template = env.from_string(f.read())
281
281
  rendered_remote_makefile = remote_template.render(
282
282
  cookiecutter=cookiecutter_config
@@ -316,6 +316,6 @@ def render_and_merge_makefiles(
316
316
  final_makefile_content = rendered_base_makefile
317
317
 
318
318
  # Write the final merged Makefile
319
- with open(final_destination / "Makefile", "w") as f:
319
+ with open(final_destination / "Makefile", "w", encoding="utf-8") as f:
320
320
  f.write(final_makefile_content)
321
321
  logging.debug("Rendered and merged Makefile written to final destination.")
src/cli/utils/template.py CHANGED
@@ -48,7 +48,7 @@ class TemplateConfig:
48
48
  def from_file(cls, config_path: pathlib.Path) -> "TemplateConfig":
49
49
  """Load template config from file with validation"""
50
50
  try:
51
- with open(config_path) as f:
51
+ with open(config_path, encoding="utf-8") as f:
52
52
  data = yaml.safe_load(f)
53
53
 
54
54
  if not isinstance(data, dict):
@@ -101,7 +101,7 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
101
101
  template_config_path = agent_dir / ".template" / "templateconfig.yaml"
102
102
  if template_config_path.exists():
103
103
  try:
104
- with open(template_config_path) as f:
104
+ with open(template_config_path, encoding="utf-8") as f:
105
105
  config = yaml.safe_load(f)
106
106
  agent_name = agent_dir.name
107
107
 
@@ -150,7 +150,7 @@ def load_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
150
150
  return {}
151
151
 
152
152
  try:
153
- with open(config_file) as f:
153
+ with open(config_file, encoding="utf-8") as f:
154
154
  config = yaml.safe_load(f)
155
155
  return config if config else {}
156
156
  except Exception as e:
@@ -230,6 +230,10 @@ def prompt_session_type_selection() -> str:
230
230
  "display_name": "AlloyDB",
231
231
  "description": "Use AlloyDB for session management. Comes with terraform resources for deployment.",
232
232
  },
233
+ "agent_engine": {
234
+ "display_name": "Vertex AI Agent Engine",
235
+ "description": "Managed session service that automatically handles conversation history",
236
+ },
233
237
  }
234
238
 
235
239
  console.print("\n> Please select a session type:")
@@ -357,6 +361,36 @@ def prompt_datastore_selection(
357
361
  return datastore_type
358
362
 
359
363
 
364
+ def prompt_cicd_runner_selection() -> str:
365
+ """Ask user to select a CI/CD runner."""
366
+ console = Console()
367
+
368
+ cicd_runners = {
369
+ "google_cloud_build": {
370
+ "display_name": "Google Cloud Build",
371
+ "description": "Fully managed CI/CD, deeply integrated with GCP for fast, consistent builds and deployments.",
372
+ },
373
+ "github_actions": {
374
+ "display_name": "GitHub Actions",
375
+ "description": "GitHub Actions: CI/CD with secure workload identity federation directly in GitHub.",
376
+ },
377
+ }
378
+
379
+ console.print("\n> Please select a CI/CD runner:")
380
+ for idx, (_key, info) in enumerate(cicd_runners.items(), 1):
381
+ console.print(
382
+ f"{idx}. [bold]{info['display_name']}[/] - [dim]{info['description']}[/]"
383
+ )
384
+
385
+ choice = IntPrompt.ask(
386
+ "\nEnter the number of your CI/CD runner choice",
387
+ default=1,
388
+ show_default=True,
389
+ )
390
+
391
+ return list(cicd_runners.keys())[choice - 1]
392
+
393
+
360
394
  def get_template_path(agent_name: str, debug: bool = False) -> pathlib.Path:
361
395
  """Get the absolute path to the agent template directory."""
362
396
  current_dir = pathlib.Path(__file__).parent.parent.parent.parent
@@ -404,6 +438,7 @@ def process_template(
404
438
  template_dir: pathlib.Path,
405
439
  project_name: str,
406
440
  deployment_target: str | None = None,
441
+ cicd_runner: str | None = None,
407
442
  include_data_ingestion: bool = False,
408
443
  datastore: str | None = None,
409
444
  session_type: str | None = None,
@@ -418,6 +453,7 @@ def process_template(
418
453
  template_dir: Directory containing the template files
419
454
  project_name: Name of the project to create
420
455
  deployment_target: Optional deployment target (agent_engine or cloud_run)
456
+ cicd_runner: Optional CI/CD runner to use
421
457
  include_data_ingestion: Whether to include data pipeline components
422
458
  datastore: Optional datastore type for data ingestion
423
459
  session_type: Optional session type for cloud_run deployment
@@ -585,13 +621,13 @@ def process_template(
585
621
  / "docs"
586
622
  / "adk-cheatsheet.md"
587
623
  )
588
- with open(adk_cheatsheet_path) as f:
624
+ with open(adk_cheatsheet_path, encoding="utf-8") as f:
589
625
  adk_cheatsheet_content = f.read()
590
626
 
591
627
  llm_txt_path = (
592
628
  pathlib.Path(__file__).parent.parent.parent.parent / "llm.txt"
593
629
  )
594
- with open(llm_txt_path) as f:
630
+ with open(llm_txt_path, encoding="utf-8") as f:
595
631
  llm_txt_content = f.read()
596
632
 
597
633
  cookiecutter_config = {
@@ -605,6 +641,7 @@ def process_template(
605
641
  "settings": settings,
606
642
  "tags": tags,
607
643
  "deployment_target": deployment_target or "",
644
+ "cicd_runner": cicd_runner or "google_cloud_build",
608
645
  "session_type": session_type or "",
609
646
  "frontend_type": frontend_type,
610
647
  "extra_dependencies": [extra_deps],
@@ -629,7 +666,9 @@ def process_template(
629
666
  ],
630
667
  }
631
668
 
632
- with open(cookiecutter_template / "cookiecutter.json", "w") as f:
669
+ with open(
670
+ cookiecutter_template / "cookiecutter.json", "w", encoding="utf-8"
671
+ ) as f:
633
672
  json.dump(cookiecutter_config, f, indent=4)
634
673
 
635
674
  logging.debug(f"Template structure created at {cookiecutter_template}")
@@ -641,6 +680,7 @@ def process_template(
641
680
  cookiecutter(
642
681
  str(cookiecutter_template),
643
682
  no_input=True,
683
+ overwrite_if_exists=True,
644
684
  extra_context={
645
685
  "project_name": project_name,
646
686
  "agent_name": agent_name,
@@ -681,6 +721,27 @@ def process_template(
681
721
  file_path.unlink()
682
722
  logging.debug(f"Deleted {file_path}")
683
723
 
724
+ # Clean up unused_* files and directories created by conditional templates
725
+ import glob
726
+
727
+ unused_patterns = [
728
+ final_destination / "unused_*",
729
+ final_destination / "**" / "unused_*",
730
+ ]
731
+
732
+ for pattern in unused_patterns:
733
+ for unused_path_str in glob.glob(str(pattern), recursive=True):
734
+ unused_path = pathlib.Path(unused_path_str)
735
+ if unused_path.exists():
736
+ if unused_path.is_dir():
737
+ shutil.rmtree(unused_path)
738
+ logging.debug(
739
+ f"Deleted unused directory: {unused_path}"
740
+ )
741
+ else:
742
+ unused_path.unlink()
743
+ logging.debug(f"Deleted unused file: {unused_path}")
744
+
684
745
  # Handle pyproject.toml and uv.lock files
685
746
  if is_remote and remote_template_path:
686
747
  # For remote templates, use their pyproject.toml and uv.lock if they exist
@@ -696,19 +757,6 @@ def process_template(
696
757
  if remote_uv_lock.exists():
697
758
  shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
698
759
  logging.debug("Used uv.lock from remote template")
699
- elif deployment_target:
700
- # Fallback to base template lock file
701
- base_template_name = get_base_template_name(remote_config or {})
702
- lock_path = (
703
- pathlib.Path(__file__).parent.parent.parent.parent
704
- / "src"
705
- / "resources"
706
- / "locks"
707
- / f"uv-{base_template_name}-{deployment_target}.lock"
708
- )
709
- if lock_path.exists():
710
- shutil.copy2(lock_path, final_destination / "uv.lock")
711
- logging.debug(f"Used fallback lock file from {lock_path}")
712
760
  elif deployment_target:
713
761
  # For local templates, use the existing logic
714
762
  lock_path = (
@@ -20,6 +20,9 @@ from google.adk.cli.fast_api import get_fast_api_app
20
20
  from google.cloud import logging as google_cloud_logging
21
21
  from opentelemetry import trace
22
22
  from opentelemetry.sdk.trace import TracerProvider, export
23
+ {%- if cookiecutter.session_type == "agent_engine" %}
24
+ from vertexai import agent_engines
25
+ {%- endif %}
23
26
 
24
27
  from app.utils.gcs import create_bucket_if_not_exists
25
28
  from app.utils.tracing import CloudTraceLoggingSpanExporter
@@ -55,6 +58,22 @@ db_host = os.environ.get("DB_HOST")
55
58
  session_service_uri = None
56
59
  if db_host and db_pass:
57
60
  session_service_uri = f"postgresql://{db_user}:{db_pass}@{db_host}:5432/{db_name}"
61
+ {%- elif cookiecutter.session_type == "agent_engine" %}
62
+ # Agent Engine session configuration
63
+ # Use environment variable for agent name, default to project name
64
+ agent_name = os.environ.get("AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}")
65
+
66
+ # Check if an agent with this name already exists
67
+ existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
68
+
69
+ if existing_agents:
70
+ # Use the existing agent
71
+ agent_engine = existing_agents[0]
72
+ else:
73
+ # Create a new agent if none exists
74
+ agent_engine = agent_engines.create(display_name=agent_name)
75
+
76
+ session_service_uri = f"agentengine://{agent_engine.resource_name}"
58
77
  {%- else %}
59
78
  # In-memory session configuration - no persistent storage
60
79
  session_service_uri = None
@@ -63,9 +63,10 @@ resource "google_service_networking_connection" "vpc_connection" {
63
63
 
64
64
  # AlloyDB Cluster
65
65
  resource "google_alloydb_cluster" "session_db_cluster" {
66
- project = var.dev_project_id
67
- cluster_id = "${var.project_name}-alloydb-cluster"
68
- location = var.region
66
+ project = var.dev_project_id
67
+ cluster_id = "${var.project_name}-alloydb-cluster"
68
+ location = var.region
69
+ deletion_policy = "FORCE"
69
70
 
70
71
  network_config {
71
72
  network = google_compute_network.default.id
@@ -75,9 +75,10 @@ resource "google_service_networking_connection" "vpc_connection" {
75
75
  resource "google_alloydb_cluster" "session_db_cluster" {
76
76
  for_each = local.deploy_project_ids
77
77
 
78
- project = local.deploy_project_ids[each.key]
79
- cluster_id = "${var.project_name}-alloydb-cluster"
80
- location = var.region
78
+ project = local.deploy_project_ids[each.key]
79
+ cluster_id = "${var.project_name}-alloydb-cluster"
80
+ location = var.region
81
+ deletion_policy = "FORCE"
81
82
 
82
83
  network_config {
83
84
  network = google_compute_network.default[each.key].id
@@ -62,6 +62,10 @@ def start_server() -> subprocess.Popen[str]:
62
62
  ]
63
63
  env = os.environ.copy()
64
64
  env["INTEGRATION_TEST"] = "TRUE"
65
+ {%- if cookiecutter.session_type == "agent_engine" %}
66
+ # Set test-specific agent engine session name
67
+ env["AGENT_ENGINE_SESSION_NAME"] = "test-{{cookiecutter.project_name}}"
68
+ {%- endif %}
65
69
  process = subprocess.Popen(
66
70
  command,
67
71
  stdout=subprocess.PIPE,
@@ -250,3 +254,34 @@ def test_collect_feedback(server_fixture: subprocess.Popen[str]) -> None:
250
254
  FEEDBACK_URL, json=feedback_data, headers=HEADERS, timeout=10
251
255
  )
252
256
  assert response.status_code == 200
257
+ {%- if cookiecutter.session_type == "agent_engine" %}
258
+
259
+
260
+ @pytest.fixture(scope="session", autouse=True)
261
+ def cleanup_agent_engine_sessions() -> None:
262
+ """Cleanup agent engine sessions created during tests."""
263
+ yield # Run tests first
264
+
265
+ # Cleanup after tests complete
266
+ from vertexai import agent_engines
267
+
268
+ try:
269
+ # Use same environment variable as server, default to project name
270
+ agent_name = os.environ.get(
271
+ "AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}"
272
+ )
273
+
274
+ # Find and delete agent engines with this name
275
+ existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
276
+
277
+ for agent_engine in existing_agents:
278
+ try:
279
+ agent_engines.delete(resource_name=agent_engine.name)
280
+ logger.info(f"Cleaned up agent engine: {agent_engine.name}")
281
+ except Exception as e:
282
+ logger.warning(
283
+ f"Failed to cleanup agent engine {agent_engine.name}: {e}"
284
+ )
285
+ except Exception as e:
286
+ logger.warning(f"Failed to cleanup agent engine sessions: {e}")
287
+ {%- endif %}
@@ -41,7 +41,7 @@ Comprehensive CSV and HTML reports detailing the load test performance will be g
41
41
 
42
42
  ## Remote Load Testing (Targeting Cloud Run)
43
43
 
44
- This framework also supports load testing against remote targets, such as a staging Cloud Run instance. This process is seamlessly integrated into the Continuous Delivery pipeline via Cloud Build, as defined in the [pipeline file](cicd/cd/staging.yaml).
44
+ This framework also supports load testing against remote targets, such as a staging Cloud Run instance. This process is seamlessly integrated into the Continuous Delivery (CD) pipeline.
45
45
 
46
46
  **Prerequisites:**
47
47
 
@@ -6452,16 +6452,16 @@
6452
6452
  }
6453
6453
  },
6454
6454
  "node_modules/compression": {
6455
- "version": "1.7.5",
6456
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz",
6457
- "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==",
6455
+ "version": "1.8.1",
6456
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
6457
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
6458
6458
  "license": "MIT",
6459
6459
  "dependencies": {
6460
6460
  "bytes": "3.1.2",
6461
6461
  "compressible": "~2.0.18",
6462
6462
  "debug": "2.6.9",
6463
6463
  "negotiator": "~0.6.4",
6464
- "on-headers": "~1.0.2",
6464
+ "on-headers": "~1.1.0",
6465
6465
  "safe-buffer": "5.2.1",
6466
6466
  "vary": "~1.1.2"
6467
6467
  },
@@ -8038,14 +8038,15 @@
8038
8038
  }
8039
8039
  },
8040
8040
  "node_modules/es-set-tostringtag": {
8041
- "version": "2.0.3",
8042
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
8043
- "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
8041
+ "version": "2.1.0",
8042
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
8043
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
8044
8044
  "license": "MIT",
8045
8045
  "dependencies": {
8046
- "get-intrinsic": "^1.2.4",
8046
+ "es-errors": "^1.3.0",
8047
+ "get-intrinsic": "^1.2.6",
8047
8048
  "has-tostringtag": "^1.0.2",
8048
- "hasown": "^2.0.1"
8049
+ "hasown": "^2.0.2"
8049
8050
  },
8050
8051
  "engines": {
8051
8052
  "node": ">= 0.4"
@@ -9335,14 +9336,16 @@
9335
9336
  }
9336
9337
  },
9337
9338
  "node_modules/form-data": {
9338
- "version": "3.0.2",
9339
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz",
9340
- "integrity": "sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==",
9339
+ "version": "3.0.4",
9340
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz",
9341
+ "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==",
9341
9342
  "license": "MIT",
9342
9343
  "dependencies": {
9343
9344
  "asynckit": "^0.4.0",
9344
9345
  "combined-stream": "^1.0.8",
9345
- "mime-types": "^2.1.12"
9346
+ "es-set-tostringtag": "^2.1.0",
9347
+ "hasown": "^2.0.2",
9348
+ "mime-types": "^2.1.35"
9346
9349
  },
9347
9350
  "engines": {
9348
9351
  "node": ">= 6"
@@ -12916,9 +12919,9 @@
12916
12919
  }
12917
12920
  },
12918
12921
  "node_modules/on-headers": {
12919
- "version": "1.0.2",
12920
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
12921
- "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
12922
+ "version": "1.1.0",
12923
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
12924
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
12922
12925
  "license": "MIT",
12923
12926
  "engines": {
12924
12927
  "node": ">= 0.8"
@@ -31,7 +31,7 @@ DEFAULT_BASE_URL = "http://localhost:8000/"
31
31
 
32
32
  DEFAULT_REMOTE_AGENT_ENGINE_ID = "N/A"
33
33
  if os.path.exists("deployment_metadata.json"):
34
- with open("deployment_metadata.json") as f:
34
+ with open("deployment_metadata.json", encoding="utf-8") as f:
35
35
  DEFAULT_REMOTE_AGENT_ENGINE_ID = json.load(f)["remote_agent_engine_id"]
36
36
  DEFAULT_AGENT_CALLABLE_PATH = "app.agent_engine_app.AgentEngineApp"
37
37
 
@@ -56,7 +56,7 @@ def save_chat(st: Any) -> None:
56
56
  if len(messages) > 0:
57
57
  session["messages"] = sanitize_messages(session["messages"])
58
58
  filename = f"{session_id}.yaml"
59
- with open(Path(SAVED_CHAT_PATH) / filename, "w") as file:
59
+ with open(Path(SAVED_CHAT_PATH) / filename, "w", encoding="utf-8") as file:
60
60
  yaml.dump(
61
61
  [session],
62
62
  file,
@@ -49,7 +49,7 @@ class LocalChatMessageHistory(BaseChatMessageHistory):
49
49
  for filename in os.listdir(self.user_dir):
50
50
  if filename.endswith(".yaml"):
51
51
  file_path = os.path.join(self.user_dir, filename)
52
- with open(file_path) as f:
52
+ with open(file_path, encoding="utf-8") as f:
53
53
  conversation = yaml.safe_load(f)
54
54
  if not isinstance(conversation, list) or len(conversation) > 1:
55
55
  raise ValueError(
@@ -71,7 +71,7 @@ class LocalChatMessageHistory(BaseChatMessageHistory):
71
71
  def upsert_session(self, session: dict) -> None:
72
72
  """Updates or inserts a session into the local storage."""
73
73
  session["update_time"] = datetime.now().isoformat()
74
- with open(self.session_file, "w") as f:
74
+ with open(self.session_file, "w", encoding="utf-8") as f:
75
75
  yaml.dump(
76
76
  [session],
77
77
  f,
@@ -356,7 +356,7 @@ document_pipeline = SequentialAgent(
356
356
  Executes `sub_agents` simultaneously. Useful for independent tasks to reduce overall latency. All sub-agents share the same `session.state`.
357
357
 
358
358
  ```python
359
- from google.adk.agents import ParallelAgent, Agent
359
+ from google.adk.agents import ParallelAgent, Agent, SequentialAgent
360
360
 
361
361
  # Agents to fetch data concurrently
362
362
  fetch_stock_price = Agent(name="StockPriceFetcher", ..., output_key="stock_data")