agent-starter-pack 0.1.7__py3-none-any.whl → 0.2.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.

Potentially problematic release.


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

Files changed (80) hide show
  1. {agent_starter_pack-0.1.7.dist-info → agent_starter_pack-0.2.0.dist-info}/METADATA +6 -6
  2. {agent_starter_pack-0.1.7.dist-info → agent_starter_pack-0.2.0.dist-info}/RECORD +77 -77
  3. agents/{agentic_rag_vertexai_search → agentic_rag}/README.md +3 -3
  4. agents/{agentic_rag_vertexai_search → agentic_rag}/app/agent.py +22 -6
  5. agents/agentic_rag/app/retrievers.py +132 -0
  6. agents/{agentic_rag_vertexai_search → agentic_rag}/notebooks/evaluating_langgraph_agent.ipynb +3 -3
  7. agents/{agentic_rag_vertexai_search → agentic_rag}/template/.templateconfig.yaml +3 -5
  8. agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +4 -4
  9. agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +3 -3
  10. agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +3 -3
  11. agents/{multimodal_live_api → live_api}/README.md +7 -0
  12. agents/{multimodal_live_api → live_api}/app/agent.py +3 -11
  13. agents/{multimodal_live_api → live_api}/app/server.py +3 -2
  14. agents/{multimodal_live_api → live_api}/template/.templateconfig.yaml +2 -2
  15. src/base_template/Makefile +12 -7
  16. src/base_template/README.md +71 -71
  17. src/base_template/app/utils/tracing.py +3 -1
  18. src/base_template/app/utils/typing.py +1 -0
  19. src/base_template/deployment/cd/deploy-to-prod.yaml +10 -4
  20. src/base_template/deployment/cd/staging.yaml +11 -10
  21. src/base_template/deployment/ci/pr_checks.yaml +1 -1
  22. src/base_template/deployment/terraform/apis.tf +6 -0
  23. src/base_template/deployment/terraform/build_triggers.tf +34 -21
  24. src/base_template/deployment/terraform/dev/iam.tf +13 -6
  25. src/base_template/deployment/terraform/dev/log_sinks.tf +25 -28
  26. src/base_template/deployment/terraform/dev/providers.tf +1 -0
  27. src/base_template/deployment/terraform/dev/storage.tf +69 -11
  28. src/base_template/deployment/terraform/dev/variables.tf +50 -53
  29. src/base_template/deployment/terraform/dev/vars/env.tfvars +13 -11
  30. src/base_template/deployment/terraform/iam.tf +3 -3
  31. src/base_template/deployment/terraform/log_sinks.tf +24 -26
  32. src/base_template/deployment/terraform/providers.tf +2 -0
  33. src/base_template/deployment/terraform/service_accounts.tf +7 -7
  34. src/base_template/deployment/terraform/storage.tf +123 -11
  35. src/base_template/deployment/terraform/variables.tf +49 -70
  36. src/base_template/deployment/terraform/vars/env.tfvars +12 -17
  37. src/base_template/pyproject.toml +4 -3
  38. src/cli/commands/create.py +79 -19
  39. src/cli/commands/setup_cicd.py +91 -22
  40. src/cli/main.py +3 -1
  41. src/cli/utils/__init__.py +9 -2
  42. src/cli/utils/cicd.py +12 -0
  43. src/cli/utils/datastores.py +32 -0
  44. src/cli/utils/gcp.py +4 -6
  45. src/cli/utils/template.py +127 -45
  46. src/cli/utils/version.py +87 -0
  47. src/data_ingestion/README.md +24 -19
  48. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +135 -2
  49. src/data_ingestion/data_ingestion_pipeline/components/process_data.py +276 -2
  50. src/data_ingestion/data_ingestion_pipeline/pipeline.py +28 -5
  51. src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +49 -14
  52. src/data_ingestion/pyproject.toml +1 -0
  53. src/deployment_targets/agent_engine/app/agent_engine_app.py +3 -1
  54. src/deployment_targets/cloud_run/tests/unit/test_server.py +15 -33
  55. src/frontends/live_api_react/frontend/package-lock.json +208 -168
  56. src/frontends/live_api_react/frontend/package.json +1 -1
  57. src/resources/containers/data_processing/Dockerfile +3 -1
  58. src/resources/locks/{uv-agentic_rag_vertexai_search-agent_engine.lock → uv-agentic_rag-agent_engine.lock} +747 -694
  59. src/resources/locks/{uv-agentic_rag_vertexai_search-cloud_run.lock → uv-agentic_rag-cloud_run.lock} +944 -806
  60. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +651 -694
  61. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +813 -789
  62. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +666 -686
  63. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +848 -798
  64. src/resources/locks/{uv-multimodal_live_api-cloud_run.lock → uv-live_api-cloud_run.lock} +856 -791
  65. src/resources/setup_cicd/cicd_variables.tf +5 -0
  66. src/resources/setup_cicd/github.tf +4 -2
  67. src/utils/watch_and_rebuild.py +14 -0
  68. agents/agentic_rag_vertexai_search/app/retrievers.py +0 -79
  69. src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +0 -22
  70. src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +0 -20
  71. {agent_starter_pack-0.1.7.dist-info → agent_starter_pack-0.2.0.dist-info}/WHEEL +0 -0
  72. {agent_starter_pack-0.1.7.dist-info → agent_starter_pack-0.2.0.dist-info}/entry_points.txt +0 -0
  73. {agent_starter_pack-0.1.7.dist-info → agent_starter_pack-0.2.0.dist-info}/licenses/LICENSE +0 -0
  74. /agents/{agentic_rag_vertexai_search → agentic_rag}/app/templates.py +0 -0
  75. /agents/{agentic_rag_vertexai_search → agentic_rag}/tests/integration/test_agent.py +0 -0
  76. /agents/{multimodal_live_api → live_api}/app/templates.py +0 -0
  77. /agents/{multimodal_live_api → live_api}/app/vector_store.py +0 -0
  78. /agents/{multimodal_live_api → live_api}/tests/integration/test_server_e2e.py +0 -0
  79. /agents/{multimodal_live_api → live_api}/tests/load_test/load_test.py +0 -0
  80. /agents/{multimodal_live_api → live_api}/tests/unit/test_server.py +0 -0
