From de320555cfad8b2d9ee53fd9789ae1dbc918e936 Mon Sep 17 00:00:00 2001 From: ejfn <148174+ejfn@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:58:20 +0930 Subject: [PATCH 1/2] fix: Handle App objects in eval and graph endpoints Fixes #3059 - Extract root_agent from App objects in eval execution endpoint - Extract root_agent from App objects in graph visualization endpoint - Both endpoints now handle BaseAgent and App objects correctly --- src/google/adk/cli/adk_web_server.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 5ae83b8ea1..d2559bcc1a 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -1096,7 +1096,14 @@ async def run_eval( status_code=400, detail=f"Eval set `{eval_set_id}` not found." ) - root_agent = self.agent_loader.load_agent(app_name) + agent_or_app = self.agent_loader.load_agent(app_name) + # The loader may return an App; unwrap to its root agent so the graph builder + # receives a BaseAgent instance. + root_agent = ( + agent_or_app.root_agent + if isinstance(agent_or_app, App) + else agent_or_app + ) eval_case_results = [] From 9d49872d6fa863c1a603ae2cc23553de6f248eb0 Mon Sep 17 00:00:00 2001 From: ejfn <148174+ejfn@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:05:41 +0930 Subject: [PATCH 2/2] refactor: Extract _get_root_agent helper method Address code review feedback by refactoring duplicated App object handling logic into a reusable private helper method. - Added _get_root_agent() helper method - Refactored 3 occurrences to use the helper - Improves maintainability and reduces duplication --- src/google/adk/cli/adk_web_server.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index d2559bcc1a..dc111ae7d7 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -486,6 +486,12 @@ async def get_runner_async(self, app_name: str) -> Runner: self.runner_dict[app_name] = runner return runner + def _get_root_agent(self, agent_or_app: BaseAgent | App) -> BaseAgent: + """Extract root agent from either a BaseAgent or App object.""" + if isinstance(agent_or_app, App): + return agent_or_app.root_agent + return agent_or_app + def _create_runner(self, agentic_app: App) -> Runner: """Create a runner with common services.""" return Runner( @@ -933,9 +939,8 @@ async def add_session_to_eval_set( # Populate the session with initial session state. agent_or_app = self.agent_loader.load_agent(app_name) - if isinstance(agent_or_app, App): - agent_or_app = agent_or_app.root_agent - initial_session_state = create_empty_state(agent_or_app) + root_agent = self._get_root_agent(agent_or_app) + initial_session_state = create_empty_state(root_agent) new_eval_case = EvalCase( eval_id=req.eval_id, @@ -1097,13 +1102,7 @@ async def run_eval( ) agent_or_app = self.agent_loader.load_agent(app_name) - # The loader may return an App; unwrap to its root agent so the graph builder - # receives a BaseAgent instance. - root_agent = ( - agent_or_app.root_agent - if isinstance(agent_or_app, App) - else agent_or_app - ) + root_agent = self._get_root_agent(agent_or_app) eval_case_results = [] @@ -1444,13 +1443,7 @@ async def get_event_graph( function_calls = event.get_function_calls() function_responses = event.get_function_responses() agent_or_app = self.agent_loader.load_agent(app_name) - # The loader may return an App; unwrap to its root agent so the graph builder - # receives a BaseAgent instance. - root_agent = ( - agent_or_app.root_agent - if isinstance(agent_or_app, App) - else agent_or_app - ) + root_agent = self._get_root_agent(agent_or_app) dot_graph = None if function_calls: function_call_highlights = []