Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5bccf8b

Browse files
Poggeccicopybara-github
authored andcommitted
feat: Refactor AgentLoader into base class and add InMemory impl alongside existing filesystem impl
PiperOrigin-RevId: 779616089
1 parent b1f4aeb commit 5bccf8b

File tree

9 files changed

+189
-51
lines changed

9 files changed

+189
-51
lines changed

src/google/adk/cli/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from ..sessions.in_memory_session_service import InMemorySessionService
3232
from ..sessions.session import Session
3333
from .utils import envs
34-
from .utils.agent_loader import AgentLoader
34+
from .utils.file_system_agent_loader import FileSystemAgentLoader
3535

3636

3737
class InputFile(BaseModel):
@@ -137,7 +137,7 @@ async def run_cli(
137137
session = await session_service.create_session(
138138
app_name=agent_folder_name, user_id=user_id
139139
)
140-
root_agent = AgentLoader(agents_dir=agent_parent_dir).load_agent(
140+
root_agent = FileSystemAgentLoader(agents_dir=agent_parent_dir).load_agent(
141141
agent_folder_name
142142
)
143143
envs.load_dotenv_for_agent(agent_folder_name, agent_parent_dir)

src/google/adk/cli/cli_eval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from typing import Optional
2626
import uuid
2727

28-
from ..agents import Agent
28+
from ..agents.llm_agent import Agent
2929
from ..artifacts.base_artifact_service import BaseArtifactService
3030
from ..evaluation.constants import MISSING_EVAL_DEPENDENCIES_MESSAGE
3131
from ..evaluation.eval_case import EvalCase

src/google/adk/cli/fast_api.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
from .utils import create_empty_state
8888
from .utils import envs
8989
from .utils import evals
90-
from .utils.agent_loader import AgentLoader
90+
from .utils.file_system_agent_loader import FileSystemAgentLoader
9191

9292
logger = logging.getLogger("google_adk." + __name__)
9393

@@ -98,7 +98,7 @@
9898

9999
class AgentChangeEventHandler(FileSystemEventHandler):
100100

101-
def __init__(self, agent_loader: AgentLoader):
101+
def __init__(self, agent_loader: FileSystemAgentLoader):
102102
self.agent_loader = agent_loader
103103

104104
def on_modified(self, event):
@@ -382,7 +382,7 @@ def _parse_agent_engine_resource_name(agent_engine_id_or_resource_name):
382382
credential_service = InMemoryCredentialService()
383383

384384
# initialize Agent Loader
385-
agent_loader = AgentLoader(agents_dir)
385+
agent_loader = FileSystemAgentLoader(agents_dir)
386386

387387
# Set up a file system watcher to detect changes in the agents directory.
388388
observer = Observer()
@@ -393,20 +393,7 @@ def _parse_agent_engine_resource_name(agent_engine_id_or_resource_name):
393393

394394
@app.get("/list-apps")
395395
def list_apps() -> list[str]:
396-
base_path = Path.cwd() / agents_dir
397-
if not base_path.exists():
398-
raise HTTPException(status_code=404, detail="Path not found")
399-
if not base_path.is_dir():
400-
raise HTTPException(status_code=400, detail="Not a directory")
401-
agent_names = [
402-
x
403-
for x in os.listdir(base_path)
404-
if os.path.isdir(os.path.join(base_path, x))
405-
and not x.startswith(".")
406-
and x != "__pycache__"
407-
]
408-
agent_names.sort()
409-
return agent_names
396+
return agent_loader.list_agents()
410397

411398
@app.get("/debug/trace/{event_id}")
412399
def get_trace_dict(event_id: str) -> Any:
@@ -511,13 +498,6 @@ async def create_session(
511498

512499
return session
513500

514-
def _get_eval_set_file_path(app_name, agents_dir, eval_set_id) -> str:
515-
return os.path.join(
516-
agents_dir,
517-
app_name,
518-
eval_set_id + _EVAL_SET_FILE_EXTENSION,
519-
)
520-
521501
@app.post(
522502
"/apps/{app_name}/eval_sets/{eval_set_id}",
523503
response_model_exclude_none=True,
@@ -1042,7 +1022,6 @@ async def _get_runner_async(app_name: str) -> Runner:
10421022
runner = runner_dict.pop(app_name, None)
10431023
await cleanup.close_runners(list([runner]))
10441024

1045-
envs.load_dotenv_for_agent(os.path.basename(app_name), agents_dir)
10461025
if app_name in runner_dict:
10471026
return runner_dict[app_name]
10481027
root_agent = agent_loader.load_agent(app_name)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Base class for agent loaders."""
16+
17+
from __future__ import annotations
18+
19+
from abc import ABC
20+
from abc import abstractmethod
21+
22+
from ...agents.base_agent import BaseAgent
23+
24+
25+
class BaseAgentLoader(ABC):
26+
"""Abstract base class for agent loaders."""
27+
28+
@abstractmethod
29+
def load_agent(self, agent_name: str) -> BaseAgent:
30+
"""Loads an instance of an agent with the given name."""
31+
raise NotImplementedError
32+
33+
@abstractmethod
34+
def list_agents(self) -> list[str]:
35+
"""Lists all agents available in the agent loader in alphabetical order."""
36+
raise NotImplementedError

src/google/adk/cli/utils/agent_loader.py renamed to src/google/adk/cli/utils/file_system_agent_loader.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,26 @@
1717
import importlib
1818
import logging
1919
import os
20+
from pathlib import Path
2021
import sys
2122
from typing import Optional
2223

24+
from google.adk.cli.utils import envs
2325
from pydantic import ValidationError
26+
from typing_extensions import override
2427

25-
from . import envs
2628
from ...agents import config_agent_utils
2729
from ...agents.base_agent import BaseAgent
2830
from ...utils.feature_decorator import working_in_progress
31+
from .base_agent_loader import BaseAgentLoader
2932

3033
logger = logging.getLogger("google_adk." + __name__)
3134

3235

33-
class AgentLoader:
36+
class FileSystemAgentLoader(BaseAgentLoader):
3437
"""Centralized agent loading with proper isolation, caching, and .env loading.
35-
Support loading agents from below folder/file structures:
38+
39+
Supports loading agents from below folder/file structures:
3640
a) {agent_name}.agent as a module name:
3741
agents_dir/{agent_name}/agent.py (with root_agent defined in the module)
3842
b) {agent_name} as a module name
@@ -41,7 +45,6 @@ class AgentLoader:
4145
agents_dir/{agent_name}/__init__.py (with root_agent in the package)
4246
d) {agent_name} as a YAML config folder:
4347
agents_dir/{agent_name}/root_agent.yaml defines the root agent
44-
4548
"""
4649

4750
def __init__(self, agents_dir: str):
@@ -188,6 +191,7 @@ def _perform_load(self, agent_name: str) -> BaseAgent:
188191
" exposed."
189192
)
190193

194+
@override
191195
def load_agent(self, agent_name: str) -> BaseAgent:
192196
"""Load an agent module (with caching & .env) and return its root_agent."""
193197
if agent_name in self._agent_cache:
@@ -199,6 +203,20 @@ def load_agent(self, agent_name: str) -> BaseAgent:
199203
self._agent_cache[agent_name] = agent
200204
return agent
201205

206+
@override
207+
def list_agents(self) -> list[str]:
208+
"""Lists all agents available in the agent loader (sorted alphabetically)."""
209+
base_path = Path.cwd() / self.agents_dir
210+
agent_names = [
211+
x
212+
for x in os.listdir(base_path)
213+
if os.path.isdir(os.path.join(base_path, x))
214+
and not x.startswith(".")
215+
and x != "__pycache__"
216+
]
217+
agent_names.sort()
218+
return agent_names
219+
202220
def remove_agent_from_cache(self, agent_name: str):
203221
# Clear module cache for the agent and its submodules
204222
keys_to_delete = [
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import logging
18+
from typing import Mapping
19+
20+
from typing_extensions import override
21+
22+
from ...agents.base_agent import BaseAgent
23+
from .base_agent_loader import BaseAgentLoader
24+
25+
logger = logging.getLogger("google_adk." + __name__)
26+
27+
28+
class InMemoryAgentLoader(BaseAgentLoader):
29+
"""Simple agent loader that loads agent from in-memory dict."""
30+
31+
def __init__(self, agents: Mapping[str, BaseAgent]):
32+
self._agent_cache: dict[str, BaseAgent] = {}
33+
for app_name, agent in agents.items():
34+
self._agent_cache[app_name] = agent
35+
logger.debug("Added %d agents into memory.", len(self._agent_cache))
36+
37+
@override
38+
def load_agent(self, agent_name: str) -> BaseAgent:
39+
"""Load an agent from in-memory dict."""
40+
return self._agent_cache[agent_name]
41+
42+
@override
43+
def list_agents(self) -> list[str]:
44+
"""List all agents available in the in-memory dict (sorted alphabetically)."""
45+
return list(self._agent_cache.keys())

tests/unittests/cli/test_fast_api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ def __init__(self, agents_dir: str):
188188
def load_agent(self, app_name):
189189
return root_agent
190190

191+
def list_agents(self):
192+
return ["test_app"]
193+
191194
return MockAgentLoader(".")
192195

193196

@@ -445,7 +448,7 @@ def test_app(
445448
return_value=mock_memory_service,
446449
),
447450
patch(
448-
"google.adk.cli.fast_api.AgentLoader",
451+
"google.adk.cli.fast_api.FileSystemAgentLoader",
449452
return_value=mock_agent_loader,
450453
),
451454
patch(
@@ -596,7 +599,7 @@ def test_app_with_a2a(
596599
return_value=mock_memory_service,
597600
),
598601
patch(
599-
"google.adk.cli.fast_api.AgentLoader",
602+
"google.adk.cli.fast_api.FileSystemAgentLoader",
600603
return_value=mock_agent_loader,
601604
),
602605
patch(

0 commit comments

Comments
 (0)