Skip to content

Commit 165bef2

Browse files
#133 separated method for tools resolving
1 parent f178e49 commit 165bef2

File tree

1 file changed

+118
-92
lines changed

1 file changed

+118
-92
lines changed

sgr_agent_core/agent_factory.py

Lines changed: 118 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,122 @@ def _create_client(cls, llm_config: LLMConfig) -> AsyncOpenAI:
4141

4242
return AsyncOpenAI(**client_kwargs)
4343

44+
@classmethod
45+
def _resolve_tool(cls, tool_name: str | type, config: GlobalConfig) -> type[BaseTool]:
46+
"""Resolve a single tool from its name or class.
47+
48+
Args:
49+
tool_name: Tool name (string) or tool class
50+
config: Global configuration containing tool definitions
51+
52+
Returns:
53+
Resolved tool class
54+
55+
Raises:
56+
TypeError: If tool class is not a subclass of BaseTool
57+
ValueError: If tool cannot be resolved
58+
"""
59+
# If tool_name is already a class, use it directly
60+
if isinstance(tool_name, type):
61+
if not issubclass(tool_name, BaseTool):
62+
raise TypeError(f"Tool class '{tool_name.__name__}' must be a subclass of BaseTool")
63+
return tool_name
64+
65+
tool_class = None
66+
67+
# First, try to find tool in config.tools
68+
if tool_name in config.tools:
69+
tool_def = config.tools[tool_name]
70+
base_class = tool_def.base_class
71+
72+
if base_class is None:
73+
# Generate default path: sgr_agent_core.tools.{ToolName}
74+
# Convert tool_name to ToolName (capitalize first letter, handle underscores)
75+
tool_class_name = "".join(word.capitalize() for word in tool_name.split("_"))
76+
base_class = f"sgr_agent_core.tools.{tool_class_name}"
77+
78+
# Resolve base_class (can be string, ImportString, or class)
79+
if isinstance(base_class, str):
80+
# Try import string resolution
81+
if "." in base_class:
82+
# Relative import - resolve relative to config file location
83+
# This is handled by sys.path in agent_config.from_yaml
84+
try:
85+
module_parts = base_class.split(".")
86+
if len(module_parts) >= 2:
87+
module_path = ".".join(module_parts[:-1])
88+
class_name = module_parts[-1]
89+
module = __import__(module_path, fromlist=[class_name])
90+
tool_class = getattr(module, class_name)
91+
except (ImportError, AttributeError) as e:
92+
logger.warning(
93+
f"Failed to import tool '{tool_name}' from '{base_class}': {e}. " f"Trying registry..."
94+
)
95+
else:
96+
# Try registry
97+
tool_class = ToolRegistry.get(base_class)
98+
elif isinstance(base_class, type):
99+
# Validate it's a BaseTool subclass
100+
if not issubclass(base_class, BaseTool):
101+
raise TypeError(f"Tool '{tool_name}' base_class must be a subclass of BaseTool")
102+
tool_class = base_class
103+
else:
104+
# ImportString - should be resolved by pydantic
105+
tool_class = base_class
106+
else:
107+
# Tool not in config.tools, try registry
108+
tool_class = ToolRegistry.get(tool_name)
109+
110+
# If still not found, try as import string
111+
if tool_class is None:
112+
if "." in tool_name:
113+
try:
114+
module_parts = tool_name.split(".")
115+
if len(module_parts) >= 2:
116+
module_path = ".".join(module_parts[:-1])
117+
class_name = module_parts[-1]
118+
module = __import__(module_path, fromlist=[class_name])
119+
tool_class = getattr(module, class_name)
120+
except (ImportError, AttributeError) as e:
121+
error_msg = (
122+
f"Tool '{tool_name}' not found in config.tools, registry, or failed to import.\n"
123+
f"Available tools in config: {', '.join(config.tools.keys())}\n"
124+
f"Available tools in registry: "
125+
f"{', '.join([c.__name__ for c in ToolRegistry.list_items()])}\n"
126+
f"Import error: {e}\n"
127+
f" - Check that the tool name is correct\n"
128+
f" - Define the tool in the 'tools' section of your config\n"
129+
f" - Or ensure the tool is registered in ToolRegistry"
130+
)
131+
logger.error(error_msg)
132+
raise ValueError(error_msg) from e
133+
134+
if tool_class is None:
135+
error_msg = (
136+
f"Tool '{tool_name}' not found.\n"
137+
f"Available tools in config: {', '.join(config.tools.keys())}\n"
138+
f"Available tools in registry: {', '.join([c.__name__ for c in ToolRegistry.list_items()])}\n"
139+
f" - Define the tool in the 'tools' section of your config\n"
140+
f" - Or ensure the tool is registered in ToolRegistry"
141+
)
142+
logger.error(error_msg)
143+
raise ValueError(error_msg)
144+
145+
return tool_class
146+
147+
@classmethod
148+
def _resolve_tools(cls, tool_names: list[str | type], config: GlobalConfig) -> list[type[BaseTool]]:
149+
"""Resolve multiple tools from their names or classes.
150+
151+
Args:
152+
tool_names: List of tool names (strings) or tool classes
153+
config: Global configuration containing tool definitions
154+
155+
Returns:
156+
List of resolved tool classes
157+
"""
158+
return [cls._resolve_tool(tool_name, config) for tool_name in tool_names]
159+
44160
@classmethod
45161
async def create(cls, agent_def: AgentDefinition, task_messages: list[ChatCompletionMessageParam]) -> Agent:
46162
"""Create an agent instance from a definition.
@@ -97,99 +213,9 @@ async def create(cls, agent_def: AgentDefinition, task_messages: list[ChatComple
97213
logger.error(error_msg)
98214
raise ValueError(error_msg)
99215
mcp_tools: list = await MCP2ToolConverter.build_tools_from_mcp(agent_def.mcp)
100-
101-
tools = [*mcp_tools]
102216
config = GlobalConfig()
103-
104-
for tool_name in agent_def.tools:
105-
tool_class = None
106-
107-
# If tool_name is already a class, use it directly
108-
if isinstance(tool_name, type):
109-
if not issubclass(tool_name, BaseTool):
110-
raise TypeError(f"Tool class '{tool_name.__name__}' must be a subclass of BaseTool")
111-
tools.append(tool_name)
112-
continue
113-
114-
# First, try to find tool in config.tools
115-
if tool_name in config.tools:
116-
tool_def = config.tools[tool_name]
117-
base_class = tool_def.base_class
118-
119-
if base_class is None:
120-
# Generate default path: sgr_agent_core.tools.{ToolName}
121-
# Convert tool_name to ToolName (capitalize first letter, handle underscores)
122-
tool_class_name = "".join(word.capitalize() for word in tool_name.split("_"))
123-
base_class = f"sgr_agent_core.tools.{tool_class_name}"
124-
125-
# Resolve base_class (can be string, ImportString, or class)
126-
if isinstance(base_class, str):
127-
# Try import string resolution
128-
if "." in base_class:
129-
# Relative import - resolve relative to config file location
130-
# This is handled by sys.path in agent_config.from_yaml
131-
try:
132-
module_parts = base_class.split(".")
133-
if len(module_parts) >= 2:
134-
module_path = ".".join(module_parts[:-1])
135-
class_name = module_parts[-1]
136-
module = __import__(module_path, fromlist=[class_name])
137-
tool_class = getattr(module, class_name)
138-
except (ImportError, AttributeError) as e:
139-
logger.warning(
140-
f"Failed to import tool '{tool_name}' from '{base_class}': {e}. " f"Trying registry..."
141-
)
142-
else:
143-
# Try registry
144-
tool_class = ToolRegistry.get(base_class)
145-
elif isinstance(base_class, type):
146-
# Validate it's a BaseTool subclass
147-
if not issubclass(base_class, BaseTool):
148-
raise TypeError(f"Tool '{tool_name}' base_class must be a subclass of BaseTool")
149-
tool_class = base_class
150-
else:
151-
# ImportString - should be resolved by pydantic
152-
tool_class = base_class
153-
else:
154-
# Tool not in config.tools, try registry
155-
tool_class = ToolRegistry.get(tool_name)
156-
157-
# If still not found, try as import string
158-
if tool_class is None:
159-
if "." in tool_name:
160-
try:
161-
module_parts = tool_name.split(".")
162-
if len(module_parts) >= 2:
163-
module_path = ".".join(module_parts[:-1])
164-
class_name = module_parts[-1]
165-
module = __import__(module_path, fromlist=[class_name])
166-
tool_class = getattr(module, class_name)
167-
except (ImportError, AttributeError) as e:
168-
error_msg = (
169-
f"Tool '{tool_name}' not found in config.tools, registry, or failed to import.\n"
170-
f"Available tools in config: {', '.join(config.tools.keys())}\n"
171-
f"Available tools in registry: "
172-
f"{', '.join([c.__name__ for c in ToolRegistry.list_items()])}\n"
173-
f"Import error: {e}\n"
174-
f" - Check that the tool name is correct\n"
175-
f" - Define the tool in the 'tools' section of your config\n"
176-
f" - Or ensure the tool is registered in ToolRegistry"
177-
)
178-
logger.error(error_msg)
179-
raise ValueError(error_msg) from e
180-
181-
if tool_class is None:
182-
error_msg = (
183-
f"Tool '{tool_name}' not found.\n"
184-
f"Available tools in config: {', '.join(config.tools.keys())}\n"
185-
f"Available tools in registry: {', '.join([c.__name__ for c in ToolRegistry.list_items()])}\n"
186-
f" - Define the tool in the 'tools' section of your config\n"
187-
f" - Or ensure the tool is registered in ToolRegistry"
188-
)
189-
logger.error(error_msg)
190-
raise ValueError(error_msg)
191-
192-
tools.append(tool_class)
217+
tools = cls._resolve_tools(agent_def.tools, config)
218+
tools.extend(mcp_tools)
193219

194220
try:
195221
# Extract agent-specific parameters from agent_def (e.g., working_directory)

0 commit comments

Comments
 (0)