From ac958b8e468c67334d3068667dce9c74f24d5ed3 Mon Sep 17 00:00:00 2001 From: "Enkhbat.E" Date: Fri, 7 Aug 2026 21:44:33 +0800 Subject: [PATCH 1/4] fix: report root_agent type mismatch instead of 'No root_agent found' When a module defines root_agent with a non-agent type, AgentLoader now raises a targeted ValueError naming the module and the actual type (with a hint to use the name 'app' when the object is an App), instead of the misleading generic not-found error. Fixes google/adk-python#6606. --- src/google/adk/cli/utils/agent_loader.py | 33 +++++++++++++ .../unittests/cli/utils/test_agent_loader.py | 47 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index f38d9fbdae5..0196d7f0b75 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -79,6 +79,7 @@ def __init__(self, agents_dir: str): self._init_agent_mode(agents_path) self._original_sys_path = None self._agent_cache: dict[str, Union[BaseAgent, App]] = {} + self._root_agent_type_mismatches: list[tuple[str, str, bool]] = [] def _init_agent_mode(self, agents_path: Path) -> None: if is_single_agent_directory(agents_path): @@ -106,6 +107,15 @@ def _set_single_agent_mode(self, name: str, agents_dir: str) -> None: self._single_agent_name = name self.agents_dir = agents_dir + def _record_root_agent_type_mismatch(self, location: str, value: Any) -> None: + """Records a found `root_agent` whose type prevents loading it.""" + value_type = type(value) + self._root_agent_type_mismatches.append(( + location, + f"{value_type.__module__}.{value_type.__qualname__}", + isinstance(value, App), + )) + def _load_from_module_or_package( self, agent_name: str ) -> Optional[Union[BaseAgent, App]]: @@ -133,6 +143,9 @@ def _load_from_module_or_package( "Root agent found is not an instance of BaseAgent. But a type %s", type(module_candidate.root_agent), ) + self._record_root_agent_type_mismatch( + agent_name, module_candidate.root_agent + ) else: logger.debug( "Module %s has no root_agent. Trying next pattern.", @@ -275,6 +288,7 @@ def _validate_agent_name(self, agent_name: str) -> None: def _perform_load(self, agent_name: str) -> Union[BaseAgent, App]: """Internal logic to load an agent""" + self._root_agent_type_mismatches = [] self._validate_agent_name(agent_name) # Determine the directory to use for loading if agent_name.startswith("__"): @@ -342,6 +356,25 @@ def _perform_load(self, agent_name: str) -> Union[BaseAgent, App]: return root_agent # If no root_agent was found by any pattern + # A root_agent was found but had an unusable type: surface that + # diagnosis instead of the generic not-found guidance (#6606). + if self._root_agent_type_mismatches: + details = "\n".join( + f" - '{location}.root_agent' is of type '{type_name}'" + for location, type_name, _ in self._root_agent_type_mismatches + ) + app_hint = "" + if any(is_app for _, _, is_app in self._root_agent_type_mismatches): + app_hint = ( + "\n\nHINT: To serve an App, expose it under the name 'app'" + " instead of 'root_agent'." + ) + raise ValueError( + f"A 'root_agent' was found for '{agent_name}' but it is not a" + " BaseAgent (or workflow node) instance:\n" + f"{details}\n\nExpose a BaseAgent instance named" + f" 'root_agent'.{app_hint}" + ) # Check if user might be in the wrong directory hint = "" agents_path = Path(agents_dir) diff --git a/tests/unittests/cli/utils/test_agent_loader.py b/tests/unittests/cli/utils/test_agent_loader.py index 749f0ef8d7f..73dcb2df535 100644 --- a/tests/unittests/cli/utils/test_agent_loader.py +++ b/tests/unittests/cli/utils/test_agent_loader.py @@ -1061,6 +1061,53 @@ def test_validate_agent_name_allows_special_agents_when_enabled(self): # Should not raise any exception loader._validate_agent_name("__adk_agent_builder_assistant") + def test_wrong_type_root_agent_raises_targeted_error(self): + """A non-agent `root_agent` raises a type-mismatch error, not 'not found'.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + agent_file = temp_path / "mistyped_agent.py" + agent_file.write_text(dedent(""" + root_agent = "I am a string, not an agent" + """)) + + loader = AgentLoader(str(temp_path)) + + with pytest.raises(ValueError) as exc_info: + loader.load_agent("mistyped_agent") + + message = str(exc_info.value) + assert "mistyped_agent.root_agent" in message + assert "builtins.str" in message + assert "No root_agent found" not in message + + def test_app_exported_as_root_agent_suggests_app_name(self): + """Exporting an App as `root_agent` points the user at the 'app' name.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + agent_file = temp_path / "app_agent.py" + agent_file.write_text(dedent(""" + from google.adk.agents.base_agent import BaseAgent + from google.adk.apps.app import App + + + class MyAgent(BaseAgent): + + def __init__(self): + super().__init__(name="my_agent") + + + root_agent = App(name="app_agent", root_agent=MyAgent()) + """)) + + loader = AgentLoader(str(temp_path)) + + with pytest.raises(ValueError) as exc_info: + loader.load_agent("app_agent") + + message = str(exc_info.value) + assert "google.adk.apps.app.App" in message + assert "under the name 'app'" in message + class TestDetermineAgentLanguage: """Tests for AgentLoader._determine_agent_language covering all 4 load patterns.""" From dd19bea81a533860132a2648fcf61bbd34d07d97 Mon Sep 17 00:00:00 2001 From: "Enkhbat.E" Date: Fri, 7 Aug 2026 21:54:01 +0800 Subject: [PATCH 2/4] fix: cover the {agent}.agent path in root_agent type-mismatch error --- src/google/adk/cli/utils/agent_loader.py | 3 +++ .../unittests/cli/utils/test_agent_loader.py | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 0196d7f0b75..95115517de8 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -195,6 +195,9 @@ def _load_from_submodule( "Root agent found is not an instance of BaseAgent. But a type %s", type(module_candidate.root_agent), ) + self._record_root_agent_type_mismatch( + f"{agent_name}.agent", module_candidate.root_agent + ) else: logger.debug( "Module %s.agent has no root_agent.", diff --git a/tests/unittests/cli/utils/test_agent_loader.py b/tests/unittests/cli/utils/test_agent_loader.py index 73dcb2df535..5eec6bb46d2 100644 --- a/tests/unittests/cli/utils/test_agent_loader.py +++ b/tests/unittests/cli/utils/test_agent_loader.py @@ -1108,6 +1108,27 @@ def __init__(self): assert "google.adk.apps.app.App" in message assert "under the name 'app'" in message + def test_wrong_type_root_agent_in_agent_module_raises_targeted_error(self): + """A non-agent `root_agent` in {agent}/agent.py reports the submodule.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + agent_dir = temp_path / "mistyped_pkg" + agent_dir.mkdir() + (agent_dir / "__init__.py").write_text("") + (agent_dir / "agent.py").write_text(dedent(""" + root_agent = 42 + """)) + + loader = AgentLoader(str(temp_path)) + + with pytest.raises(ValueError) as exc_info: + loader.load_agent("mistyped_pkg") + + message = str(exc_info.value) + assert "mistyped_pkg.agent.root_agent" in message + assert "builtins.int" in message + assert "No root_agent found" not in message + class TestDetermineAgentLanguage: """Tests for AgentLoader._determine_agent_language covering all 4 load patterns.""" From bd8b1805f7dc4539fcc2dc883398e533e56f8e59 Mon Sep 17 00:00:00 2001 From: "Enkhbat.E" Date: Fri, 7 Aug 2026 21:59:49 +0800 Subject: [PATCH 3/4] test: guard root_agent fallthrough when package export is mistyped --- .../unittests/cli/utils/test_agent_loader.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unittests/cli/utils/test_agent_loader.py b/tests/unittests/cli/utils/test_agent_loader.py index 5eec6bb46d2..65ae7cd665d 100644 --- a/tests/unittests/cli/utils/test_agent_loader.py +++ b/tests/unittests/cli/utils/test_agent_loader.py @@ -1129,6 +1129,34 @@ def test_wrong_type_root_agent_in_agent_module_raises_targeted_error(self): assert "builtins.int" in message assert "No root_agent found" not in message + def test_valid_agent_module_wins_over_mistyped_package_root_agent(self): + """A bad `root_agent` in __init__.py must not block agent.py's valid one.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + agent_dir = temp_path / "fallthrough_agent" + agent_dir.mkdir() + (agent_dir / "__init__.py").write_text(dedent(""" + root_agent = "not an agent" + """)) + (agent_dir / "agent.py").write_text(dedent(""" + from google.adk.agents.base_agent import BaseAgent + + + class FallthroughAgent(BaseAgent): + + def __init__(self): + super().__init__(name="fallthrough_agent") + + + root_agent = FallthroughAgent() + """)) + + loader = AgentLoader(str(temp_path)) + + agent = loader.load_agent("fallthrough_agent") + + assert agent.name == "fallthrough_agent" + class TestDetermineAgentLanguage: """Tests for AgentLoader._determine_agent_language covering all 4 load patterns.""" From 9a198440ead2b8d11dcda0b26dfd894e3ea2bab9 Mon Sep 17 00:00:00 2001 From: "Enkhbat.E" Date: Fri, 7 Aug 2026 22:08:57 +0800 Subject: [PATCH 4/4] chore: keep the not-found comment attached to the not-found handling --- src/google/adk/cli/utils/agent_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 95115517de8..6a2ad9aea33 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -358,7 +358,6 @@ def _perform_load(self, agent_name: str) -> Union[BaseAgent, App]: ) return root_agent - # If no root_agent was found by any pattern # A root_agent was found but had an unusable type: surface that # diagnosis instead of the generic not-found guidance (#6606). if self._root_agent_type_mismatches: @@ -378,6 +377,7 @@ def _perform_load(self, agent_name: str) -> Union[BaseAgent, App]: f"{details}\n\nExpose a BaseAgent instance named" f" 'root_agent'.{app_hint}" ) + # If no root_agent was found by any pattern # Check if user might be in the wrong directory hint = "" agents_path = Path(agents_dir)