From 139e4888ed4211fd2a4825ce115cf936b6fbb037 Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:52:44 -0600 Subject: [PATCH 01/11] feat: add list-apps-detailed endpoint --- src/google/adk/cli/adk_web_server.py | 16 ++++++ src/google/adk/cli/utils/agent_loader.py | 53 +++++++++++++++++++ src/google/adk/cli/utils/base_agent_loader.py | 6 +++ tests/unittests/cli/test_fast_api.py | 28 ++++++++++ 4 files changed, 103 insertions(+) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 1b422fe335..6777cc65c2 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -280,6 +280,17 @@ class ListMetricsInfoResponse(common.BaseModel): metrics_info: list[MetricInfo] +class AppInfo(common.BaseModel): + name: str + display_name: str + description: str + agent_type: Literal["yaml", "python", "package"] + + +class ListAppsResponse(common.BaseModel): + apps: list[AppInfo] + + def _setup_telemetry( otel_to_cloud: bool = False, internal_exporters: Optional[list[SpanProcessor]] = None, @@ -702,6 +713,11 @@ async def internal_lifespan(app: FastAPI): async def list_apps() -> list[str]: return self.agent_loader.list_agents() + @app.get("/list-apps-detailed") + async def list_apps_detailed() -> ListAppsResponse: + apps_info = self.agent_loader.list_agents_detailed() + return ListAppsResponse(apps=[AppInfo(**app) for app in apps_info]) + @app.get("/debug/trace/{event_id}", tags=[TAG_DEBUG]) async def get_trace_dict(event_id: str) -> Any: event_dict = trace_dict.get(event_id, None) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 0755c9147c..4db095f41e 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -20,6 +20,8 @@ import os from pathlib import Path import sys +from typing import Any +from typing import Literal from typing import Optional from typing import Union @@ -321,6 +323,57 @@ def list_agents(self) -> list[str]: agent_names.sort() return agent_names + def list_agents_detailed(self) -> list[dict[str, Any]]: + """Lists all agents with detailed metadata (name, description, type).""" + agent_names = self.list_agents() + apps_info = [] + + for agent_name in agent_names: + try: + loaded = self.load_agent(agent_name) + if isinstance(loaded, App): + agent = loaded.root_agent + else: + agent = loaded + + agent_type = self._determine_agent_type(agent_name) + + # Convert underscores to spaces for display_name + agent.name = agent.name.replace("_", " ") + + app_info = { + "name": agent_name, + "display_name": agent.name, + "description": agent.description, + "agent_type": agent_type, + } + apps_info.append(app_info) + + except Exception as e: + logger.error("Failed to load agent '%s': %s", agent_name, e) + continue + + return apps_info + + def _determine_agent_type( + self, agent_name: str + ) -> Literal["yaml", "python", "package"]: + """Determine the type of agent based on file structure.""" + base_path = Path.cwd() / self.agents_dir / agent_name + + if (base_path / "root_agent.yaml").exists(): + return "yaml" + elif (base_path / "agent.py").exists(): + return "package" + elif (base_path / "__init__.py").exists(): + return "package" + else: + module_path = Path.cwd() / self.agents_dir / f"{agent_name}.py" + if module_path.exists(): + return "python" + + return "package" + def remove_agent_from_cache(self, agent_name: str): # Clear module cache for the agent and its submodules keys_to_delete = [ diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index d62a6b8651..d27fdea0f4 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -18,10 +18,12 @@ from abc import ABC from abc import abstractmethod +from typing import Any from typing import Union from ...agents.base_agent import BaseAgent from ...apps.app import App +from ..adk_web_server import ListAppsResponse class BaseAgentLoader(ABC): @@ -34,3 +36,7 @@ def load_agent(self, agent_name: str) -> Union[BaseAgent, App]: @abstractmethod def list_agents(self) -> list[str]: """Lists all agents available in the agent loader in alphabetical order.""" + + @abstractmethod + def list_agents_detailed(self) -> ListAppsResponse: + """Lists all agents with detailed metadata (name, display name, description, type).""" diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index f4a84179be..cfa0ef3ffd 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -190,6 +190,14 @@ def load_agent(self, app_name): def list_agents(self): return ["test_app"] + def list_agents_detailed(self): + return [{ + "name": "test_app", + "display_name": "test_agent", + "description": "A test agent for unit testing", + "agent_type": "python", + }] + return MockAgentLoader(".") @@ -550,6 +558,26 @@ def test_list_apps(test_app): logger.info(f"Listed apps: {data}") +def test_list_apps_detailed(test_app): + """Test listing available applications with detailed metadata.""" + response = test_app.get("/list-apps-detailed") + + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "apps" in data + assert isinstance(data["apps"], list) + + for app in data["apps"]: + assert "name" in app + assert "displayName" in app + assert "description" in app + assert "agentType" in app + assert app["agentType"] in ["yaml", "python", "package"] + + logger.info(f"Listed apps: {data}") + + def test_create_session_with_id(test_app, test_session_info): """Test creating a session with a specific ID.""" new_session_id = "new_session_id" From 03e149afb4b6c0e5b245cd43c2f5d8d9b15570a9 Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:09:05 -0600 Subject: [PATCH 02/11] circular dependency --- src/google/adk/cli/utils/base_agent_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index d27fdea0f4..bf2540efe3 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -23,7 +23,7 @@ from ...agents.base_agent import BaseAgent from ...apps.app import App -from ..adk_web_server import ListAppsResponse + class BaseAgentLoader(ABC): @@ -38,5 +38,5 @@ def list_agents(self) -> list[str]: """Lists all agents available in the agent loader in alphabetical order.""" @abstractmethod - def list_agents_detailed(self) -> ListAppsResponse: + def list_agents_detailed(self) -> list[dict[str, str, str, str]]: """Lists all agents with detailed metadata (name, display name, description, type).""" From ba8a026978b96a8b3d6cca1504d404044be36829 Mon Sep 17 00:00:00 2001 From: Dylan <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:14:07 -0600 Subject: [PATCH 03/11] Update src/google/adk/cli/utils/agent_loader.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/cli/utils/agent_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 4db095f41e..401ea79342 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -339,11 +339,11 @@ def list_agents_detailed(self) -> list[dict[str, Any]]: agent_type = self._determine_agent_type(agent_name) # Convert underscores to spaces for display_name - agent.name = agent.name.replace("_", " ") + display_name = agent.name.replace("_", " ") app_info = { "name": agent_name, - "display_name": agent.name, + "display_name": display_name, "description": agent.description, "agent_type": agent_type, } From d3ffd5a2057698421352c6f3d2943bae32d65017 Mon Sep 17 00:00:00 2001 From: Dylan <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:14:23 -0600 Subject: [PATCH 04/11] Update src/google/adk/cli/utils/base_agent_loader.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/cli/utils/base_agent_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index bf2540efe3..e26e8c4a92 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -38,5 +38,5 @@ def list_agents(self) -> list[str]: """Lists all agents available in the agent loader in alphabetical order.""" @abstractmethod - def list_agents_detailed(self) -> list[dict[str, str, str, str]]: + def list_agents_detailed(self) -> list[dict[str, Any]]: """Lists all agents with detailed metadata (name, display name, description, type).""" From 57b008479d76806260c77dba9c195711adc9e059 Mon Sep 17 00:00:00 2001 From: Dylan <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:14:57 -0600 Subject: [PATCH 05/11] Update src/google/adk/cli/utils/agent_loader.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- 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 401ea79342..1027d6989f 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -372,7 +372,7 @@ def _determine_agent_type( if module_path.exists(): return "python" - return "package" + raise ValueError(f"Could not determine agent type for '{agent_name}'.") def remove_agent_from_cache(self, agent_name: str): # Clear module cache for the agent and its submodules From 58ffe0962080a6cb84b3d52252b34ec36f83daea Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:29:12 -0600 Subject: [PATCH 06/11] remove python as agentType. change display_name to agent_name. --- src/google/adk/cli/adk_web_server.py | 18 +++++++++--------- src/google/adk/cli/utils/agent_loader.py | 13 +++---------- tests/unittests/cli/test_fast_api.py | 14 +++++++------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 6777cc65c2..d12568c85f 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -281,10 +281,10 @@ class ListMetricsInfoResponse(common.BaseModel): class AppInfo(common.BaseModel): - name: str - display_name: str + simple_name: str + agent_name: str description: str - agent_type: Literal["yaml", "python", "package"] + agent_type: Literal["yaml", "package"] class ListAppsResponse(common.BaseModel): @@ -710,14 +710,14 @@ async def internal_lifespan(app: FastAPI): ) @app.get("/list-apps") - async def list_apps() -> list[str]: + async def list_apps( + detailed: bool = Query(default=False, description="Return detailed app information") + ) -> list[str] | ListAppsResponse: + if detailed: + apps_info = self.agent_loader.list_agents_detailed() + return ListAppsResponse(apps=[AppInfo(**app) for app in apps_info]) return self.agent_loader.list_agents() - @app.get("/list-apps-detailed") - async def list_apps_detailed() -> ListAppsResponse: - apps_info = self.agent_loader.list_agents_detailed() - return ListAppsResponse(apps=[AppInfo(**app) for app in apps_info]) - @app.get("/debug/trace/{event_id}", tags=[TAG_DEBUG]) async def get_trace_dict(event_id: str) -> Any: event_dict = trace_dict.get(event_id, None) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 1027d6989f..cbef984c3b 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -338,12 +338,9 @@ def list_agents_detailed(self) -> list[dict[str, Any]]: agent_type = self._determine_agent_type(agent_name) - # Convert underscores to spaces for display_name - display_name = agent.name.replace("_", " ") - app_info = { - "name": agent_name, - "display_name": display_name, + "simple_name": agent_name, + "agent_name": agent.name, "description": agent.description, "agent_type": agent_type, } @@ -357,7 +354,7 @@ def list_agents_detailed(self) -> list[dict[str, Any]]: def _determine_agent_type( self, agent_name: str - ) -> Literal["yaml", "python", "package"]: + ) -> Literal["yaml", "package"]: """Determine the type of agent based on file structure.""" base_path = Path.cwd() / self.agents_dir / agent_name @@ -367,10 +364,6 @@ def _determine_agent_type( return "package" elif (base_path / "__init__.py").exists(): return "package" - else: - module_path = Path.cwd() / self.agents_dir / f"{agent_name}.py" - if module_path.exists(): - return "python" raise ValueError(f"Could not determine agent type for '{agent_name}'.") diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index ffbd93c970..c5c84e2df7 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -192,10 +192,10 @@ def list_agents(self): def list_agents_detailed(self): return [{ - "name": "test_app", - "display_name": "test_agent", + "simple_name": "test_app", + "agent_name": "test_agent", "description": "A test agent for unit testing", - "agent_type": "python", + "agent_type": "package", }] return MockAgentLoader(".") @@ -560,7 +560,7 @@ def test_list_apps(test_app): def test_list_apps_detailed(test_app): """Test listing available applications with detailed metadata.""" - response = test_app.get("/list-apps-detailed") + response = test_app.get("/list-apps?detailed=true") assert response.status_code == 200 data = response.json() @@ -569,11 +569,11 @@ def test_list_apps_detailed(test_app): assert isinstance(data["apps"], list) for app in data["apps"]: - assert "name" in app - assert "displayName" in app + assert "simpleName" in app + assert "agentName" in app assert "description" in app assert "agentType" in app - assert app["agentType"] in ["yaml", "python", "package"] + assert app["agentType"] in ["yaml", "package"] logger.info(f"Listed apps: {data}") From 7eec0f8342c7fe17ec389a326cd45a7c01f21584 Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:30:08 -0600 Subject: [PATCH 07/11] run autoformat.sh --- contributing/samples/gepa/experiment.py | 1 - contributing/samples/gepa/run_experiment.py | 1 - src/google/adk/cli/adk_web_server.py | 4 +++- src/google/adk/cli/utils/base_agent_loader.py | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contributing/samples/gepa/experiment.py b/contributing/samples/gepa/experiment.py index 2f5d03a772..f68b349d9c 100644 --- a/contributing/samples/gepa/experiment.py +++ b/contributing/samples/gepa/experiment.py @@ -43,7 +43,6 @@ from tau_bench.types import EnvRunResult from tau_bench.types import RunConfig import tau_bench_agent as tau_bench_agent_lib - import utils diff --git a/contributing/samples/gepa/run_experiment.py b/contributing/samples/gepa/run_experiment.py index cfd850b3a3..1bc4ee58c8 100644 --- a/contributing/samples/gepa/run_experiment.py +++ b/contributing/samples/gepa/run_experiment.py @@ -25,7 +25,6 @@ from absl import flags import experiment from google.genai import types - import utils _OUTPUT_DIR = flags.DEFINE_string( diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index d12568c85f..63b4f9544e 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -711,7 +711,9 @@ async def internal_lifespan(app: FastAPI): @app.get("/list-apps") async def list_apps( - detailed: bool = Query(default=False, description="Return detailed app information") + detailed: bool = Query( + default=False, description="Return detailed app information" + ) ) -> list[str] | ListAppsResponse: if detailed: apps_info = self.agent_loader.list_agents_detailed() diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index e26e8c4a92..e6d2e84af2 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -25,7 +25,6 @@ from ...apps.app import App - class BaseAgentLoader(ABC): """Abstract base class for agent loaders.""" From 1bee9a1e91ef3221f491f0b250d91b570134eee4 Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:31:32 -0600 Subject: [PATCH 08/11] make recommended changes --- src/google/adk/cli/adk_web_server.py | 6 +++--- src/google/adk/cli/utils/agent_loader.py | 16 ++++++++-------- tests/unittests/cli/test_fast_api.py | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 63b4f9544e..45747a52a1 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -281,10 +281,10 @@ class ListMetricsInfoResponse(common.BaseModel): class AppInfo(common.BaseModel): - simple_name: str - agent_name: str + name: str + root_agent_name: str description: str - agent_type: Literal["yaml", "package"] + language: Literal["yaml", "python"] class ListAppsResponse(common.BaseModel): diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index cbef984c3b..6a32ff93ee 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -336,13 +336,13 @@ def list_agents_detailed(self) -> list[dict[str, Any]]: else: agent = loaded - agent_type = self._determine_agent_type(agent_name) + language = self._determine_agent_language(agent_name) app_info = { - "simple_name": agent_name, - "agent_name": agent.name, + "name": agent_name, + "root_agent_name": agent.name, "description": agent.description, - "agent_type": agent_type, + "language": language, } apps_info.append(app_info) @@ -352,18 +352,18 @@ def list_agents_detailed(self) -> list[dict[str, Any]]: return apps_info - def _determine_agent_type( + def _determine_agent_language( self, agent_name: str - ) -> Literal["yaml", "package"]: + ) -> Literal["yaml", "python"]: """Determine the type of agent based on file structure.""" base_path = Path.cwd() / self.agents_dir / agent_name if (base_path / "root_agent.yaml").exists(): return "yaml" elif (base_path / "agent.py").exists(): - return "package" + return "python" elif (base_path / "__init__.py").exists(): - return "package" + return "python" raise ValueError(f"Could not determine agent type for '{agent_name}'.") diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index c5c84e2df7..3bde93c7dc 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -192,10 +192,10 @@ def list_agents(self): def list_agents_detailed(self): return [{ - "simple_name": "test_app", - "agent_name": "test_agent", + "name": "test_app", + "root_agent_name": "test_agent", "description": "A test agent for unit testing", - "agent_type": "package", + "language": "python", }] return MockAgentLoader(".") @@ -569,11 +569,11 @@ def test_list_apps_detailed(test_app): assert isinstance(data["apps"], list) for app in data["apps"]: - assert "simpleName" in app - assert "agentName" in app + assert "name" in app + assert "rootAgentName" in app assert "description" in app - assert "agentType" in app - assert app["agentType"] in ["yaml", "package"] + assert "language" in app + assert app["language"] in ["yaml", "python"] logger.info(f"Listed apps: {data}") From 0e7e1372dd86563e6228c771f5a5b0a7b0bcb701 Mon Sep 17 00:00:00 2001 From: Hangfei Lin Date: Fri, 14 Nov 2025 20:23:40 -0800 Subject: [PATCH 09/11] Add import statement for utils module --- contributing/samples/gepa/experiment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/contributing/samples/gepa/experiment.py b/contributing/samples/gepa/experiment.py index f68b349d9c..2f5d03a772 100644 --- a/contributing/samples/gepa/experiment.py +++ b/contributing/samples/gepa/experiment.py @@ -43,6 +43,7 @@ from tau_bench.types import EnvRunResult from tau_bench.types import RunConfig import tau_bench_agent as tau_bench_agent_lib + import utils From 28ff984ced735b02ca281a28f78ad9ad920ba77b Mon Sep 17 00:00:00 2001 From: Hangfei Lin Date: Fri, 14 Nov 2025 20:23:53 -0800 Subject: [PATCH 10/11] Update run_experiment.py --- contributing/samples/gepa/run_experiment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/contributing/samples/gepa/run_experiment.py b/contributing/samples/gepa/run_experiment.py index 1bc4ee58c8..cfd850b3a3 100644 --- a/contributing/samples/gepa/run_experiment.py +++ b/contributing/samples/gepa/run_experiment.py @@ -25,6 +25,7 @@ from absl import flags import experiment from google.genai import types + import utils _OUTPUT_DIR = flags.DEFINE_string( From e6864fd61a673da5fd2fb28d2d7d72cb90f5af0a Mon Sep 17 00:00:00 2001 From: Dylan Snyder <114695692+dylan-apex@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:59:50 -0600 Subject: [PATCH 11/11] add default implementation for detailed list agent --- src/google/adk/cli/utils/base_agent_loader.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index e6d2e84af2..bcef0dae42 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -36,6 +36,14 @@ def load_agent(self, agent_name: str) -> Union[BaseAgent, App]: def list_agents(self) -> list[str]: """Lists all agents available in the agent loader in alphabetical order.""" - @abstractmethod def list_agents_detailed(self) -> list[dict[str, Any]]: - """Lists all agents with detailed metadata (name, display name, description, type).""" + agent_names = self.list_agents() + return [ + { + 'name': name, + 'display_name': None, + 'description': None, + 'type': None, + } + for name in agent_names + ]