17
17
import importlib
18
18
import logging
19
19
import sys
20
+ from typing import Optional
20
21
21
22
from . import envs
22
23
from ...agents .base_agent import BaseAgent
27
28
class AgentLoader :
28
29
"""Centralized agent loading with proper isolation, caching, and .env loading.
29
30
Support loading agents from below folder/file structures:
30
- a) agents_dir/agent_name.py (with root_agent or agent.root_agent in it)
31
- b) agents_dir/agent_name_folder/__init__.py (with root_agent or agent.root_agent in the package)
32
- c) agents_dir/agent_name_folder/agent.py (where agent.py has root_agent)
31
+ a) {agent_name}.agent as a module name:
32
+ agents_dir/{agent_name}/agent.py (with root_agent defined in the module)
33
+ b) {agent_name} as a module name
34
+ agents_dir/{agent_name}.py (with root_agent defined in the module)
35
+ c) {agent_name} as a package name
36
+ agents_dir/{agent_name}/__init__.py (with root_agent in the package)
37
+
33
38
"""
34
39
35
40
def __init__ (self , agents_dir : str ):
36
41
self .agents_dir = agents_dir .rstrip ("/" )
37
42
self ._original_sys_path = None
38
43
self ._agent_cache : dict [str , BaseAgent ] = {}
39
44
40
- def _load_from_module_or_package (self , agent_name : str ) -> BaseAgent :
41
- # Load for case: Import "<agent_name>" (as a package or module)
45
+ def _load_from_module_or_package (
46
+ self , agent_name : str
47
+ ) -> Optional [BaseAgent ]:
48
+ # Load for case: Import "{agent_name}" (as a package or module)
42
49
# Covers structures:
43
- # a) agents_dir/agent_name.py (with root_agent or agent.root_agent in it )
44
- # b) agents_dir/agent_name_folder /__init__.py (with root_agent or agent. root_agent in the package)
50
+ # a) agents_dir/{ agent_name} .py (with root_agent in the module )
51
+ # b) agents_dir/{agent_name} /__init__.py (with root_agent in the package)
45
52
try :
46
53
module_candidate = importlib .import_module (agent_name )
47
- # Check for "root_agent" directly in "< agent_name> " module/package
54
+ # Check for "root_agent" directly in "{ agent_name} " module/package
48
55
if hasattr (module_candidate , "root_agent" ):
49
56
logger .debug ("Found root_agent directly in %s" , agent_name )
50
- return module_candidate .root_agent
51
- # Check for "<agent_name>.agent.root_agent" structure (e.g. agent_name is a package,
52
- # and it has an 'agent' submodule/attribute which in turn has 'root_agent')
53
- if hasattr (module_candidate , "agent" ) and hasattr (
54
- module_candidate .agent , "root_agent"
55
- ):
56
- logger .debug ("Found root_agent in %s.agent attribute" , agent_name )
57
- if isinstance (module_candidate .agent , BaseAgent ):
58
- return module_candidate .agent .root_agent
57
+ if isinstance (module_candidate .root_agent , BaseAgent ):
58
+ return module_candidate .root_agent
59
59
else :
60
60
logger .warning (
61
61
"Root agent found is not an instance of BaseAgent. But a type %s" ,
62
- type (module_candidate .agent ),
62
+ type (module_candidate .root_agent ),
63
63
)
64
+
64
65
except ModuleNotFoundError :
65
66
logger .debug ("Module %s itself not found." , agent_name )
66
67
# Re-raise as ValueError to be caught by the final error message construction
@@ -72,13 +73,13 @@ def _load_from_module_or_package(self, agent_name: str) -> BaseAgent:
72
73
73
74
return None
74
75
75
- def _load_from_submodule (self , agent_name : str ) -> BaseAgent :
76
- # Load for case: Import "< agent_name> .agent" and look for "root_agent"
77
- # Covers structure: agents_dir/agent_name_folder /agent.py (where agent.py has root_agent )
76
+ def _load_from_submodule (self , agent_name : str ) -> Optional [ BaseAgent ] :
77
+ # Load for case: Import "{ agent_name} .agent" and look for "root_agent"
78
+ # Covers structure: agents_dir/{agent_name} /agent.py (with root_agent defined in the module )
78
79
try :
79
80
module_candidate = importlib .import_module (f"{ agent_name } .agent" )
80
81
if hasattr (module_candidate , "root_agent" ):
81
- logger .debug ("Found root_agent in %s.agent" , agent_name )
82
+ logger .info ("Found root_agent in %s.agent" , agent_name )
82
83
if isinstance (module_candidate .root_agent , BaseAgent ):
83
84
return module_candidate .root_agent
84
85
else :
@@ -106,32 +107,28 @@ def _perform_load(self, agent_name: str) -> BaseAgent:
106
107
)
107
108
envs .load_dotenv_for_agent (agent_name , str (self .agents_dir ))
108
109
109
- root_agent = self ._load_from_module_or_package (agent_name )
110
- if root_agent :
110
+ if root_agent := self ._load_from_submodule (agent_name ):
111
111
return root_agent
112
112
113
- root_agent = self ._load_from_submodule (agent_name )
114
- if root_agent :
113
+ if root_agent := self ._load_from_module_or_package (agent_name ):
115
114
return root_agent
116
115
117
116
# If no root_agent was found by any pattern
118
117
raise ValueError (
119
118
f"No root_agent found for '{ agent_name } '. Searched in"
120
- f" '{ agent_name } .agent.root_agent', '{ agent_name } .root_agent', and"
121
- f" via an 'agent' attribute within the '{ agent_name } ' module/package."
119
+ f" '{ agent_name } .agent.root_agent', '{ agent_name } .root_agent'."
122
120
f" Ensure '{ self .agents_dir } /{ agent_name } ' is structured correctly,"
123
121
" an .env file can be loaded if present, and a root_agent is"
124
122
" exposed."
125
123
)
126
124
127
125
def load_agent (self , agent_name : str ) -> BaseAgent :
128
- """Load an agent module (with caching & .env) and return its root_agent (asynchronously) ."""
126
+ """Load an agent module (with caching & .env) and return its root_agent."""
129
127
if agent_name in self ._agent_cache :
130
128
logger .debug ("Returning cached agent for %s (async)" , agent_name )
131
129
return self ._agent_cache [agent_name ]
132
130
133
131
logger .debug ("Loading agent %s - not in cache." , agent_name )
134
- # Assumes this method is called when the context manager (`with self:`) is active
135
132
agent = self ._perform_load (agent_name )
136
133
self ._agent_cache [agent_name ] = agent
137
134
return agent
0 commit comments