From 0b73a6937bd84a41f79a9ada3fc782dca1d6fb11 Mon Sep 17 00:00:00 2001 From: ejfn <148174+ejfn@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:10:50 -0700 Subject: [PATCH] fix: Handle App objects in eval and graph endpoints Merge https://github.com/google/adk-python/pull/3060 ## Description Fixes #3059 This PR fixes two endpoints in `adk web` that fail when using App objects instead of bare agents. ## Changes - **Eval execution endpoint** (line ~969): Extract root_agent from App objects before passing to LocalEvalService - **Graph visualization endpoint** (line ~1308): Extract root_agent from App objects before graph operations Both endpoints now properly handle both BaseAgent and App objects by checking the type and extracting `.root_agent` when needed. ## Testing Plan ### Manual E2E Testing with ADK Web Tested with an App object that includes context caching: ```python from google.adk.apps import App from google.adk.agents import LlmAgent root_agent = LlmAgent(name="MyAgent", model="gemini-1.5-pro-002") app = App( name="my_agent", root_agent=root_agent, context_cache_config=ContextCacheConfig(...) ) ``` **Before fix:** - Graph visualization failed (tried to call agent methods on App object) - Eval execution failed (LocalEvalService received App instead of agent) **After fix:** - Graph visualization works correctly - Eval execution works correctly - Both endpoints properly extract root_agent from App objects ## Checklist - [x] Code follows project style (autoformat.sh passed) - [x] Changes are focused and minimal - [x] Issue #3059 created and referenced - [x] Manual E2E testing completed COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3060 from ejfn:ejfn/bugfix-app-object-endpoints 01c30191bfd9487a8c8463ccf24b297cb9a4ce37 PiperOrigin-RevId: 821746910 --- src/google/adk/cli/adk_web_server.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 5ae83b8e..dc111ae7 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -486,6 +486,12 @@ class AdkWebServer: 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 @@ class AdkWebServer: # 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, @@ -1096,7 +1101,8 @@ class AdkWebServer: 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) + root_agent = self._get_root_agent(agent_or_app) eval_case_results = [] @@ -1437,13 +1443,7 @@ class AdkWebServer: 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 = []