From 3b5937f022adf9286dc41e01e3618071a23eb992 Mon Sep 17 00:00:00 2001 From: Mark Nawar Date: Wed, 4 Mar 2026 14:40:39 -0800 Subject: [PATCH] fix: filter non-agent directoris from list_agents() Merge https://github.com/google/adk-python/pull/4648 **Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.** ### Link to Issue or Description of Change **1. Link to an existing issue (if applicable):** - Closes: #4647 - Related: #3429, #3430 **2. Or, if no issue exists, describe the change:** **Problem:** `AgentLoader.list_agents()` returns every non-hidden subdirectory in the agents directory, regardless of whether it contains a valid agent definition. This causes non-agent directories (e.g. `tmp/`, `data/`, `utils/`) to appear in the `/list-apps` API response. This affects both the ADK web UI agent selector and any production deployment depending on this API. **Solution:** Reuse the existing `_determine_agent_language()` method inside `list_agents()` to verify each candidate directory contains at least one recognized agent file (`root_agent.yaml`, `agent.py`, or `__init__.py`). Directories that fail this check are excluded from the result. This avoids introducing any new methods or abstractions and keeps the check lightweight (filesystem only, no agent imports). ### Testing Plan **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. 27 passed in 2.85s: pytest tests/unittests/cli/utils/test_agent_loader.py -v ======================= 27 passed, 14 warnings in 2.85s ======================== Added `test_list_agents_excludes_non_agent_directories` which creates a temp directory with three valid agent types (package with `__init__.py`, module with `agent.py`, YAML with `root_agent.yaml`) and three non-agent directories, and asserts only the valid agents are listed. **Screenshots / Video:** | Before (non-agent directories listed) | After (only valid agents listed) | |----------------------------------------|----------------------------------| |Image|Image| **Manual End-to-End (E2E) Tests:** 1. Create a project directory containing both valid agent subdirectories and non-agent subdirectories 2. Run `adk web .` 3. Open the web UI and verify only valid agents appear in the agent selector 4. See screenshots below for before/after comparison ### Checklist - [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document. - [x] I have performed a self-review of my own code. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] New and existing unit tests pass locally with my changes. - [x] I have manually tested my changes end-to-end. - [ ] Any dependent changes have been merged and published in downstream modules. ### Additional context COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4648 from markadelnawar:fix/list-agents-filter-non-agents-dirs 041895610fa0c52f2bf3cf7ba0d072a5c580c1b6 PiperOrigin-RevId: 878674609 --- src/google/adk/cli/utils/agent_loader.py | 19 +++++--- .../unittests/cli/utils/test_agent_loader.py | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 8b5805c5..efd24648 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -335,13 +335,18 @@ class AgentLoader(BaseAgentLoader): def list_agents(self) -> list[str]: """Lists all agents available in the agent loader (sorted alphabetically).""" base_path = Path.cwd() / self.agents_dir - agent_names = [ - x - for x in os.listdir(base_path) - if os.path.isdir(os.path.join(base_path, x)) - and not x.startswith(".") - and x != "__pycache__" - ] + agent_names = [] + for x in os.listdir(base_path): + if ( + os.path.isdir(os.path.join(base_path, x)) + and not x.startswith(".") + and x != "__pycache__" + ): + try: + self._determine_agent_language(x) + agent_names.append(x) + except ValueError: + continue agent_names.sort() return agent_names diff --git a/tests/unittests/cli/utils/test_agent_loader.py b/tests/unittests/cli/utils/test_agent_loader.py index f3eb3396..0a7f9fc0 100644 --- a/tests/unittests/cli/utils/test_agent_loader.py +++ b/tests/unittests/cli/utils/test_agent_loader.py @@ -993,3 +993,48 @@ class TestAgentLoader: assert len(detailed_list) == 1 assert detailed_list[0]["name"] == agent_name assert not detailed_list[0]["is_computer_use"] + + def test_list_agents_excludes_non_agent_directories(self): + """Test that list_agents filters out directories without agent definitions.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + valid_package = temp_path / "valid_agent" + valid_package.mkdir() + (valid_package / "__init__.py").write_text(dedent(""" + from google.adk.agents.base_agent import BaseAgent + + class ValidAgent(BaseAgent): + def __init__(self): + super().__init__(name="valid_agent") + + root_agent = ValidAgent() + """)) + + valid_module = temp_path / "module_agent" + valid_module.mkdir() + (valid_module / "agent.py").write_text(dedent(""" + from google.adk.agents.base_agent import BaseAgent + + class ModuleAgent(BaseAgent): + def __init__(self): + super().__init__(name="module_agent") + + root_agent = ModuleAgent() + """)) + + valid_yaml = temp_path / "yaml_agent" + valid_yaml.mkdir() + (valid_yaml / "root_agent.yaml").write_text("name: yaml_agent\n") + + (temp_path / "random_folder").mkdir() + (temp_path / "data").mkdir() + (temp_path / "tmp").mkdir() + + loader = AgentLoader(str(temp_path)) + agents = loader.list_agents() + + assert agents == ["module_agent", "valid_agent", "yaml_agent"] + assert "random_folder" not in agents + assert "data" not in agents + assert "tmp" not in agents