|
2 | 2 | import logging |
3 | 3 | from typing import TYPE_CHECKING, Callable, Type, Optional, Any, Dict |
4 | 4 | from inspect import signature, Parameter |
5 | | -from pydantic import BaseModel, Field, ValidationError, model_validator, PrivateAttr |
| 5 | +from pydantic import ( |
| 6 | + BaseModel, |
| 7 | + Field, |
| 8 | + ValidationError, |
| 9 | + model_validator, |
| 10 | + PrivateAttr, |
| 11 | + create_model, |
| 12 | +) |
6 | 13 | from mcp.types import CallToolResult, TextContent |
7 | 14 | from dapr_agents.tool.utils.tool import ToolHelper |
8 | 15 | from dapr_agents.tool.utils.function_calling import to_function_call_definition |
|
11 | 18 | if TYPE_CHECKING: |
12 | 19 | from mcp.types import Tool as MCPTool |
13 | 20 | from mcp import ClientSession |
| 21 | + from toolbox_core.sync_tool import ToolboxSyncTool |
14 | 22 |
|
15 | 23 | logger = logging.getLogger(__name__) |
16 | 24 |
|
@@ -190,6 +198,96 @@ async def from_mcp_session(cls, session: "ClientSession") -> list: |
190 | 198 | session=session, |
191 | 199 | ) |
192 | 200 |
|
| 201 | + @classmethod |
| 202 | + def from_toolbox( |
| 203 | + cls, |
| 204 | + toolbox_tool: "ToolboxSyncTool", |
| 205 | + ) -> "AgentTool": |
| 206 | + """ |
| 207 | + Create an AgentTool from a ToolboxSyncTool. |
| 208 | +
|
| 209 | + Args: |
| 210 | + toolbox_tool: A ToolboxSyncTool instance from toolbox-core. |
| 211 | +
|
| 212 | + Returns: |
| 213 | + AgentTool: A ready-to-use AgentTool instance. |
| 214 | + """ |
| 215 | + tool_name = toolbox_tool._name |
| 216 | + tool_description = toolbox_tool._description |
| 217 | + |
| 218 | + def executor(**kwargs: Any) -> Any: |
| 219 | + try: |
| 220 | + logger.debug(f"Calling Toolbox tool '{tool_name}' with args: {kwargs}") |
| 221 | + result = toolbox_tool(**kwargs) |
| 222 | + tool_result = CallToolResult( |
| 223 | + isError=False, |
| 224 | + content=[TextContent(type="text", text=str(result))], |
| 225 | + structuredContent={}, |
| 226 | + ) |
| 227 | + return tool_result |
| 228 | + except (ValidationError, ToolError, Exception) as e: |
| 229 | + err_type = type(e).__name__ |
| 230 | + logger.error(f"{err_type} running tool: {str(e)}") |
| 231 | + return CallToolResult( |
| 232 | + isError=True, |
| 233 | + content=[ |
| 234 | + TextContent( |
| 235 | + type="text", |
| 236 | + text=f"{err_type} during Tool Call. Arguments sent to Tool: {str(kwargs)}.\nError: {str(e)}", |
| 237 | + ) |
| 238 | + ], |
| 239 | + ) |
| 240 | + |
| 241 | + tool_args_model = None |
| 242 | + try: |
| 243 | + params = toolbox_tool._params |
| 244 | + if params and len(params) > 0: |
| 245 | + field_definitions = {} |
| 246 | + for param in params: |
| 247 | + annotation = ( |
| 248 | + param.annotation if hasattr(param, "annotation") else Any |
| 249 | + ) |
| 250 | + if param.required: |
| 251 | + field_definitions[param.name] = (annotation, ...) |
| 252 | + else: |
| 253 | + field_definitions[param.name] = (Optional[annotation], None) |
| 254 | + if field_definitions: |
| 255 | + tool_args_model = create_model( |
| 256 | + f"{tool_name}Args", **field_definitions |
| 257 | + ) |
| 258 | + else: |
| 259 | + # Create empty model to avoid infering kwargs onto model |
| 260 | + tool_args_model = create_model(f"{tool_name}Args") |
| 261 | + except Exception as e: |
| 262 | + logger.warning( |
| 263 | + f"Failed to create args model for Toolbox tool '{tool_name}': {e}" |
| 264 | + ) |
| 265 | + # Failed to create model from params, fallback to empty model |
| 266 | + tool_args_model = create_model(f"{tool_name}Args") |
| 267 | + |
| 268 | + return cls( |
| 269 | + name=tool_name, |
| 270 | + description=tool_description, |
| 271 | + func=executor, |
| 272 | + args_model=tool_args_model, |
| 273 | + ) |
| 274 | + |
| 275 | + @classmethod |
| 276 | + def from_toolbox_many( |
| 277 | + cls, |
| 278 | + toolbox_tools: list, |
| 279 | + ) -> list: |
| 280 | + """ |
| 281 | + Batch-create AgentTool objects from a list of ToolboxSyncTool objects. |
| 282 | +
|
| 283 | + Args: |
| 284 | + toolbox_tools: List of ToolboxSyncTool objects to convert. |
| 285 | +
|
| 286 | + Returns: |
| 287 | + List[AgentTool]: List of ready-to-use AgentTool objects. |
| 288 | + """ |
| 289 | + return [cls.from_toolbox(tool) for tool in toolbox_tools] |
| 290 | + |
193 | 291 | def model_post_init(self, __context: Any) -> None: |
194 | 292 | """ |
195 | 293 | Handles post-initialization logic for both class-based and function-based tools. |
|
0 commit comments