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
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Bug Fixes
* Sanitize Agent Engine temp folder names to avoid invalid module imports.
* Fix Agent Engine template imports for agents in an app/ subdirectory.

## [1.23.0](https://github.com/google/adk-python/compare/v1.22.1...v1.23.0) (2026-01-22)

### ⚠ BREAKING CHANGES
Expand Down Expand Up @@ -82,8 +88,6 @@
* Upgrade the sample BigQuery agent model version to `gemini-2.5-flash` ([fd2c0f5](https://github.com/google/adk-python/commit/fd2c0f556b786417a9f6add744827b07e7a06b7d))
* Import `migration_runner` lazily within the migrate command ([905604f](https://github.com/google/adk-python/commit/905604faac82aca8ae0935eebea288f82985e9c5))



## [1.22.1](https://github.com/google/adk-python/compare/v1.22.0...v1.22.1) (2026-01-09)

### Bug Fixes
Expand Down
28 changes: 26 additions & 2 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@
)


def _sanitize_temp_folder_name(folder_name: str) -> str:
if not folder_name:
return folder_name
sanitized = ''.join(
char if char.isalnum() or char == '_' else '_' for char in folder_name
)
if sanitized and sanitized[0].isdigit():
return '_' + sanitized
return sanitized
Comment on lines +36 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _sanitize_temp_folder_name function is a good addition to ensure that generated temporary folder names are valid Python module identifiers. This prevents import errors that could arise from special characters or leading digits in agent names.



def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None:
"""Ensures staged requirements include Agent Engine dependencies."""
if not os.path.exists(requirements_txt_path):
Expand Down Expand Up @@ -108,7 +119,7 @@ def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None:
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}
from {adk_app_import_module} import {adk_app_object}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Changing the import statement in the _AGENT_ENGINE_APP_TEMPLATE to use adk_app_import_module is crucial for supporting agents located in subdirectories. This makes the deployment process more flexible and robust.


if {express_mode}: # Whether or not to use Express Mode
vertexai.init(api_key=os.environ.get("GOOGLE_API_KEY"))
Expand Down Expand Up @@ -790,7 +801,8 @@ def to_agent_engine(
os.chdir(parent_folder)
did_change_cwd = True
tmp_app_name = app_name + '_tmp' + datetime.now().strftime('%Y%m%d_%H%M%S')
temp_folder = temp_folder or tmp_app_name
raw_temp_folder = temp_folder or tmp_app_name
temp_folder = _sanitize_temp_folder_name(raw_temp_folder)
Comment on lines +804 to +805
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Applying the _sanitize_temp_folder_name function to the temp_folder variable ensures that the directory created for staging files will always be a valid Python module name, preventing potential import issues during deployment.

agent_src_path = os.path.join(parent_folder, temp_folder)
click.echo(f'Staging all files in: {agent_src_path}')
# remove agent_src_path if it exists
Expand Down Expand Up @@ -954,6 +966,17 @@ def to_agent_engine(
is_config_agent = True

adk_app_file = os.path.join(temp_folder, f'{adk_app}.py')
app_subdir_agent = os.path.join(agent_src_path, 'app', 'agent.py')
app_subdir_init = os.path.join(agent_src_path, 'app', '__init__.py')
root_agent_file = os.path.join(agent_src_path, 'agent.py')
if (
os.path.exists(app_subdir_agent)
and os.path.exists(app_subdir_init)
and not os.path.exists(root_agent_file)
):
adk_app_import_module = '.app.agent'
else:
adk_app_import_module = '.agent'
Comment on lines +969 to +979
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic to determine adk_app_import_module based on the presence of app/agent.py and app/__init__.py is well-thought-out. This correctly handles the scenario where an agent is nested within an app/ subdirectory, ensuring the generated import path is accurate.

if adk_app_object == 'root_agent':
adk_app_type = 'agent'
elif adk_app_object == 'app':
Expand All @@ -973,6 +996,7 @@ def to_agent_engine(
agent_folder=f'./{temp_folder}',
adk_app_object=adk_app_object,
adk_app_type=adk_app_type,
adk_app_import_module=adk_app_import_module,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Passing the newly determined adk_app_import_module to the _AGENT_ENGINE_APP_TEMPLATE is the final step to ensure the generated adk_app.py file uses the correct import path for the agent.

express_mode=api_key is not None,
)
)
Expand Down
Loading