@@ -22,6 +22,7 @@ import tempfile
22
22
  import time
23
23
  from pathlib import Path
24
24
 
25
+ import backoff
25
26
  import click
26
27
  from rich.console import Console
27
28
 
@@ -164,11 +165,12 @@ def update_build_triggers(tf_dir: Path) -> None:
164
165
 
165
166
  def prompt_for_repository_details(
166
167
  repository_name: str | None = None, repository_owner: str | None = None
167
- ) -> tuple[str, str]:
168
+ ) -> tuple[str, str, bool]:
168
169
  """Interactive prompt for repository details with option to use existing repo."""
169
170
  # Get current GitHub username as default owner
170
171
  result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
171
172
  default_owner = result.stdout.strip()
173
+ repository_exists = False
172
174
 
173
175
  if not (repository_name and repository_owner):
174
176
  console.print("\n📦 Repository Configuration", style="bold blue")
@@ -192,6 +194,7 @@ def prompt_for_repository_details(
192
194
  )
193
195
  else:
194
196
  # Existing repository
197
+ repository_exists = True
195
198
  while True:
196
199
  repo_url = click.prompt(
197
200
  "Enter existing repository URL (e.g., https://github.com/owner/repo)"
@@ -233,10 +236,12 @@ def prompt_for_repository_details(
233
236
 
234
237
  if repository_name is None or repository_owner is None:
235
238
  raise ValueError("Repository name and owner must be provided")
236
- return repository_name, repository_owner
239
+ return repository_name, repository_owner, repository_exists
237
240
 
238
241
 
239
- def setup_terraform_backend(tf_dir: Path, project_id: str, region: str) -> None:
242
+ def setup_terraform_backend(
243
+ tf_dir: Path, project_id: str, region: str, repository_name: str
244
+ ) -> None:
240
245
  """Setup terraform backend configuration with GCS bucket"""
241
246
  console.print("\n🔧 Setting up Terraform backend...")
242
247
 
@@ -273,7 +278,7 @@ def setup_terraform_backend(tf_dir: Path, project_id: str, region: str) -> None:
273
278
  if dir_path.exists():
274
279
  # Use different state prefixes for dev and prod
275
280
  is_dev_dir = str(dir_path).endswith("/dev")
276
- state_prefix = "dev" if is_dev_dir else "prod"
281
+ state_prefix = f"{repository_name}/{(is_dev_dir and 'dev') or 'prod'}"
277
282
 
278
283
  backend_file = dir_path / "backend.tf"
279
284
  backend_content = f'''terraform {{
@@ -383,6 +388,18 @@ console = Console()
383
388
  is_flag=True,
384
389
  help="Skip confirmation prompts and proceed automatically",
385
390
  )
391
+ @click.option(
392
+ "--repository-exists",
393
+ is_flag=True,
394
+ default=False,
395
+ help="Flag indicating if the repository already exists",
396
+ )
397
+ @backoff.on_exception(
398
+ backoff.expo,
399
+ (subprocess.CalledProcessError, click.ClickException),
400
+ max_tries=3,
401
+ jitter=backoff.full_jitter,
402
+ )
386
403
  def setup_cicd(
387
404
  dev_project: str | None,
388
405
  staging_project: str,
@@ -398,6 +415,7 @@ def setup_cicd(
398
415
  local_state: bool,
399
416
  debug: bool,
400
417
  auto_approve: bool,
418
+ repository_exists: bool,
401
419
  ) -> None:
402
420
  """Set up CI/CD infrastructure using Terraform."""
403
421
 
@@ -482,12 +500,12 @@ def setup_cicd(
482
500
 
483
501
  # Only prompt for repository details if not provided via CLI
484
502
  if not (repository_name and repository_owner):
485
- repository_name, repository_owner = prompt_for_repository_details(
486
- repository_name, repository_owner
503
+ repository_name, repository_owner, repository_exists = (
504
+ prompt_for_repository_details(repository_name, repository_owner)
487
505
  )
488
506
  # Set default host connection name if not provided
489
507
  if not host_connection_name:
490
- host_connection_name = "github-connection"
508
+ host_connection_name = f"git-{repository_name}"
491
509
  # Check and enable required APIs regardless of auth method
492
510
  required_apis = ["secretmanager.googleapis.com", "cloudbuild.googleapis.com"]
493
511
  ensure_apis_enabled(cicd_project, required_apis)
@@ -548,6 +566,7 @@ def setup_cicd(
548
566
  github_pat=github_pat,
549
567
  github_app_installation_id=github_app_installation_id,
550
568
  git_provider=git_provider,
569
+ repository_exists=repository_exists,
551
570
  )
552
571
 
553
572
  tf_dir = Path("deployment/terraform")
@@ -562,7 +581,12 @@ def setup_cicd(
562
581
  # Setup Terraform backend if not using local state
563
582
  if not local_state:
564
583
  console.print("\n🔧 Setting up remote Terraform backend...")
565
- setup_terraform_backend(tf_dir, cicd_project, region)
584
+ setup_terraform_backend(
585
+ tf_dir=tf_dir,
586
+ project_id=cicd_project,
587
+ region=region,
588
+ repository_name=repository_name,
589
+ )
566
590
  console.print("✅ Remote Terraform backend configured")
567
591
  else:
568
592
  console.print("\n📝 Using local Terraform state (remote backend disabled)")
@@ -600,6 +624,7 @@ def setup_cicd(
600
624
  new_vars["connection_exists"] = (
601
625
  "true" if detected_mode == "interactive" else "false"
602
626
  )
627
+ new_vars["repository_exists"] = "true" if config.repository_exists else "false"
603
628
 
604
629
  # Update or append variables
605
630
  with open(env_vars_path, "w") as f:
@@ -649,16 +674,35 @@ def setup_cicd(
649
674
  else:
650
675
  run_command(["terraform", "init"], cwd=dev_tf_dir)
651
676
 
652
- run_command(
653
- [
654
- "terraform",
655
- "apply",
656
- "-auto-approve",
657
- "--var-file",
658
- "vars/env.tfvars",
659
- ],
660
- cwd=dev_tf_dir,
661
- )
677
+ try:
678
+ run_command(
679
+ [
680
+ "terraform",
681
+ "apply",
682
+ "-auto-approve",
683
+ "--var-file",
684
+ "vars/env.tfvars",
685
+ ],
686
+ cwd=dev_tf_dir,
687
+ )
688
+ except subprocess.CalledProcessError as e:
689
+ if "Error acquiring the state lock" in str(e):
690
+ console.print(
691
+ "[yellow]State lock error detected, retrying without lock...[/yellow]"
692
+ )
693
+ run_command(
694
+ [
695
+ "terraform",
696
+ "apply",
697
+ "-auto-approve",
698
+ "--var-file",
699
+ "vars/env.tfvars",
700
+ "-lock=false",
701
+ ],
702
+ cwd=dev_tf_dir,
703
+ )
704
+ else:
705
+ raise
662
706
 
663
707
  console.print("✅ Dev environment Terraform configuration applied")
664
708
  elif dev_tf_dir.exists():
@@ -673,10 +717,35 @@ def setup_cicd(
673
717
  else:
674
718
  run_command(["terraform", "init"], cwd=tf_dir)
675
719
 
676
- run_command(
677
- ["terraform", "apply", "-auto-approve", "--var-file", "vars/env.tfvars"],
678
- cwd=tf_dir,
679
- )
720
+ try:
721
+ run_command(
722
+ [
723
+ "terraform",
724
+ "apply",
725
+ "-auto-approve",
726
+ "--var-file",
727
+ "vars/env.tfvars",
728
+ ],
729
+ cwd=tf_dir,
730
+ )
731
+ except subprocess.CalledProcessError as e:
732
+ if "Error acquiring the state lock" in str(e):
733
+ console.print(
734
+ "[yellow]State lock error detected, retrying without lock...[/yellow]"
735
+ )
736
+ run_command(
737
+ [
738
+ "terraform",
739
+ "apply",
740
+ "-auto-approve",
741
+ "--var-file",
742
+ "vars/env.tfvars",
743
+ "-lock=false",
744
+ ],
745
+ cwd=tf_dir,
746
+ )
747
+ else:
748
+ raise
680
749
 
681
750
  console.print("✅ Prod/Staging Terraform configuration applied")
682
751
 
src/cli/main.py CHANGED
@@ -19,6 +19,7 @@ from rich.console import Console
19
19
 
20
20
  from .commands.create import create
21
21
  from .commands.setup_cicd import setup_cicd
22
+ from .utils import display_update_message
22
23
 
23
24
  console = Console()
24
25
 
@@ -45,7 +46,8 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
45
46
  help="Show the version and exit.",
46
47
  )
47
48
  def cli() -> None:
48
- pass
49
+ # Check for updates at startup
50
+ display_update_message()
49
51
 
50
52
 
51
53
  # Register commands
src/cli/utils/__init__.py CHANGED
@@ -12,24 +12,31 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from .datastores import DATASTORE_TYPES, get_datastore_info
15
16
  from .gcp import verify_credentials
16
17
  from .logging import handle_cli_error
17
18
  from .template import (
18
19
  get_available_agents,
19
20
  get_deployment_targets,
20
21
  get_template_path,
22
+ load_template_config,
21
23
  process_template,
22
- prompt_data_ingestion,
24
+ prompt_datastore_selection,
23
25
  prompt_deployment_target,
24
26
  )
27
+ from .version import display_update_message
25
28
 
26
29
  __all__ = [
30
+ "DATASTORE_TYPES",
31
+ "display_update_message",
27
32
  "get_available_agents",
33
+ "get_datastore_info",
28
34
  "get_deployment_targets",
29
35
  "get_template_path",
30
36
  "handle_cli_error",
37
+ "load_template_config",
31
38
  "process_template",
32
- "prompt_data_ingestion",
39
+ "prompt_datastore_selection",
33
40
  "prompt_deployment_target",
34
41
  "verify_credentials",
35
42
  ]
src/cli/utils/cicd.py CHANGED
@@ -22,6 +22,7 @@ from dataclasses import dataclass
22
22
  from enum import Enum
23
23
  from pathlib import Path
24
24
 
25
+ import backoff
25
26
  import click
26
27
  from rich.console import Console
27
28
  from rich.prompt import IntPrompt, Prompt
@@ -309,6 +310,7 @@ class ProjectConfig:
309
310
  project_name: str | None = None
310
311
  repository_name: str | None = None
311
312
  repository_owner: str | None = None
313
+ repository_exists: bool | None = None
312
314
  host_connection_name: str | None = None
313
315
  github_pat: str | None = None
314
316
  github_app_installation_id: str | None = None
@@ -414,6 +416,7 @@ def ensure_apis_enabled(project_id: str, apis: list[str]) -> None:
414
416
  project_id,
415
417
  f"--member=serviceAccount:{cloudbuild_sa}",
416
418
  "--role=roles/secretmanager.admin",
419
+ "--condition=None",
417
420
  ]
418
421
  )
419
422
  console.print("✅ Permissions granted to Cloud Build service account")
@@ -428,6 +431,15 @@ def ensure_apis_enabled(project_id: str, apis: list[str]) -> None:
428
431
  time.sleep(10)
429
432
 
430
433
 
434
+ @backoff.on_exception(
435
+ backoff.expo,
436
+ subprocess.CalledProcessError,
437
+ max_tries=3,
438
+ max_time=60,
439
+ on_backoff=lambda details: console.print(
440
+ f"⚠️ Command failed, retrying in {details['wait']:.1f}s (attempt {details['tries']})"
441
+ ),
442
+ )
431
443
  def run_command(
432
444
  cmd: list[str] | str,
433
445
  check: bool = True,
@@ -0,0 +1,32 @@
1
+ """Datastore types and descriptions for data ingestion."""
2
+
3
+ # Dictionary mapping datastore types to their descriptions
4
+ DATASTORES = {
5
+ "vertex_ai_search": {
6
+ "name": "Vertex AI Search",
7
+ "description": "Managed, serverless document store that enables Google-quality search and RAG for generative AI.",
8
+ },
9
+ "vertex_ai_vector_search": {
10
+ "name": "Vertex AI Vector Search",
11
+ "description": "Scalable vector search engine for building search, recommendation systems, and generative AI applications. Based on ScaNN algorithm.",
12
+ },
13
+ }
14
+
15
+ DATASTORE_TYPES = list(DATASTORES.keys())
16
+
17
+
18
+ def get_datastore_info(datastore_type: str) -> dict:
19
+ """Get information about a datastore type.
20
+
21
+ Args:
22
+ datastore_type: The datastore type key
23
+
24
+ Returns:
25
+ Dictionary with datastore information
26
+
27
+ Raises:
28
+ ValueError: If the datastore type is not valid
29
+ """
30
+ if datastore_type not in DATASTORES:
31
+ raise ValueError(f"Invalid datastore type: {datastore_type}")
32
+ return DATASTORES[datastore_type]
src/cli/utils/gcp.py CHANGED
@@ -14,7 +14,6 @@
14
14
 
15
15
  # ruff: noqa: E722
16
16
  import subprocess
17
- from importlib import metadata
18
17
 
19
18
  import google.auth
20
19
  from google.api_core.client_options import ClientOptions
@@ -27,14 +26,13 @@ from google.cloud.aiplatform_v1beta1.types.prediction_service import (
27
26
  CountTokensRequest,
28
27
  )
29
28
 
29
+ from src.cli.utils.version import PACKAGE_NAME, get_current_version
30
+
30
31
 
31
32
  def get_user_agent() -> tuple[str, str]:
32
33
  """Returns custom user agent header tuple (version, agent string)."""
33
- try:
34
- version = metadata.version(distribution_name="ags")
35
- except metadata.PackageNotFoundError:
36
- version = "0.0.0"
37
- return version, f"ags/{version}"
34
+ version = get_current_version()
35
+ return version, f"{PACKAGE_NAME}/{version}"
38
36
 
39
37
 
40
38
  def get_client_info() -> ClientInfo:
src/cli/utils/template.py CHANGED
@@ -22,6 +22,12 @@ from typing import Any
22
22
 
23
23
  import yaml
24
24
  from cookiecutter.main import cookiecutter
25
+ from rich.console import Console
26
+ from rich.prompt import Prompt
27
+
28
+ from src.cli.utils.version import get_current_version
29
+
30
+ from .datastores import DATASTORES
25
31
 
26
32
 
27
33
  @dataclass
@@ -172,15 +178,13 @@ def prompt_deployment_target(agent_name: str) -> str:
172
178
  if not targets:
173
179
  return ""
174
180
 
175
- from rich.console import Console
176
-
177
181
  console = Console()
178
182
  console.print("\n> Please select a deployment target:")
179
183
  for idx, target in enumerate(targets, 1):
180
184
  info = TARGET_INFO.get(target, {})
181
185
  display_name = info.get("display_name", target)
182
186
  description = info.get("description", "")
183
- console.print(f"{idx}. {display_name} - {description}")
187
+ console.print(f"{idx}. [bold]{display_name}[/] - [dim]{description}[/]")
184
188
 
185
189
  from rich.prompt import IntPrompt
186
190
 
@@ -192,8 +196,38 @@ def prompt_deployment_target(agent_name: str) -> str:
192
196
  return targets[choice - 1]
193
197
 
194
198
 
195
- def prompt_data_ingestion(agent_name: str) -> bool:
196
- """Ask user if they want to include data pipeline if the agent supports it."""
199
+ def prompt_datastore_selection(
200
+ agent_name: str, from_cli_flag: bool = False
201
+ ) -> str | None:
202
+ """Ask user to select a datastore type if the agent supports data ingestion.
203
+
204
+ Args:
205
+ agent_name: Name of the agent
206
+ from_cli_flag: Whether this is being called due to explicit --include-data-ingestion flag
207
+ """
208
+ console = Console()
209
+
210
+ # If this is from CLI flag, skip the "would you like to include" prompt
211
+ if from_cli_flag:
212
+ console.print("\n> Please select a datastore type for your data:")
213
+
214
+ # Display options with descriptions
215
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
216
+ console.print(
217
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
218
+ )
219
+
220
+ choice = Prompt.ask(
221
+ "\nEnter the number of your choice",
222
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
223
+ default="1",
224
+ )
225
+
226
+ # Convert choice number to datastore type
227
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
228
+ return datastore_type
229
+
230
+ # Otherwise, proceed with normal flow
197
231
  template_path = (
198
232
  pathlib.Path(__file__).parent.parent.parent.parent
199
233
  / "agents"
@@ -203,26 +237,76 @@ def prompt_data_ingestion(agent_name: str) -> bool:
203
237
  config = load_template_config(template_path)
204
238
 
205
239
  if config:
206
- # If requires_data_ingestion is true, return True without prompting
240
+ # If requires_data_ingestion is true, prompt for datastore type without asking if they want it
207
241
  if config.get("settings", {}).get("requires_data_ingestion"):
208
- return True
242
+ console.print("\n> This agent includes a data ingestion pipeline.")
243
+ console.print("> Please select a datastore type for your data:")
209
244
 
210
- # Only prompt if the agent has optional data processing support
211
- if "data_ingestion" in config.get("settings", {}):
212
- from rich.prompt import Prompt
245
+ # Display options with descriptions
246
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
247
+ console.print(
248
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
249
+ )
250
+ choice = Prompt.ask(
251
+ "\nEnter the number of your choice",
252
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
253
+ default="1",
254
+ )
213
255
 
214
- return (
256
+ # Convert choice number to datastore type
257
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
258
+ return datastore_type
259
+
260
+ # Only prompt if the agent has optional data ingestion support
261
+ if "requires_data_ingestion" in config.get("settings", {}):
262
+ include = (
215
263
  Prompt.ask(
216
- "\n> This agent supports a data pipeline. Would you like to include it?",
264
+ "\n> This agent supports data ingestion. Would you like to include a data pipeline?",
217
265
  choices=["y", "n"],
218
266
  default="n",
219
267
  ).lower()
220
268
  == "y"
221
269
  )
222
- return False
223
270
 
271
+ if include:
272
+ console.print("\n> Please select a datastore type for your data:")
224
273
 
225
- def get_template_path(agent_name: str, debug: bool = False) -> str:
274
+ # Display options with descriptions
275
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
276
+ console.print(
277
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
278
+ )
279
+
280
+ choice = Prompt.ask(
281
+ "\nEnter the number of your choice",
282
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
283
+ default="1",
284
+ )
285
+
286
+ # Convert choice number to datastore type
287
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
288
+ return datastore_type
289
+
290
+ # If we get here, we need to prompt for datastore selection for explicit --include-data-ingestion flag
291
+ console.print(
292
+ "\n> Please select a datastore type for your data ingestion pipeline:"
293
+ )
294
+ # Display options with descriptions
295
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
296
+ console.print(f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]")
297
+
298
+ choice = Prompt.ask(
299
+ "\nEnter the number of your choice",
300
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
301
+ default="1",
302
+ )
303
+
304
+ # Convert choice number to datastore type
305
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
306
+ return datastore_type
307
+
308
+
309
+ def get_template_path(agent_name: str, debug: bool = False) -> pathlib.Path:
226
310
  """Get the absolute path to the agent template directory."""
227
311
  current_dir = pathlib.Path(__file__).parent.parent.parent.parent
228
312
  template_path = current_dir / "agents" / agent_name / "template"
@@ -235,14 +319,17 @@ def get_template_path(agent_name: str, debug: bool = False) -> str:
235
319
  if not template_path.exists():
236
320
  raise ValueError(f"Template directory not found at {template_path}")
237
321
 
238
- return str(template_path)
322
+ return template_path
239
323
 
240
324
 
241
- def copy_data_ingestion_files(project_template: pathlib.Path) -> None:
242
- """Copy data processing files to the project template.
325
+ def copy_data_ingestion_files(
326
+ project_template: pathlib.Path, datastore_type: str
327
+ ) -> None:
328
+ """Copy data processing files to the project template for cookiecutter templating.
243
329
 
244
330
  Args:
245
331
  project_template: Path to the project template directory
332
+ datastore_type: Type of datastore to use for data ingestion
246
333
  """
247
334
  data_ingestion_src = pathlib.Path(__file__).parent.parent.parent / "data_ingestion"
248
335
  data_ingestion_dst = project_template / "data_ingestion"
@@ -251,7 +338,10 @@ def copy_data_ingestion_files(project_template: pathlib.Path) -> None:
251
338
  logging.debug(
252
339
  f"Copying data processing files from {data_ingestion_src} to {data_ingestion_dst}"
253
340
  )
341
+
254
342
  copy_files(data_ingestion_src, data_ingestion_dst, overwrite=True)
343
+
344
+ logging.debug(f"Data ingestion files prepared for datastore: {datastore_type}")
255
345
  else:
256
346
  logging.warning(
257
347
  f"Data processing source directory not found at {data_ingestion_src}"
@@ -260,10 +350,11 @@ def copy_data_ingestion_files(project_template: pathlib.Path) -> None:
260
350
 
261
351
  def process_template(
262
352
  agent_name: str,
263
- template_dir: str,
353
+ template_dir: pathlib.Path,
264
354
  project_name: str,
265
355
  deployment_target: str | None = None,
266
356
  include_data_ingestion: bool = False,
357
+ datastore: str | None = None,
267
358
  output_dir: pathlib.Path | None = None,
268
359
  ) -> None:
269
360
  """Process the template directory and create a new project.
@@ -278,11 +369,11 @@ def process_template(
278
369
  """
279
370
  logging.debug(f"Processing template from {template_dir}")
280
371
  logging.debug(f"Project name: {project_name}")
281
- logging.debug(f"Include pipeline: {include_data_ingestion}")
372
+ logging.debug(f"Include pipeline: {datastore}")
282
373
  logging.debug(f"Output directory: {output_dir}")
283
374
 
284
375
  # Get paths
285
- agent_path = pathlib.Path(template_dir).parent # Get parent of template dir
376
+ agent_path = template_dir.parent # Get parent of template dir
286
377
  logging.debug(f"agent path: {agent_path}")
287
378
  logging.debug(f"agent path exists: {agent_path.exists()}")
288
379
  logging.debug(
@@ -340,19 +431,15 @@ def process_template(
340
431
  )
341
432
 
342
433
  # 3. Copy data ingestion files if needed
343
- template_config = load_template_config(pathlib.Path(template_dir))
344
- requires_data_ingestion = template_config.get("settings", {}).get(
345
- "requires_data_ingestion", False
346
- )
347
- should_include_data_ingestion = (
348
- include_data_ingestion or requires_data_ingestion
349
- )
350
-
351
- if should_include_data_ingestion:
352
- logging.debug("3. Including data processing files")
353
- copy_data_ingestion_files(project_template)
434
+ if include_data_ingestion and datastore:
435
+ logging.debug(
436
+ f"3. Including data processing files with datastore: {datastore}"
437
+ )
438
+ copy_data_ingestion_files(project_template, datastore)
354
439
 
355
440
  # 4. Process frontend files
441
+ # Load template config
442
+ template_config = load_template_config(pathlib.Path(template_dir))
356
443
  frontend_type = template_config.get("settings", {}).get(
357
444
  "frontend_type", DEFAULT_FRONTEND
358
445
  )
@@ -399,18 +486,11 @@ def process_template(
399
486
  template_config = load_template_config(pathlib.Path(template_dir))
400
487
 
401
488
  # Check if data processing should be included
402
- requires_data_ingestion = template_config.get("settings", {}).get(
403
- "requires_data_ingestion", False
404
- )
405
- should_include_data_ingestion = (
406
- include_data_ingestion or requires_data_ingestion
407
- )
408
-
409
- if should_include_data_ingestion:
489
+ if include_data_ingestion and datastore:
410
490
  logging.debug(
411
- "Including data processing files based on template config or user request"
491
+ f"Including data processing files with datastore: {datastore}"
412
492
  )
413
- copy_data_ingestion_files(project_template)
493
+ copy_data_ingestion_files(project_template, datastore)
414
494
 
415
495
  # Create cookiecutter.json in the template root
416
496
  # Process extra dependencies
@@ -427,12 +507,14 @@ def process_template(
427
507
  cookiecutter_config = {
428
508
  "project_name": "my-project",
429
509
  "agent_name": agent_name,
510
+ "package_version": get_current_version(),
430
511
  "agent_description": template_config.get("description", ""),
431
512
  "deployment_target": deployment_target or "",
432
513
  "frontend_type": frontend_type,
433
514
  "extra_dependencies": [extra_deps],
434
515
  "otel_instrumentations": otel_instrumentations,
435
- "data_ingestion": should_include_data_ingestion,
516
+ "data_ingestion": include_data_ingestion, # Use explicit flag for cookiecutter
517
+ "datastore_type": datastore if datastore else "",
436
518
  "_copy_without_render": [
437
519
  "*.ipynb", # Don't render notebooks
438
520
  "*.json", # Don't render JSON files
@@ -535,10 +617,10 @@ def process_template(
535
617
 
536
618
  def should_exclude_path(path: pathlib.Path, agent_name: str) -> bool:
537
619
  """Determine if a path should be excluded based on the agent type."""
538
- if agent_name == "multimodal_live_api":
539
- # Exclude the unit test utils folder and app/utils folder for multimodal_live_api
620
+ if agent_name == "live_api":
621
+ # Exclude the unit test utils folder and app/utils folder for live_api
540
622
  if "tests/unit/test_utils" in str(path) or "app/utils" in str(path):
541
- logging.debug(f"Excluding path for multimodal_live_api: {path}")
623
+ logging.debug(f"Excluding path for live_api: {path}")
542
624
  return True
543
625
  return False
544
626