Skip to content

Commit 40cf97b

Browse files
boyangsvlcopybara-github
authored andcommitted
fix(cli): treat agent folder with subfolders as single agent in adk web
When `adk web` is given a path to a single agent directory (containing agent.py or root_agent.yaml), preserve single-agent mode even if that directory contains subfolders. Fixes #6434 Co-authored-by: Bo Yang <ybo@google.com> PiperOrigin-RevId: 953470819
1 parent 1ae292f commit 40cf97b

3 files changed

Lines changed: 42 additions & 44 deletions

File tree

src/google/adk/cli/utils/_nested_agent_loader.py

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ...agents.base_agent import BaseAgent
3131
from ...apps.app import App
3232
from .agent_loader import AgentLoader
33+
from .agent_loader import is_single_agent_directory
3334
from .agent_loader import SPECIAL_AGENTS_DIR
3435

3536
logger = logging.getLogger("google_adk." + __name__)
@@ -41,23 +42,7 @@ class NestedAgentLoader(AgentLoader):
4142
@staticmethod
4243
def _is_valid_agent_dir(path: Path) -> bool:
4344
"""Returns True if the directory is a valid agent directory."""
44-
if not path.is_dir():
45-
return False
46-
if (path / "agent.py").is_file():
47-
return True
48-
if (path / "root_agent.yaml").is_file():
49-
return True
50-
51-
init_py = path / "__init__.py"
52-
if init_py.is_file():
53-
try:
54-
content = init_py.read_text(encoding="utf-8")
55-
if "root_agent" in content:
56-
return True
57-
except Exception as e:
58-
logger.warning("Error reading %s: %s", init_py, e)
59-
60-
return False
45+
return is_single_agent_directory(path)
6146

6247
def _has_nested_agents(self, agents_path: Path) -> bool:
6348
"""Returns True if there are any nested agents within the directory (up to max depth)."""
@@ -84,30 +69,37 @@ def _has_nested_agents(self, agents_path: Path) -> bool:
8469

8570
@override
8671
def _init_agent_mode(self, agents_path: Path) -> None:
87-
if agents_path.is_file():
72+
try:
73+
is_file = agents_path.is_file()
74+
except Exception:
75+
is_file = False
76+
77+
if is_file:
8878
# Explicit file-based single-agent mode
8979
self._is_single_agent = True
9080
self._single_agent_name = agents_path.stem
9181
self.agents_dir = str(agents_path.parent)
82+
elif self._is_valid_agent_dir(agents_path):
83+
# Single-agent directory mode: treat as single agent even if it has subfolders.
84+
self._is_single_agent = True
85+
self._single_agent_name = agents_path.name
86+
self.agents_dir = str(agents_path.parent)
9287
else:
93-
# It is a directory. Check if it contains any nested agents.
94-
if self._has_nested_agents(agents_path):
95-
# Force multi-agent (nested) mode even if the root directory itself
96-
# contains an agent.py, to allow discovering the nested agents.
97-
self._is_single_agent = False
98-
self._single_agent_name = None
99-
self.agents_dir = str(agents_path)
100-
else:
101-
# Fall back to parent class behavior
102-
super()._init_agent_mode(agents_path)
88+
# It is a multi-agent directory containing sub-directories with agents.
89+
self._is_single_agent = False
90+
self._single_agent_name = None
91+
self.agents_dir = str(agents_path)
10392

10493
@override
10594
def list_agents(self) -> list[str]:
10695
"""Lists all agents recursively across subdirectories (sorted alphabetically)."""
10796
if self._is_single_agent:
10897
return [self._single_agent_name]
10998
base_path = Path(self.agents_dir)
110-
if not base_path.exists() or not base_path.is_dir():
99+
try:
100+
if not base_path.exists() or not base_path.is_dir():
101+
return []
102+
except Exception:
111103
return []
112104

113105
apps = []

src/google/adk/cli/utils/agent_loader.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@
4242

4343
def is_single_agent_directory(path: Path | str) -> bool:
4444
"""Returns True if the directory contains a single agent configuration or file."""
45-
p = Path(path).resolve()
46-
return (
47-
p.joinpath("agent.py").is_file()
48-
or p.joinpath("root_agent.yaml").is_file()
49-
)
45+
try:
46+
p = Path(path).resolve()
47+
if not p.is_dir():
48+
return False
49+
return (
50+
p.joinpath("agent.py").is_file()
51+
or p.joinpath("root_agent.yaml").is_file()
52+
)
53+
except Exception:
54+
return False
5055

5156

5257
# Special agents directory for agents with names starting with double underscore

tests/unittests/cli/utils/test_nested_agent_loader.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ def test_list_agents_finds_nested_agents_recursively(self):
5858
dir2.mkdir(parents=True)
5959
(dir2 / "root_agent.yaml").write_text("")
6060

61-
# Create sub_dir/sub_sub_dir/agent_three/__init__.py
61+
# Create sub_dir/sub_sub_dir/agent_three/agent.py
6262
dir3 = temp_path / "sub_dir" / "sub_sub_dir" / "agent_three"
6363
dir3.mkdir(parents=True)
64-
(dir3 / "__init__.py").write_text("root_agent = None")
64+
(dir3 / "agent.py").write_text("")
6565

6666
loader = NestedAgentLoader(str(temp_path))
6767
agents = loader.list_agents()
@@ -102,23 +102,24 @@ def test_list_agents_ignores_hidden_and_special_directories(self):
102102

103103
assert agents == ["valid_agent"]
104104

105-
def test_list_agents_excludes_root_directory_itself(self):
106-
"""Root app directory itself is not listed as a nested agent sub-app."""
105+
def test_single_agent_directory_with_subfolders_remains_single_agent(self):
106+
"""A directory with an agent.py at root remains in single-agent mode even with subfolders."""
107107
with tempfile.TemporaryDirectory() as temp_dir:
108-
temp_path = Path(temp_dir)
108+
temp_path = Path(temp_dir) / "my_single_agent"
109+
temp_path.mkdir()
109110

110-
# Create agent.py at the root itself
111+
# Create agent.py at the root of my_single_agent directory
111112
(temp_path / "agent.py").write_text("")
112113

113-
# Create a nested valid agent
114+
# Create a nested subfolder containing an agent
114115
dir_nested = temp_path / "sub_agent"
115116
dir_nested.mkdir()
116117
(dir_nested / "agent.py").write_text("")
117118

118119
loader = NestedAgentLoader(str(temp_path))
119-
agents = loader.list_agents()
120-
121-
assert agents == ["sub_agent"]
120+
assert loader.is_single_agent is True
121+
assert loader.single_agent_name == "my_single_agent"
122+
assert loader.list_agents() == ["my_single_agent"]
122123

123124
def test_list_agents_sorts_discovered_agents_alphabetically(self):
124125
"""Returned list of discovered agents is sorted alphabetically."""

0 commit comments

Comments
 (0)