Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions gradient_adk/cli/agent/deployment/deploy_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from typing import Protocol

import gradient_adk
from gradient_adk.logging import get_logger
from gradient_adk.digital_ocean_api.client_async import AsyncDigitalOceanGenAI
from gradient_adk.digital_ocean_api.models import (
Expand Down Expand Up @@ -73,6 +74,7 @@ async def deploy_agent(
source_dir: Path,
project_id: str,
api_token: str,
description: str | None = None,
) -> str:
"""Deploy an agent to the platform.

Expand All @@ -90,6 +92,7 @@ async def deploy_agent(
source_dir: Directory containing the agent code
project_id: DigitalOcean project ID
api_token: DigitalOcean API token to include in .env
description: Optional description for the deployment (max 1000 chars)

Returns:
agent_workspace_uuid: UUID of the deployed agent workspace
Expand Down Expand Up @@ -125,6 +128,7 @@ async def deploy_agent(
agent_deployment_name=agent_deployment_name,
code_artifact=code_artifact,
project_id=project_id,
description=description,
)

# Poll for deployment completion
Expand Down Expand Up @@ -302,6 +306,7 @@ async def _create_or_update_deployment(
agent_deployment_name: str,
code_artifact: AgentDeploymentCodeArtifact,
project_id: str,
description: str | None = None,
) -> str:
"""Create or update the deployment based on what exists.

Expand All @@ -312,6 +317,7 @@ async def _create_or_update_deployment(
agent_deployment_name: Name of the deployment
code_artifact: Code artifact metadata
project_id: Project ID
description: Optional description for the deployment

Returns:
UUID of the created release
Expand All @@ -324,6 +330,8 @@ async def _create_or_update_deployment(
agent_deployment_name=agent_deployment_name,
agent_deployment_code_artifact=code_artifact,
project_id=project_id,
library_version=gradient_adk.__version__,
description=description,
)
workspace_output = await self.client.create_agent_workspace(workspace_input)

Expand All @@ -349,6 +357,8 @@ async def _create_or_update_deployment(
agent_workspace_name=agent_workspace_name,
agent_deployment_name=agent_deployment_name,
agent_deployment_code_artifact=code_artifact,
library_version=gradient_adk.__version__,
description=description,
)
deployment_output = await self.client.create_agent_workspace_deployment(
deployment_input
Expand All @@ -366,6 +376,7 @@ async def _create_or_update_deployment(
agent_workspace_name=agent_workspace_name,
agent_deployment_name=agent_deployment_name,
agent_deployment_code_artifact=code_artifact,
library_version=gradient_adk.__version__,
)
release_output = await self.client.create_agent_deployment_release(
release_input
Expand Down
54 changes: 48 additions & 6 deletions gradient_adk/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
from gradient_adk.cli.agent.deployment.deploy_service import AgentDeployService
from gradient_adk.cli.agent.direct_launch_service import DirectLaunchService
from gradient_adk.cli.agent.traces_service import GalileoTracesService
from gradient_adk.cli.agent.evaluation_service import EvaluationService, validate_evaluation_dataset
from gradient_adk.cli.agent.evaluation_service import (
EvaluationService,
validate_evaluation_dataset,
)
from gradient_adk.cli.agent.env_utils import get_do_api_token, EnvironmentError


Expand Down Expand Up @@ -61,6 +64,7 @@ def _configure_agent(
agent_name: Optional[str] = None,
deployment_name: Optional[str] = None,
entrypoint_file: Optional[str] = None,
description: Optional[str] = None,
interactive: bool = True,
skip_entrypoint_prompt: bool = False, # New parameter for init
) -> None:
Expand All @@ -79,6 +83,7 @@ def _configure_agent(
agent_name=agent_name,
agent_environment=deployment_name,
entrypoint_file=entrypoint_file,
description=description,
interactive=False,
)
else:
Expand All @@ -87,6 +92,7 @@ def _configure_agent(
agent_name=agent_name,
agent_environment=deployment_name,
entrypoint_file=entrypoint_file,
description=description,
interactive=interactive,
)

Expand Down Expand Up @@ -140,6 +146,14 @@ def _create_project_structure() -> None:
env_path.write_text(env_content)


# Default description for agents created with `gradient agent init`
_DEFAULT_INIT_DESCRIPTION = (
"Example LangGraph agent. Invoke: curl -X POST <url> "
'-H "Authorization: Bearer $DIGITALOCEAN_API_TOKEN" '
'-H "Content-Type: application/json" -d \'{"prompt": "hello"}\''
)


@agent_app.command("init")
def agent_init(
agent_name: Optional[str] = typer.Option(
Expand All @@ -164,6 +178,7 @@ def agent_init(
agent_name=agent_name,
deployment_name=deployment_name,
entrypoint_file=entrypoint_file,
description=_DEFAULT_INIT_DESCRIPTION,
interactive=interactive,
skip_entrypoint_prompt=True, # Don't prompt for entrypoint in init
)
Expand All @@ -190,6 +205,11 @@ def agent_configure(
"--entrypoint-file",
help="Python file containing @entrypoint decorated function",
),
description: Optional[str] = typer.Option(
None,
"--description",
help="Description for the agent deployment (max 1000 characters)",
),
interactive: bool = typer.Option(
True, "--interactive/--no-interactive", help="Interactive prompt mode"
),
Expand All @@ -199,6 +219,7 @@ def agent_configure(
agent_name=agent_name,
deployment_name=deployment_name,
entrypoint_file=entrypoint_file,
description=description,
interactive=interactive,
)

Expand Down Expand Up @@ -380,6 +401,9 @@ async def deploy():
)
raise typer.Exit(1)

# Get description from config (optional)
description = _agent_config_manager.get_description()

# Create deploy service with injected client
deploy_service = AgentDeployService(client=client)

Expand All @@ -390,6 +414,7 @@ async def deploy():
source_dir=Path.cwd(),
project_id=project_id,
api_token=api_token,
description=description,
)

typer.echo(
Expand Down Expand Up @@ -420,6 +445,23 @@ async def deploy():
# Get error message with fallback
error_msg = str(e) if str(e) else repr(e)

# Check for "feature not enabled" error
if "feature not enabled" in error_msg.lower():
typer.echo(f"❌ Deployment failed: {error_msg}", err=True)
typer.echo(
"\nThe Gradient ADK is currently in public preview. To access it, enable it for your team via:",
err=True,
)
typer.echo(
" https://cloud.digitalocean.com/account/feature-preview",
err=True,
)
typer.echo(
"\nIt may take up to 5 minutes to take effect.",
err=True,
)
raise typer.Exit(1)

typer.echo(f"❌ Deployment failed: {error_msg}", err=True)

typer.echo(
Expand Down Expand Up @@ -647,14 +689,14 @@ def agent_evaluate(
def prompt_and_validate_dataset() -> str:
"""Prompt for dataset file path and validate it. Re-prompts on error."""
nonlocal dataset_file

while True:
if dataset_file is None:
dataset_file = typer.prompt("Dataset file path")

dataset_path = Path(dataset_file)
is_valid, errors = validate_evaluation_dataset(dataset_path)

if is_valid:
return dataset_file
else:
Expand All @@ -676,7 +718,7 @@ async def get_interactive_inputs():

if test_case_name is None:
test_case_name = typer.prompt("Evaluation test case name")

# Validate dataset file immediately when prompting
if dataset_file is None or not Path(dataset_file).exists():
prompt_and_validate_dataset()
Expand Down Expand Up @@ -922,4 +964,4 @@ async def run_evaluation():


def run():
app()
app()
4 changes: 4 additions & 0 deletions gradient_adk/cli/config/agent_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ def get_agent_environment(self) -> Optional[str]:
def get_entrypoint_file(self) -> Optional[str]:
raise NotImplementedError

def get_description(self) -> Optional[str]:
raise NotImplementedError

def configure(
self,
agent_name: Optional[str] = None,
agent_environment: Optional[str] = None,
entrypoint_file: Optional[str] = None,
description: Optional[str] = None,
interactive: bool = True,
) -> None:
raise NotImplementedError
28 changes: 26 additions & 2 deletions gradient_adk/cli/config/yaml_agent_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@ def get_entrypoint_file(self) -> Optional[str]:
config = self.load_config()
return config.get("entrypoint_file") if config else None

def get_description(self) -> Optional[str]:
config = self.load_config()
return config.get("description") if config else None

def configure(
self,
agent_name: Optional[str] = None,
agent_environment: Optional[str] = None,
entrypoint_file: Optional[str] = None,
description: Optional[str] = None,
interactive: bool = True,
) -> None:
"""Configure agent settings and save to YAML file."""
Expand All @@ -67,6 +72,7 @@ def configure(
entrypoint_file = typer.prompt(
"Entrypoint file (e.g., main.py, agent.py)", default="main.py"
)
# Note: description is optional and not prompted for in interactive mode
else:
if not all([agent_name, agent_environment, entrypoint_file]):
typer.echo(
Expand All @@ -92,8 +98,16 @@ def configure(
)
raise typer.Exit(1)

# Validate description length if provided
if description is not None and len(description) > 1000:
typer.echo(
f"Error: Description exceeds maximum length of 1000 characters (got {len(description)}).",
err=True,
)
raise typer.Exit(1)

self._validate_entrypoint_file(entrypoint_file)
self._save_config(agent_name, agent_environment, entrypoint_file)
self._save_config(agent_name, agent_environment, entrypoint_file, description)

def _validate_name(self, name: str) -> bool:
"""Validate that a name only contains alphanumeric characters, hyphens, and underscores."""
Expand Down Expand Up @@ -159,7 +173,11 @@ def _show_entrypoint_example(self) -> None:
)

def _save_config(
self, agent_name: str, agent_environment: str, entrypoint_file: str
self,
agent_name: str,
agent_environment: str,
entrypoint_file: str,
description: Optional[str] = None,
) -> None:
"""Save configuration to YAML file."""
config = {
Expand All @@ -168,13 +186,19 @@ def _save_config(
"entrypoint_file": entrypoint_file,
}

# Only include description if provided
if description is not None:
config["description"] = description

try:
with open(self.config_file, "w") as f:
yaml.safe_dump(config, f, default_flow_style=False)
typer.echo(f"✅ Configuration saved to {self.config_file}")
typer.echo(f" Agent workspace name: {agent_name}")
typer.echo(f" Agent deployment name: {agent_environment}")
typer.echo(f" Entrypoint: {entrypoint_file}")
if description:
typer.echo(f" Description: {description[:50]}{'...' if len(description) > 50 else ''}")
except Exception as e:
typer.echo(f"Error writing configuration file: {e}", err=True)
raise typer.Exit(1)
22 changes: 22 additions & 0 deletions gradient_adk/digital_ocean_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ class AgentDeploymentRelease(BaseModel):
created_by_user_email: Optional[str] = Field(
None, description="Email of user that created the agent deployment release"
)
library_version: Optional[str] = Field(
None, description="Version of the ADK library used to create this release"
)


class AgentLoggingConfig(BaseModel):
Expand Down Expand Up @@ -438,6 +441,14 @@ class CreateAgentWorkspaceDeploymentInput(BaseModel):
agent_deployment_code_artifact: AgentDeploymentCodeArtifact = Field(
..., description="The agent deployment code artifact"
)
library_version: Optional[str] = Field(
None, description="Version of the ADK library used to create this deployment"
)
description: Optional[str] = Field(
None,
description="Description of the agent deployment (max 1000 characters)",
max_length=1000,
)


class CreateAgentWorkspaceDeploymentOutput(BaseModel):
Expand All @@ -464,6 +475,9 @@ class CreateAgentDeploymentReleaseInput(BaseModel):
agent_deployment_code_artifact: AgentDeploymentCodeArtifact = Field(
..., description="The agent deployment code artifact"
)
library_version: Optional[str] = Field(
None, description="Version of the ADK library used to create this release"
)


class CreateAgentDeploymentReleaseOutput(BaseModel):
Expand Down Expand Up @@ -513,6 +527,14 @@ class CreateAgentWorkspaceInput(BaseModel):
..., description="The agent deployment code artifact"
)
project_id: str = Field(..., description="The project id")
library_version: Optional[str] = Field(
None, description="Version of the ADK library used to create this workspace"
)
description: Optional[str] = Field(
None,
description="Description of the agent workspace deployment (max 1000 characters)",
max_length=1000,
)


class CreateAgentWorkspaceOutput(BaseModel):
Expand Down
Loading