Skip to content
Open
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
252 changes: 110 additions & 142 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,33 +99,7 @@ def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None:

EXPOSE {port}

CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {otel_to_cloud_option} {allow_origins_option} {a2a_option} {trigger_sources_option} "/app/agents"
"""

_AGENT_ENGINE_APP_TEMPLATE: Final[str] = """
import os
import vertexai
from vertexai.agent_engines import AdkApp

if {is_config_agent}:
from google.adk.agents import config_agent_utils
config_path = os.path.join(os.path.dirname(__file__), "root_agent.yaml")
root_agent = config_agent_utils.from_config(config_path)
else:
from .agent import {adk_app_object}

if {express_mode}: # Whether or not to use Express Mode
vertexai.init(api_key=os.environ.get("GOOGLE_API_KEY"))
else:
vertexai.init(
project=os.environ.get("GOOGLE_CLOUD_PROJECT"),
location=os.environ.get("GOOGLE_CLOUD_LOCATION"),
)

adk_app = AdkApp(
{adk_app_type}={adk_app_object},
enable_tracing={trace_to_cloud_option},
)
CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {otel_to_cloud_option} {allow_origins_option} {a2a_option} {trigger_sources_option} {gemini_enterprise_option} "/app/agents"
"""

_AGENT_ENGINE_CLASS_METHODS = [
Expand Down Expand Up @@ -830,7 +804,7 @@ def to_agent_engine(
*,
agent_folder: str,
temp_folder: Optional[str] = None,
adk_app: str,
adk_app: Optional[str] = None,
staging_bucket: Optional[str] = None,
trace_to_cloud: Optional[bool] = None,
otel_to_cloud: Optional[bool] = None,
Expand All @@ -846,36 +820,26 @@ def to_agent_engine(
env_file: Optional[str] = None,
agent_engine_config_file: Optional[str] = None,
skip_agent_import_validation: bool = True,
trigger_sources: Optional[str] = None,
artifact_service_uri: Optional[str] = None,
adk_version: Optional[str] = None,
):
"""Deploys an agent to Vertex AI Agent Engine.

`agent_folder` should contain the following files:

- __init__.py
- agent.py
- <adk_app>.py (optional, for customization; will be autogenerated otherwise)
- requirements.txt (optional, for additional dependencies)
- .env (optional, for environment variables)
- ... (other required source files)

The contents of `adk_app` should look something like:

```
from agent import <adk_app_object>
from vertexai.agent_engines import AdkApp

adk_app = AdkApp(
agent=<adk_app_object>, # or `app=<adk_app_object>`
)
```

