agent-starter-pack 0.1.6__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.
- {agent_starter_pack-0.1.6.dist-info → agent_starter_pack-0.2.0.dist-info}/METADATA +6 -6
- {agent_starter_pack-0.1.6.dist-info → agent_starter_pack-0.2.0.dist-info}/RECORD +78 -78
- agents/{agentic_rag_vertexai_search → agentic_rag}/README.md +3 -3
- agents/{agentic_rag_vertexai_search → agentic_rag}/app/agent.py +22 -6
- agents/agentic_rag/app/retrievers.py +132 -0
- agents/{agentic_rag_vertexai_search → agentic_rag}/notebooks/evaluating_langgraph_agent.ipynb +3 -3
- agents/{agentic_rag_vertexai_search → agentic_rag}/template/.templateconfig.yaml +3 -5
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +4 -4
- agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +3 -3
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +3 -3
- agents/{multimodal_live_api → live_api}/README.md +7 -0
- agents/{multimodal_live_api → live_api}/app/agent.py +3 -11
- agents/{multimodal_live_api → live_api}/app/server.py +3 -2
- agents/{multimodal_live_api → live_api}/template/.templateconfig.yaml +2 -2
- src/base_template/Makefile +12 -7
- src/base_template/README.md +71 -71
- src/base_template/app/utils/tracing.py +3 -1
- src/base_template/app/utils/typing.py +1 -0
- src/base_template/deployment/cd/deploy-to-prod.yaml +10 -4
- src/base_template/deployment/cd/staging.yaml +11 -10
- src/base_template/deployment/ci/pr_checks.yaml +1 -1
- src/base_template/deployment/terraform/apis.tf +6 -0
- src/base_template/deployment/terraform/build_triggers.tf +34 -21
- src/base_template/deployment/terraform/dev/iam.tf +13 -6
- src/base_template/deployment/terraform/dev/log_sinks.tf +25 -28
- src/base_template/deployment/terraform/dev/providers.tf +1 -0
- src/base_template/deployment/terraform/dev/storage.tf +69 -11
- src/base_template/deployment/terraform/dev/variables.tf +50 -53
- src/base_template/deployment/terraform/dev/vars/env.tfvars +13 -11
- src/base_template/deployment/terraform/iam.tf +3 -3
- src/base_template/deployment/terraform/log_sinks.tf +24 -26
- src/base_template/deployment/terraform/providers.tf +2 -0
- src/base_template/deployment/terraform/service_accounts.tf +7 -7
- src/base_template/deployment/terraform/storage.tf +123 -11
- src/base_template/deployment/terraform/variables.tf +49 -70
- src/base_template/deployment/terraform/vars/env.tfvars +12 -17
- src/base_template/pyproject.toml +4 -3
- src/cli/commands/create.py +79 -19
- src/cli/commands/setup_cicd.py +91 -22
- src/cli/main.py +3 -1
- src/cli/utils/__init__.py +9 -2
- src/cli/utils/cicd.py +12 -0
- src/cli/utils/datastores.py +32 -0
- src/cli/utils/gcp.py +4 -6
- src/cli/utils/template.py +127 -45
- src/cli/utils/version.py +87 -0
- src/data_ingestion/README.md +24 -19
- src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +135 -2
- src/data_ingestion/data_ingestion_pipeline/components/process_data.py +276 -2
- src/data_ingestion/data_ingestion_pipeline/pipeline.py +28 -5
- src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +49 -14
- src/data_ingestion/pyproject.toml +1 -0
- src/deployment_targets/agent_engine/app/agent_engine_app.py +3 -1
- src/deployment_targets/cloud_run/tests/unit/test_server.py +15 -33
- src/frontends/live_api_react/frontend/package-lock.json +226 -186
- src/frontends/live_api_react/frontend/package.json +1 -1
- src/frontends/streamlit/frontend/utils/stream_handler.py +5 -5
- src/resources/containers/data_processing/Dockerfile +3 -1
- src/resources/locks/{uv-agentic_rag_vertexai_search-agent_engine.lock → uv-agentic_rag-agent_engine.lock} +747 -694
- src/resources/locks/{uv-agentic_rag_vertexai_search-cloud_run.lock → uv-agentic_rag-cloud_run.lock} +944 -806
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +651 -694
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +813 -789
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +666 -686
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +848 -798
- src/resources/locks/{uv-multimodal_live_api-cloud_run.lock → uv-live_api-cloud_run.lock} +856 -791
- src/resources/setup_cicd/cicd_variables.tf +5 -0
- src/resources/setup_cicd/github.tf +4 -2
- src/utils/watch_and_rebuild.py +14 -0
- agents/agentic_rag_vertexai_search/app/retrievers.py +0 -79
- src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +0 -22
- src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +0 -20
- {agent_starter_pack-0.1.6.dist-info → agent_starter_pack-0.2.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.1.6.dist-info → agent_starter_pack-0.2.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.1.6.dist-info → agent_starter_pack-0.2.0.dist-info}/licenses/LICENSE +0 -0
- /agents/{agentic_rag_vertexai_search → agentic_rag}/app/templates.py +0 -0
- /agents/{agentic_rag_vertexai_search → agentic_rag}/tests/integration/test_agent.py +0 -0
- /agents/{multimodal_live_api → live_api}/app/templates.py +0 -0
- /agents/{multimodal_live_api → live_api}/app/vector_store.py +0 -0
- /agents/{multimodal_live_api → live_api}/tests/integration/test_server_e2e.py +0 -0
- /agents/{multimodal_live_api → live_api}/tests/load_test/load_test.py +0 -0
- /agents/{multimodal_live_api → live_api}/tests/unit/test_server.py +0 -0
src/cli/commands/setup_cicd.py
CHANGED
|
@@ -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(
|
|
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 = "
|
|
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 =
|
|
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 = "
|
|
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(
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
34
|
-
|
|
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
|
|
196
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
322
|
+
return template_path
|
|
239
323
|
|
|
240
324
|
|
|
241
|
-
def copy_data_ingestion_files(
|
|
242
|
-
|
|
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:
|
|
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: {
|
|
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 =
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
|
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":
|
|
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 == "
|
|
539
|
-
# Exclude the unit test utils folder and app/utils folder for
|
|
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
|
|
623
|
+
logging.debug(f"Excluding path for live_api: {path}")
|
|
542
624
|
return True
|
|
543
625
|
return False
|
|
544
626
|
|