agentex-sdk 0.8.1__py3-none-any.whl → 0.9.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.
- agentex/_base_client.py +134 -11
- agentex/_models.py +16 -1
- agentex/_types.py +9 -0
- agentex/_version.py +1 -1
- agentex/lib/cli/commands/agents.py +141 -73
- agentex/lib/cli/commands/init.py +13 -2
- agentex/lib/cli/handlers/agent_handlers.py +130 -12
- agentex/lib/cli/templates/sync-openai-agents/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/sync-openai-agents/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/sync-openai-agents/Dockerfile.j2 +43 -0
- agentex/lib/cli/templates/sync-openai-agents/README.md.j2 +313 -0
- agentex/lib/cli/templates/sync-openai-agents/dev.ipynb.j2 +167 -0
- agentex/lib/cli/templates/sync-openai-agents/environments.yaml.j2 +53 -0
- agentex/lib/cli/templates/sync-openai-agents/manifest.yaml.j2 +115 -0
- agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 +137 -0
- agentex/lib/cli/templates/sync-openai-agents/pyproject.toml.j2 +32 -0
- agentex/lib/cli/templates/sync-openai-agents/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/sync-openai-agents/test_agent.py.j2 +70 -0
- agentex/lib/sdk/config/environment_config.py +113 -73
- agentex/lib/sdk/config/validation.py +62 -61
- {agentex_sdk-0.8.1.dist-info → agentex_sdk-0.9.0.dist-info}/METADATA +1 -1
- {agentex_sdk-0.8.1.dist-info → agentex_sdk-0.9.0.dist-info}/RECORD +25 -14
- {agentex_sdk-0.8.1.dist-info → agentex_sdk-0.9.0.dist-info}/licenses/LICENSE +1 -1
- {agentex_sdk-0.8.1.dist-info → agentex_sdk-0.9.0.dist-info}/WHEEL +0 -0
- {agentex_sdk-0.8.1.dist-info → agentex_sdk-0.9.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import AsyncGenerator, List
|
|
3
|
+
|
|
4
|
+
from agentex.lib import adk
|
|
5
|
+
from agentex.lib.adk.providers._modules.sync_provider import SyncStreamingProvider, convert_openai_to_agentex_events
|
|
6
|
+
from agentex.lib.sdk.fastacp.fastacp import FastACP
|
|
7
|
+
from agentex.lib.types.acp import SendMessageParams
|
|
8
|
+
from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config
|
|
9
|
+
from agentex.lib.types.tracing import SGPTracingProcessorConfig
|
|
10
|
+
from agentex.lib.utils.model_utils import BaseModel
|
|
11
|
+
|
|
12
|
+
from agentex.types.task_message_update import TaskMessageUpdate
|
|
13
|
+
from agentex.types.task_message_content import TaskMessageContent
|
|
14
|
+
from agentex.lib.utils.logging import make_logger
|
|
15
|
+
from agents import Agent, Runner, RunConfig, function_tool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = make_logger(__name__)
|
|
19
|
+
|
|
20
|
+
SGP_API_KEY = os.environ.get("SGP_API_KEY", "")
|
|
21
|
+
SGP_ACCOUNT_ID = os.environ.get("SGP_ACCOUNT_ID", "")
|
|
22
|
+
|
|
23
|
+
if SGP_API_KEY and SGP_ACCOUNT_ID:
|
|
24
|
+
add_tracing_processor_config(
|
|
25
|
+
SGPTracingProcessorConfig(
|
|
26
|
+
sgp_api_key=SGP_API_KEY,
|
|
27
|
+
sgp_account_id=SGP_ACCOUNT_ID,
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
MODEL = "gpt-4o-mini"
|
|
33
|
+
|
|
34
|
+
SYSTEM_PROMPT = """
|
|
35
|
+
<role>
|
|
36
|
+
You are a helpful assistant. Use your tools to help the user.
|
|
37
|
+
</role>
|
|
38
|
+
|
|
39
|
+
<communication_style>
|
|
40
|
+
Communicate in a witty and friendly manner
|
|
41
|
+
</communication_style>
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
AGENT_NAME = "{{ agent_name }}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@function_tool
|
|
48
|
+
async def get_weather() -> str:
|
|
49
|
+
"""
|
|
50
|
+
Get the current weather.
|
|
51
|
+
|
|
52
|
+
This is a dummy activity that returns a hardcoded string for demo purposes.
|
|
53
|
+
Replace this with a real weather API call in your implementation.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
A string describing the current weather conditions.
|
|
57
|
+
"""
|
|
58
|
+
logger.info("get_weather activity called")
|
|
59
|
+
return "Sunny, 72°F"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Create an ACP server
|
|
64
|
+
acp = FastACP.create(
|
|
65
|
+
acp_type="sync",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
class StateModel(BaseModel):
|
|
69
|
+
input_list: List[dict]
|
|
70
|
+
turn_number: int
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@acp.on_message_send
|
|
74
|
+
async def handle_message_send(
|
|
75
|
+
params: SendMessageParams
|
|
76
|
+
) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
|
|
77
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
78
|
+
yield StreamTaskMessageFull(
|
|
79
|
+
index=0,
|
|
80
|
+
type="full",
|
|
81
|
+
content=TextContent(
|
|
82
|
+
author="agent",
|
|
83
|
+
content="Hey, sorry I'm unable to respond to your message because you're running this example without an OpenAI API key. Please set the OPENAI_API_KEY environment variable to run this example. Do this by either by adding a .env file to the project/ directory or by setting the environment variable in your terminal.",
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
user_prompt = params.content.content
|
|
88
|
+
|
|
89
|
+
# Retrieve the task state. Each event is handled as a new turn, so we need to get the state for the current turn.
|
|
90
|
+
task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id)
|
|
91
|
+
if not task_state:
|
|
92
|
+
# If the state doesn't exist, create it.
|
|
93
|
+
state = StateModel(input_list=[], turn_number=0)
|
|
94
|
+
task_state = await adk.state.create(task_id=params.task.id, agent_id=params.agent.id, state=state)
|
|
95
|
+
else:
|
|
96
|
+
state = StateModel.model_validate(task_state.state)
|
|
97
|
+
|
|
98
|
+
state.turn_number += 1
|
|
99
|
+
state.input_list.append({"role": "user", "content": user_prompt})
|
|
100
|
+
|
|
101
|
+
# Initialize the sync provider and run config to allow for tracing
|
|
102
|
+
provider = SyncStreamingProvider(
|
|
103
|
+
trace_id=params.task.id,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
run_config = RunConfig(
|
|
107
|
+
model_provider=provider,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Initialize the agent
|
|
111
|
+
agent = Agent(
|
|
112
|
+
name=AGENT_NAME,
|
|
113
|
+
instructions=SYSTEM_PROMPT,
|
|
114
|
+
model=MODEL,
|
|
115
|
+
tools=[get_weather],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Run the agent with the conversation history from state
|
|
119
|
+
result = Runner.run_streamed(
|
|
120
|
+
agent,
|
|
121
|
+
state.input_list,
|
|
122
|
+
run_config=run_config
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Convert the OpenAI events to Agentex events and stream them back to the client
|
|
126
|
+
async for agentex_event in convert_openai_to_agentex_events(result.stream_events()):
|
|
127
|
+
yield agentex_event
|
|
128
|
+
|
|
129
|
+
# After streaming is complete, update state with the full conversation history
|
|
130
|
+
state.input_list = result.to_input_list()
|
|
131
|
+
await adk.state.update(
|
|
132
|
+
state_id=task_state.id,
|
|
133
|
+
task_id=params.task.id,
|
|
134
|
+
agent_id=params.agent.id,
|
|
135
|
+
state=state,
|
|
136
|
+
trace_id=params.task.id,
|
|
137
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "{{ project_name }}"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "{{ description }}"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"agentex-sdk",
|
|
12
|
+
"scale-gp",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest",
|
|
18
|
+
"black",
|
|
19
|
+
"isort",
|
|
20
|
+
"flake8",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["project"]
|
|
25
|
+
|
|
26
|
+
[tool.black]
|
|
27
|
+
line-length = 88
|
|
28
|
+
target-version = ['py312']
|
|
29
|
+
|
|
30
|
+
[tool.isort]
|
|
31
|
+
profile = "black"
|
|
32
|
+
line_length = 88
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sample tests for AgentEx ACP agent.
|
|
3
|
+
|
|
4
|
+
This test suite demonstrates how to test the main AgentEx API functions:
|
|
5
|
+
- Non-streaming message sending
|
|
6
|
+
- Streaming message sending
|
|
7
|
+
- Task creation via RPC
|
|
8
|
+
|
|
9
|
+
To run these tests:
|
|
10
|
+
1. Make sure the agent is running (via docker-compose or `agentex agents run`)
|
|
11
|
+
2. Set the AGENTEX_API_BASE_URL environment variable if not using default
|
|
12
|
+
3. Run: pytest test_agent.py -v
|
|
13
|
+
|
|
14
|
+
Configuration:
|
|
15
|
+
- AGENTEX_API_BASE_URL: Base URL for the AgentEx server (default: http://localhost:5003)
|
|
16
|
+
- AGENT_NAME: Name of the agent to test (default: {{ agent_name }})
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import pytest
|
|
21
|
+
from agentex import Agentex
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Configuration from environment variables
|
|
25
|
+
AGENTEX_API_BASE_URL = os.environ.get("AGENTEX_API_BASE_URL", "http://localhost:5003")
|
|
26
|
+
AGENT_NAME = os.environ.get("AGENT_NAME", "{{ agent_name }}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def client():
|
|
31
|
+
"""Create an AgentEx client instance for testing."""
|
|
32
|
+
return Agentex(base_url=AGENTEX_API_BASE_URL)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def agent_name():
|
|
37
|
+
"""Return the agent name for testing."""
|
|
38
|
+
return AGENT_NAME
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def agent_id(client, agent_name):
|
|
43
|
+
"""Retrieve the agent ID based on the agent name."""
|
|
44
|
+
agents = client.agents.list()
|
|
45
|
+
for agent in agents:
|
|
46
|
+
if agent.name == agent_name:
|
|
47
|
+
return agent.id
|
|
48
|
+
raise ValueError(f"Agent with name {agent_name} not found.")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestNonStreamingMessages:
|
|
52
|
+
"""Test non-streaming message sending."""
|
|
53
|
+
|
|
54
|
+
def test_send_message(self, client: Agentex, _agent_name: str):
|
|
55
|
+
"""Test sending a message and receiving a response."""
|
|
56
|
+
# TODO: Fill in the test based on what data your agent is expected to handle
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestStreamingMessages:
|
|
61
|
+
"""Test streaming message sending."""
|
|
62
|
+
|
|
63
|
+
def test_send_stream_message(self, client: Agentex, _agent_name: str):
|
|
64
|
+
"""Test streaming a message and aggregating deltas."""
|
|
65
|
+
# TODO: Fill in the test based on what data your agent is expected to handle
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
pytest.main([__file__, "-v"])
|
|
@@ -18,13 +18,12 @@ from agentex.lib.utils.model_utils import BaseModel as UtilsBaseModel
|
|
|
18
18
|
|
|
19
19
|
class AgentAuthConfig(BaseModel):
|
|
20
20
|
"""Authentication configuration for an agent in a specific environment."""
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
principal: Dict[str, Any] = Field(
|
|
23
|
-
...,
|
|
24
|
-
description="Principal configuration for agent authorization and registration"
|
|
23
|
+
..., description="Principal configuration for agent authorization and registration"
|
|
25
24
|
)
|
|
26
|
-
|
|
27
|
-
@field_validator(
|
|
25
|
+
|
|
26
|
+
@field_validator("principal")
|
|
28
27
|
@classmethod
|
|
29
28
|
def validate_principal_required_fields(cls, v: Any) -> Dict[str, Any]:
|
|
30
29
|
"""Ensure principal has required fields for agent registration."""
|
|
@@ -35,125 +34,166 @@ class AgentAuthConfig(BaseModel):
|
|
|
35
34
|
|
|
36
35
|
class AgentKubernetesConfig(BaseModel):
|
|
37
36
|
"""Kubernetes configuration for an agent in a specific environment."""
|
|
38
|
-
|
|
39
|
-
namespace: str = Field(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
@field_validator('namespace')
|
|
37
|
+
|
|
38
|
+
namespace: str = Field(..., description="Kubernetes namespace where the agent will be deployed")
|
|
39
|
+
|
|
40
|
+
@field_validator("namespace")
|
|
45
41
|
@classmethod
|
|
46
42
|
def validate_namespace_format(cls, v: str) -> str:
|
|
47
43
|
"""Ensure namespace follows Kubernetes naming conventions."""
|
|
48
44
|
if not v or not v.strip():
|
|
49
45
|
raise ValueError("Namespace cannot be empty")
|
|
50
|
-
|
|
46
|
+
|
|
51
47
|
# Basic Kubernetes namespace validation
|
|
52
48
|
namespace = v.strip().lower()
|
|
53
|
-
if not namespace.replace(
|
|
54
|
-
raise ValueError(
|
|
55
|
-
|
|
56
|
-
"hyphens, and periods"
|
|
57
|
-
)
|
|
58
|
-
|
|
49
|
+
if not namespace.replace("-", "").replace(".", "").isalnum():
|
|
50
|
+
raise ValueError(f"Namespace '{v}' must contain only lowercase letters, numbers, hyphens, and periods")
|
|
51
|
+
|
|
59
52
|
if len(namespace) > 63:
|
|
60
53
|
raise ValueError(f"Namespace '{v}' cannot exceed 63 characters")
|
|
61
|
-
|
|
54
|
+
|
|
62
55
|
return namespace
|
|
63
56
|
|
|
64
57
|
|
|
65
58
|
class AgentEnvironmentConfig(BaseModel):
|
|
66
59
|
"""Complete configuration for an agent in a specific environment."""
|
|
67
|
-
|
|
68
|
-
kubernetes: AgentKubernetesConfig | None = Field(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
auth: AgentAuthConfig = Field(
|
|
73
|
-
...,
|
|
74
|
-
description="Authentication and authorization configuration"
|
|
75
|
-
)
|
|
76
|
-
helm_repository_name: str = Field(
|
|
77
|
-
default="scale-egp",
|
|
78
|
-
description="Helm repository name for the environment"
|
|
60
|
+
|
|
61
|
+
kubernetes: AgentKubernetesConfig | None = Field(default=None, description="Kubernetes deployment configuration")
|
|
62
|
+
environment: str | None = Field(
|
|
63
|
+
default=None,
|
|
64
|
+
description="The environment keyword that this specific environment maps to: either dev, staging, prod",
|
|
79
65
|
)
|
|
66
|
+
auth: AgentAuthConfig = Field(..., description="Authentication and authorization configuration")
|
|
67
|
+
helm_repository_name: str = Field(default="scale-egp", description="Helm repository name for the environment")
|
|
80
68
|
helm_repository_url: str = Field(
|
|
81
|
-
default="https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts",
|
|
82
|
-
description="Helm repository url for the environment"
|
|
69
|
+
default="https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts",
|
|
70
|
+
description="Helm repository url for the environment",
|
|
83
71
|
)
|
|
84
72
|
helm_overrides: Dict[str, Any] = Field(
|
|
85
|
-
default_factory=dict,
|
|
86
|
-
description="Helm chart value overrides for environment-specific tuning"
|
|
73
|
+
default_factory=dict, description="Helm chart value overrides for environment-specific tuning"
|
|
87
74
|
)
|
|
88
75
|
|
|
89
76
|
|
|
90
77
|
class AgentEnvironmentsConfig(UtilsBaseModel):
|
|
91
78
|
"""All environment configurations for an agent."""
|
|
92
|
-
|
|
93
|
-
schema_version: str = Field(
|
|
94
|
-
default="v1",
|
|
95
|
-
description="Schema version for validation and compatibility"
|
|
96
|
-
)
|
|
79
|
+
|
|
80
|
+
schema_version: str = Field(default="v1", description="Schema version for validation and compatibility")
|
|
97
81
|
environments: Dict[str, AgentEnvironmentConfig] = Field(
|
|
98
|
-
...,
|
|
99
|
-
description="Environment-specific configurations (dev, prod, etc.)"
|
|
82
|
+
..., description="Environment-specific configurations (dev, prod, etc.)"
|
|
100
83
|
)
|
|
101
|
-
|
|
102
|
-
@field_validator(
|
|
84
|
+
|
|
85
|
+
@field_validator("schema_version")
|
|
103
86
|
@classmethod
|
|
104
87
|
def validate_schema_version(cls, v: str) -> str:
|
|
105
88
|
"""Ensure schema version is supported."""
|
|
106
|
-
supported_versions = [
|
|
89
|
+
supported_versions = ["v1"]
|
|
107
90
|
if v not in supported_versions:
|
|
108
|
-
raise ValueError(
|
|
109
|
-
f"Schema version '{v}' not supported. "
|
|
110
|
-
f"Supported versions: {', '.join(supported_versions)}"
|
|
111
|
-
)
|
|
91
|
+
raise ValueError(f"Schema version '{v}' not supported. Supported versions: {', '.join(supported_versions)}")
|
|
112
92
|
return v
|
|
113
|
-
|
|
114
|
-
@field_validator(
|
|
93
|
+
|
|
94
|
+
@field_validator("environments")
|
|
115
95
|
@classmethod
|
|
116
96
|
def validate_environments_not_empty(cls, v: Dict[str, AgentEnvironmentConfig]) -> Dict[str, AgentEnvironmentConfig]:
|
|
117
97
|
"""Ensure at least one environment is defined."""
|
|
118
98
|
if not v:
|
|
119
99
|
raise ValueError("At least one environment must be defined")
|
|
120
100
|
return v
|
|
121
|
-
|
|
101
|
+
|
|
122
102
|
def get_config_for_env(self, env_name: str) -> AgentEnvironmentConfig:
|
|
123
103
|
"""Get configuration for a specific environment.
|
|
124
|
-
|
|
104
|
+
|
|
125
105
|
Args:
|
|
126
106
|
env_name: Name of the environment (e.g., 'dev', 'prod')
|
|
127
|
-
|
|
107
|
+
|
|
128
108
|
Returns:
|
|
129
109
|
AgentEnvironmentConfig for the specified environment
|
|
130
|
-
|
|
110
|
+
|
|
131
111
|
Raises:
|
|
132
112
|
ValueError: If environment is not found
|
|
133
113
|
"""
|
|
134
114
|
if env_name not in self.environments:
|
|
135
|
-
available_envs =
|
|
115
|
+
available_envs = ", ".join(self.environments.keys())
|
|
136
116
|
raise ValueError(
|
|
137
|
-
f"Environment '{env_name}' not found in environments.yaml. "
|
|
138
|
-
f"Available environments: {available_envs}"
|
|
117
|
+
f"Environment '{env_name}' not found in environments.yaml. Available environments: {available_envs}"
|
|
139
118
|
)
|
|
140
119
|
return self.environments[env_name]
|
|
141
|
-
|
|
120
|
+
|
|
121
|
+
def get_configs_for_env(self, env_target: str) -> dict[str, AgentEnvironmentConfig]:
|
|
122
|
+
"""Get configuration for a specific environment based on the expected mapping.
|
|
123
|
+
The environment is either:
|
|
124
|
+
1. explicitly specified like so using a key-map in the environments conifg:
|
|
125
|
+
environments:
|
|
126
|
+
dev-aws:
|
|
127
|
+
environment: "dev"
|
|
128
|
+
kubernetes:
|
|
129
|
+
namespace: "sgp-000-hello-acp"
|
|
130
|
+
auth:
|
|
131
|
+
principal:
|
|
132
|
+
user_id: 73d0c8bd-4726-434c-9686-eb627d89f078
|
|
133
|
+
account_id: 6887f093600ecd59bbbd3095
|
|
134
|
+
helm_overrides:
|
|
135
|
+
|
|
136
|
+
or: it it can be defined at the top level:
|
|
137
|
+
dev:
|
|
138
|
+
kubernetes:
|
|
139
|
+
namespace: "sgp-000-hello-acp"
|
|
140
|
+
auth:
|
|
141
|
+
principal:
|
|
142
|
+
user_id: 73d0c8bd-4726-434c-9686-eb627d89f078
|
|
143
|
+
account_id: 6887f093600ecd59bbbd3095
|
|
144
|
+
helm_overrides:
|
|
145
|
+
|
|
146
|
+
if the environment field is not explicitly set, we assume its the same as
|
|
147
|
+
the name of the environment
|
|
148
|
+
Args:
|
|
149
|
+
env_target: Name of the environment target (e.g., 'dev', 'prod')
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
AgentEnvironmentConfig for the specified environment
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If environment is not found
|
|
156
|
+
"""
|
|
157
|
+
envs_to_deploy = {}
|
|
158
|
+
if env_target in self.environments:
|
|
159
|
+
# this supports if the top-level key is just "dev, staging, etc" and matches
|
|
160
|
+
# the environment name exactly without any explicit mapping
|
|
161
|
+
envs_to_deploy[env_target] = self.environments[env_target]
|
|
162
|
+
|
|
163
|
+
for env_name, config in self.environments.items():
|
|
164
|
+
if config.environment == env_target:
|
|
165
|
+
envs_to_deploy[env_name] = config
|
|
166
|
+
|
|
167
|
+
if len(envs_to_deploy) == 0:
|
|
168
|
+
## this just finds environments for each target, so "available_envs" refers to each target environment
|
|
169
|
+
|
|
170
|
+
available_envs = set()
|
|
171
|
+
for env_name, config in self.environments.items():
|
|
172
|
+
if config.environment is not None:
|
|
173
|
+
available_envs.add(config.environment)
|
|
174
|
+
else:
|
|
175
|
+
available_envs.add(env_name)
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Environment '{env_target}' not found in environments.yaml. Available environments: {available_envs}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return envs_to_deploy
|
|
181
|
+
|
|
142
182
|
def list_environments(self) -> list[str]:
|
|
143
183
|
"""Get list of all configured environment names."""
|
|
144
184
|
return list(self.environments.keys())
|
|
145
|
-
|
|
185
|
+
|
|
146
186
|
@classmethod
|
|
147
187
|
@override
|
|
148
188
|
def from_yaml(cls, file_path: str) -> "AgentEnvironmentsConfig":
|
|
149
189
|
"""Load configuration from environments.yaml file.
|
|
150
|
-
|
|
190
|
+
|
|
151
191
|
Args:
|
|
152
192
|
file_path: Path to environments.yaml file
|
|
153
|
-
|
|
193
|
+
|
|
154
194
|
Returns:
|
|
155
195
|
Parsed and validated AgentEnvironmentsConfig
|
|
156
|
-
|
|
196
|
+
|
|
157
197
|
Raises:
|
|
158
198
|
FileNotFoundError: If file doesn't exist
|
|
159
199
|
ValueError: If file is invalid or doesn't validate
|
|
@@ -161,16 +201,16 @@ class AgentEnvironmentsConfig(UtilsBaseModel):
|
|
|
161
201
|
path = Path(file_path)
|
|
162
202
|
if not path.exists():
|
|
163
203
|
raise FileNotFoundError(f"environments.yaml not found: {file_path}")
|
|
164
|
-
|
|
204
|
+
|
|
165
205
|
try:
|
|
166
|
-
with open(path,
|
|
206
|
+
with open(path, "r") as f:
|
|
167
207
|
data = yaml.safe_load(f)
|
|
168
|
-
|
|
208
|
+
|
|
169
209
|
if not data:
|
|
170
210
|
raise ValueError("environments.yaml file is empty")
|
|
171
|
-
|
|
211
|
+
|
|
172
212
|
return cls.model_validate(data)
|
|
173
|
-
|
|
213
|
+
|
|
174
214
|
except yaml.YAMLError as e:
|
|
175
215
|
raise ValueError(f"Invalid YAML format in {file_path}: {e}") from e
|
|
176
216
|
except Exception as e:
|
|
@@ -179,18 +219,18 @@ class AgentEnvironmentsConfig(UtilsBaseModel):
|
|
|
179
219
|
|
|
180
220
|
def load_environments_config_from_manifest_dir(manifest_dir: Path) -> AgentEnvironmentsConfig | None:
|
|
181
221
|
"""Helper function to load environments.yaml from same directory as manifest.yaml.
|
|
182
|
-
|
|
222
|
+
|
|
183
223
|
Args:
|
|
184
224
|
manifest_dir: Directory containing manifest.yaml
|
|
185
|
-
|
|
225
|
+
|
|
186
226
|
Returns:
|
|
187
227
|
AgentEnvironmentsConfig if environments.yaml exists, None otherwise
|
|
188
|
-
|
|
228
|
+
|
|
189
229
|
Raises:
|
|
190
230
|
ValueError: If environments.yaml exists but is invalid
|
|
191
231
|
"""
|
|
192
232
|
environments_file = manifest_dir / "environments.yaml"
|
|
193
233
|
if not environments_file.exists():
|
|
194
234
|
return None
|
|
195
|
-
|
|
235
|
+
|
|
196
236
|
return AgentEnvironmentsConfig.from_yaml(str(environments_file))
|