Args:
agent_folder (str): The folder (absolute path) containing the agent source
code.
temp_folder (str): The temp folder for the generated Agent Engine source
files. It will be replaced with the generated files if it already exists.
adk_app (str): The name of the file (without .py) containing the AdkApp
instance.
adk_app (str): Deprecated. This argument is no longer required or used.
staging_bucket (str): Deprecated. This argument is no longer required or
used.
trace_to_cloud (bool): Whether to enable Cloud Trace.
Expand All @@ -884,13 +848,12 @@ def to_agent_engine(
api_key (str): Optional. The API key to use for Express Mode.
If not provided, the API key from the GOOGLE_API_KEY environment variable
will be used. It will only be used if GOOGLE_GENAI_USE_VERTEXAI is true.
adk_app_object (str): Optional. The Python object corresponding to the root
ADK agent or app. Defaults to `root_agent` if not specified.
adk_app_object (str): Deprecated. This argument is no longer required or
used.
agent_engine_id (str): Optional. The ID of the Agent Engine instance to
update. If not specified, a new Agent Engine instance will be created.
absolutize_imports (bool): Optional. Default is True. Whether to absolutize
imports. If True, all relative imports will be converted to absolute
import statements.
absolutize_imports (bool): Deprecated. This argument is no longer required
or used.
project (str): Optional. Google Cloud project id for the deployed agent. If
not specified, the project from the `GOOGLE_CLOUD_PROJECT` environment
variable will be used. It will be ignored if `api_key` is specified.
Expand All @@ -899,31 +862,41 @@ def to_agent_engine(
variable will be used. It will be ignored if `api_key` is specified.
display_name (str): Optional. The display name of the Agent Engine.
description (str): Optional. The description of the Agent Engine.
requirements_file (str): Optional. The filepath to the `requirements.txt`
file to use. If not specified, the `requirements.txt` file in the
`agent_folder` will be used.
requirements_file (str): Deprecated. This argument is no longer required or
used.
env_file (str): Optional. The filepath to the `.env` file for environment
variables. If not specified, the `.env` file in the `agent_folder` will be
used. The values of `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`
will be overridden by `project` and `region` if they are specified.
agent_engine_config_file (str): The filepath to the agent engine config file
to use. If not specified, the `.agent_engine_config.json` file in the
`agent_folder` will be used.
skip_agent_import_validation (bool): Optional. Default is True. If True,
skip the pre-deployment import validation of `agent.py`. This can be
useful when the local environment does not have the same dependencies as
the deployment environment.
skip_agent_import_validation (bool): Deprecated. This argument is no longer
required or used.
trigger_sources (str): Optional. Comma-separated list of trigger sources to
enable (e.g., 'pubsub,eventarc'). Registers /trigger/* endpoints for
batch and event-driven agent invocations.
artifact_service_uri (str): Optional. The URI of the artifact service.
adk_version (str): Optional. The ADK version to use in Agent Engine
deployment.
"""
app_name = os.path.basename(agent_folder)
display_name = display_name or app_name
parent_folder = os.path.dirname(agent_folder)
adk_app_object = adk_app_object or 'root_agent'
if adk_app_object not in ['root_agent', 'app']:
click.echo(
f'Invalid adk_app_object: {adk_app_object}. Please use "root_agent"'
' or "app".'
if adk_app_object:
warnings.warn(
'WARNING: `--adk_app_object` is deprecated and will be removed in the'
' future. Please drop it from the list of arguments.',
DeprecationWarning,
stacklevel=2,
)
if adk_app:
warnings.warn(
'WARNING: `adk_app` is deprecated and will be removed in a future'
' release. Please drop it from the list of arguments.',
DeprecationWarning,
stacklevel=2,
)
return
if staging_bucket:
warnings.warn(
'WARNING: `staging_bucket` is deprecated and will be removed in a'
Expand Down Expand Up @@ -966,6 +939,7 @@ def to_agent_engine(
ignore=ignore_patterns,
dirs_exist_ok=True,
)
os.chdir(agent_src_path)
click.echo('Copying agent source code complete.')

project = _resolve_project(project)
Expand Down Expand Up @@ -1002,30 +976,13 @@ def to_agent_engine(
)
agent_config['description'] = description

requirements_txt_path = os.path.join(agent_src_path, 'requirements.txt')
if requirements_file:
if os.path.exists(requirements_txt_path):
click.echo(
f'Overwriting {requirements_txt_path} with {requirements_file}'
)
shutil.copyfile(requirements_file, requirements_txt_path)
elif 'requirements_file' in agent_config:
if os.path.exists(requirements_txt_path):
click.echo(
f'Overwriting {requirements_txt_path} with'
f' {agent_config["requirements_file"]}'
)
shutil.copyfile(agent_config['requirements_file'], requirements_txt_path)
else:
# Attempt to read requirements from requirements.txt in the dir (if any).
if not os.path.exists(requirements_txt_path):
click.echo(f'Creating {requirements_txt_path}...')
with open(requirements_txt_path, 'w', encoding='utf-8') as f:
f.write(_AGENT_ENGINE_REQUIREMENT + '\n')
click.echo(f'Created {requirements_txt_path}')
_ensure_agent_engine_dependency(requirements_txt_path)
agent_config['requirements_file'] = f'{temp_folder}/requirements.txt'

warnings.warn(
'WARNING: `--requirements_file` is deprecated and will be removed in the'
' future. Please drop it from the list of arguments.',
DeprecationWarning,
stacklevel=2,
)
env_vars = {}
if not env_file:
# Attempt to read the env variables from .env in the dir (if any).
Expand Down Expand Up @@ -1094,87 +1051,98 @@ def to_agent_engine(

from ..utils._google_client_headers import get_tracking_headers

http_options = {'headers': get_tracking_headers()}
if project and region:
click.echo('Initializing Vertex AI...')
click.echo('Initializing Client with project and region...')
client = vertexai.Client(
project=project,
location=region,
http_options={'headers': get_tracking_headers()},
http_options=http_options,
)
elif api_key:
click.echo('Initializing Vertex AI in Express Mode with API key...')
client = vertexai.Client(
api_key=api_key, http_options={'headers': get_tracking_headers()}
)
click.echo('Initializing Client with Express Mode API key...')
client = vertexai.Client(api_key=api_key, http_options=http_options)
else:
click.echo(
'No project/region or api_key provided. '
'Please specify either project/region or api_key.'
)
return
click.echo('Vertex AI initialized.')

is_config_agent = False
config_root_agent_file = os.path.join(agent_src_path, 'root_agent.yaml')
if os.path.exists(config_root_agent_file):
click.echo(f'Config agent detected: {config_root_agent_file}')
is_config_agent = True

# Validate that the agent module can be imported before deployment.
if not skip_agent_import_validation:
click.echo('Validating agent module...')
_validate_agent_import(agent_src_path, adk_app_object, is_config_agent)

adk_app_file = os.path.join(temp_folder, f'{adk_app}.py')
if adk_app_object == 'root_agent':
adk_app_type = 'agent'
elif adk_app_object == 'app':
adk_app_type = 'app'
else:
click.echo(
f'Invalid adk_app_object: {adk_app_object}. Please use "root_agent"'
' or "app".'

if skip_agent_import_validation:
warnings.warn(
'WARNING: `--skip-agent-import-validation` is deprecated and will be'
' removed in the future. Please drop it from the list of arguments.',
DeprecationWarning,
stacklevel=2,
)
return
with open(adk_app_file, 'w', encoding='utf-8') as f:
f.write(
_AGENT_ENGINE_APP_TEMPLATE.format(
app_name=app_name,
trace_to_cloud_option=trace_to_cloud,
is_config_agent=is_config_agent,
agent_folder=f'./{temp_folder}',
adk_app_object=adk_app_object,
adk_app_type=adk_app_type,
express_mode=api_key is not None,
)
click.echo('Creating Dockerfile...')
requirements_txt_path = os.path.join(agent_src_path, 'requirements.txt')
install_agent_deps = (
f'RUN pip install -r "/app/agents/{app_name}/requirements.txt"'
if os.path.exists(requirements_txt_path)
else '# No requirements.txt found.'
)
trigger_sources_option = (
f'--trigger_sources={trigger_sources}' if trigger_sources else ''
)
def create_dockerfile_for_agent_engine(resource_name: str):
agent_engine_uri = f'agentengine://{resource_name}'
dockerfile_content = _DOCKERFILE_TEMPLATE.format(
gcp_project_id=project,
gcp_region=region,
app_name=app_name,
port=8080,
command='api_server',
install_agent_deps=install_agent_deps,
service_option=_get_service_option_by_adk_version(
adk_version,
agent_engine_uri, # session_service_uri
artifact_service_uri,
agent_engine_uri, # memory_service_uri
False, # use_local_storage
),
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
otel_to_cloud_option='--otel_to_cloud' if otel_to_cloud else '',
allow_origins_option='', # Not supported for now.
adk_version=adk_version,
host_option='--host=0.0.0.0',
a2a_option='--a2a',
trigger_sources_option=trigger_sources_option,
gemini_enterprise_option=f'--gemini_enterprise_app_name={app_name}',
)
click.echo(f'Created {adk_app_file}')
click.echo('Files and dependencies resolved')
if absolutize_imports:
dockerfile_path = os.path.join(temp_folder, 'Dockerfile')
os.makedirs(temp_folder, exist_ok=True)
with open(dockerfile_path, 'w', encoding='utf-8') as f:
f.write(dockerfile_content)
click.echo(
'Agent Engine deployments have switched to source-based deployment, '
'so it is no longer necessary to absolutize imports.'
f'Creating Dockerfile complete. {os.path.abspath(dockerfile_path)}'
)

if absolutize_imports:
warnings.warn(
'WARNING: `--absolutize_imports` is deprecated and will be removed'
' in the future. Please drop it from the list of arguments.',
DeprecationWarning,
stacklevel=2,
)
click.echo('Deploying to agent engine...')
agent_config['entrypoint_module'] = f'{temp_folder}.{adk_app}'
agent_config['entrypoint_object'] = 'adk_app'
agent_config['source_packages'] = [temp_folder]
agent_config['source_packages'] = ['.']
agent_config['image_spec'] = {} # Use the Dockerfile
agent_config['class_methods'] = _AGENT_ENGINE_CLASS_METHODS
agent_config['agent_framework'] = 'google-adk'

if not agent_engine_id:
agent_engine = client.agent_engines.create(config=agent_config)
click.secho(
f'✅ Created agent engine: {agent_engine.api_resource.name}',
fg='green',
)
_print_agent_engine_url(agent_engine.api_resource.name)
else:
if project and region and not agent_engine_id.startswith('projects/'):
agent_engine_id = f'projects/{project}/locations/{region}/reasoningEngines/{agent_engine_id}'
client.agent_engines.update(name=agent_engine_id, config=agent_config)
click.secho(f'✅ Updated agent engine: {agent_engine_id}', fg='green')
agent_engine = client.agent_engines.create()
agent_engine_id = agent_engine.api_resource.name
click.secho(f'✅ Created agent engine: {agent_engine_id}', fg='green')
_print_agent_engine_url(agent_engine_id)
elif project and region and not agent_engine_id.startswith('projects/'):
agent_engine_id = f'projects/{project}/locations/{region}/reasoningEngines/{agent_engine_id}'
create_dockerfile_for_agent_engine(agent_engine_id)
client.agent_engines.update(name=agent_engine_id, config=agent_config)
click.secho(f'✅ Updated agent engine: {agent_engine_id}', fg='green')
_print_agent_engine_url(agent_engine_id)
finally:
click.echo(f'Cleaning up the temp folder: {temp_folder}')
shutil.rmtree(agent_src_path)
Expand Down
Loading
Loading