Skip to content

strands.tools.loader

Tool loading utilities.

_TOOL_MODULE_PREFIX = '_strands_tool_' module-attribute

logger = logging.getLogger(__name__) module-attribute

AgentTool

Bases: ABC

Abstract base class for all SDK tools.

This class defines the interface that all tool implementations must follow. Each tool must provide its name, specification, and implement a stream method that executes the tool's functionality.

Source code in strands/types/tools.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
class AgentTool(ABC):
    """Abstract base class for all SDK tools.

    This class defines the interface that all tool implementations must follow. Each tool must provide its name,
    specification, and implement a stream method that executes the tool's functionality.
    """

    _is_dynamic: bool

    def __init__(self) -> None:
        """Initialize the base agent tool with default dynamic state."""
        self._is_dynamic = False

    @property
    @abstractmethod
    # pragma: no cover
    def tool_name(self) -> str:
        """The unique name of the tool used for identification and invocation."""
        pass

    @property
    @abstractmethod
    # pragma: no cover
    def tool_spec(self) -> ToolSpec:
        """Tool specification that describes its functionality and parameters."""
        pass

    @property
    @abstractmethod
    # pragma: no cover
    def tool_type(self) -> str:
        """The type of the tool implementation (e.g., 'python', 'javascript', 'lambda').

        Used for categorization and appropriate handling.
        """
        pass

    @property
    def supports_hot_reload(self) -> bool:
        """Whether the tool supports automatic reloading when modified.

        Returns:
            False by default.
        """
        return False

    @abstractmethod
    # pragma: no cover
    def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
        """Stream tool events and return the final result.

        Args:
            tool_use: The tool use request containing tool ID and parameters.
            invocation_state: Caller-provided kwargs that were passed to the agent when it was invoked (agent(),
                              agent.invoke_async(), etc.).
            **kwargs: Additional keyword arguments for future extensibility.

        Yields:
            Tool events with the last being the tool result.
        """
        ...

    @property
    def is_dynamic(self) -> bool:
        """Whether the tool was dynamically loaded during runtime.

        Dynamic tools may have different lifecycle management.

        Returns:
            True if loaded dynamically, False otherwise.
        """
        return self._is_dynamic

    def mark_dynamic(self) -> None:
        """Mark this tool as dynamically loaded."""
        self._is_dynamic = True

    def get_display_properties(self) -> dict[str, str]:
        """Get properties to display in UI representations of this tool.

        Subclasses can extend this to include additional properties.

        Returns:
            Dictionary of property names and their string values.
        """
        return {
            "Name": self.tool_name,
            "Type": self.tool_type,
        }

is_dynamic property

Whether the tool was dynamically loaded during runtime.

Dynamic tools may have different lifecycle management.

Returns:

Type Description
bool

True if loaded dynamically, False otherwise.

supports_hot_reload property

Whether the tool supports automatic reloading when modified.

Returns:

Type Description
bool

False by default.

tool_name abstractmethod property

The unique name of the tool used for identification and invocation.

tool_spec abstractmethod property

Tool specification that describes its functionality and parameters.

tool_type abstractmethod property

The type of the tool implementation (e.g., 'python', 'javascript', 'lambda').

Used for categorization and appropriate handling.

__init__()

Initialize the base agent tool with default dynamic state.

Source code in strands/types/tools.py
227
228
229
def __init__(self) -> None:
    """Initialize the base agent tool with default dynamic state."""
    self._is_dynamic = False

get_display_properties()

Get properties to display in UI representations of this tool.

Subclasses can extend this to include additional properties.

Returns:

Type Description
dict[str, str]

Dictionary of property names and their string values.

Source code in strands/types/tools.py
295
296
297
298
299
300
301
302
303
304
305
306
def get_display_properties(self) -> dict[str, str]:
    """Get properties to display in UI representations of this tool.

    Subclasses can extend this to include additional properties.

    Returns:
        Dictionary of property names and their string values.
    """
    return {
        "Name": self.tool_name,
        "Type": self.tool_type,
    }

mark_dynamic()

Mark this tool as dynamically loaded.

Source code in strands/types/tools.py
291
292
293
def mark_dynamic(self) -> None:
    """Mark this tool as dynamically loaded."""
    self._is_dynamic = True

stream(tool_use, invocation_state, **kwargs) abstractmethod

Stream tool events and return the final result.

Parameters:

Name Type Description Default
tool_use ToolUse

The tool use request containing tool ID and parameters.

required
invocation_state dict[str, Any]

Caller-provided kwargs that were passed to the agent when it was invoked (agent(), agent.invoke_async(), etc.).

required
**kwargs Any

Additional keyword arguments for future extensibility.

{}

Yields:

Type Description
ToolGenerator

Tool events with the last being the tool result.

Source code in strands/types/tools.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
@abstractmethod
# pragma: no cover
def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
    """Stream tool events and return the final result.

    Args:
        tool_use: The tool use request containing tool ID and parameters.
        invocation_state: Caller-provided kwargs that were passed to the agent when it was invoked (agent(),
                          agent.invoke_async(), etc.).
        **kwargs: Additional keyword arguments for future extensibility.

    Yields:
        Tool events with the last being the tool result.
    """
    ...

DecoratedFunctionTool

Bases: AgentTool, Generic[P, R]

An AgentTool that wraps a function that was decorated with @tool.

This class adapts Python functions decorated with @tool to the AgentTool interface. It handles both direct function calls and tool use invocations, maintaining the function's original behavior while adding tool capabilities.

The class is generic over the function's parameter types (P) and return type (R) to maintain type safety.

Source code in strands/tools/decorator.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
class DecoratedFunctionTool(AgentTool, Generic[P, R]):
    """An AgentTool that wraps a function that was decorated with @tool.

    This class adapts Python functions decorated with @tool to the AgentTool interface. It handles both direct
    function calls and tool use invocations, maintaining the function's
    original behavior while adding tool capabilities.

    The class is generic over the function's parameter types (P) and return type (R) to maintain type safety.
    """

    _tool_name: str
    _tool_spec: ToolSpec
    _tool_func: Callable[P, R]
    _metadata: FunctionToolMetadata

    def __init__(
        self,
        tool_name: str,
        tool_spec: ToolSpec,
        tool_func: Callable[P, R],
        metadata: FunctionToolMetadata,
    ):
        """Initialize the decorated function tool.

        Args:
            tool_name: The name to use for the tool (usually the function name).
            tool_spec: The tool specification containing metadata for Agent integration.
            tool_func: The original function being decorated.
            metadata: The FunctionToolMetadata object with extracted function information.
        """
        super().__init__()

        self._tool_name = tool_name
        self._tool_spec = tool_spec
        self._tool_func = tool_func
        self._metadata = metadata

        functools.update_wrapper(wrapper=self, wrapped=self._tool_func)

    def __get__(self, instance: Any, obj_type: Optional[Type] = None) -> "DecoratedFunctionTool[P, R]":
        """Descriptor protocol implementation for proper method binding.

        This method enables the decorated function to work correctly when used as a class method.
        It binds the instance to the function call when accessed through an instance.

        Args:
            instance: The instance through which the descriptor is accessed, or None when accessed through the class.
            obj_type: The class through which the descriptor is accessed.

        Returns:
            A new DecoratedFunctionTool with the instance bound to the function if accessed through an instance,
            otherwise returns self.

        Example:
            ```python
            class MyClass:
                @tool
                def my_tool():
                    ...

            instance = MyClass()
            # instance of DecoratedFunctionTool that works as you'd expect
            tool = instance.my_tool
            ```
        """
        if instance is not None and not inspect.ismethod(self._tool_func):
            # Create a bound method
            tool_func = self._tool_func.__get__(instance, instance.__class__)
            return DecoratedFunctionTool(self._tool_name, self._tool_spec, tool_func, self._metadata)

        return self

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
        """Call the original function with the provided arguments.

        This method enables the decorated function to be called directly with its original signature,
        preserving the normal function call behavior.

        Args:
            *args: Positional arguments to pass to the function.
            **kwargs: Keyword arguments to pass to the function.

        Returns:
            The result of the original function call.
        """
        return self._tool_func(*args, **kwargs)

    @property
    def tool_name(self) -> str:
        """Get the name of the tool.

        Returns:
            The tool name as a string.
        """
        return self._tool_name

    @property
    def tool_spec(self) -> ToolSpec:
        """Get the tool specification.

        Returns:
            The tool specification dictionary containing metadata for Agent integration.
        """
        return self._tool_spec

    @property
    def tool_type(self) -> str:
        """Get the type of the tool.

        Returns:
            The string "function" indicating this is a function-based tool.
        """
        return "function"

    @override
    async def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
        """Stream the tool with a tool use specification.

        This method handles tool use streams from a Strands Agent. It validates the input,
        calls the function, and formats the result according to the expected tool result format.

        Key operations:

        1. Extract tool use ID and input parameters
        2. Validate input against the function's expected parameters
        3. Call the function with validated input
        4. Format the result as a standard tool result
        5. Handle and format any errors that occur

        Args:
            tool_use: The tool use specification from the Agent.
            invocation_state: Caller-provided kwargs that were passed to the agent when it was invoked (agent(),
                              agent.invoke_async(), etc.).
            **kwargs: Additional keyword arguments for future extensibility.

        Yields:
            Tool events with the last being the tool result.
        """
        # This is a tool use call - process accordingly
        tool_use_id = tool_use.get("toolUseId", "unknown")
        tool_input: dict[str, Any] = tool_use.get("input", {})

        try:
            # Validate input against the Pydantic model
            validated_input = self._metadata.validate_input(tool_input)

            # Inject special framework-provided parameters
            self._metadata.inject_special_parameters(validated_input, tool_use, invocation_state)

            # Note: "Too few arguments" expected for the _tool_func calls, hence the type ignore

            # Async-generators, yield streaming events and final tool result
            if inspect.isasyncgenfunction(self._tool_func):
                sub_events = self._tool_func(**validated_input)  # type: ignore
                async for sub_event in sub_events:
                    yield ToolStreamEvent(tool_use, sub_event)

                # The last event is the result
                yield self._wrap_tool_result(tool_use_id, sub_event)

            # Async functions, yield only the result
            elif inspect.iscoroutinefunction(self._tool_func):
                result = await self._tool_func(**validated_input)  # type: ignore
                yield self._wrap_tool_result(tool_use_id, result)

            # Other functions, yield only the result
            else:
                result = await asyncio.to_thread(self._tool_func, **validated_input)  # type: ignore
                yield self._wrap_tool_result(tool_use_id, result)

        except InterruptException as e:
            yield ToolInterruptEvent(tool_use, [e.interrupt])
            return

        except ValueError as e:
            # Special handling for validation errors
            error_msg = str(e)
            yield self._wrap_tool_result(
                tool_use_id,
                {
                    "toolUseId": tool_use_id,
                    "status": "error",
                    "content": [{"text": f"Error: {error_msg}"}],
                },
            )
        except Exception as e:
            # Return error result with exception details for any other error
            error_type = type(e).__name__
            error_msg = str(e)
            yield self._wrap_tool_result(
                tool_use_id,
                {
                    "toolUseId": tool_use_id,
                    "status": "error",
                    "content": [{"text": f"Error: {error_type} - {error_msg}"}],
                },
            )

    def _wrap_tool_result(self, tool_use_d: str, result: Any) -> ToolResultEvent:
        # FORMAT THE RESULT for Strands Agent
        if isinstance(result, dict) and "status" in result and "content" in result:
            # Result is already in the expected format, just add toolUseId
            result["toolUseId"] = tool_use_d
            return ToolResultEvent(cast(ToolResult, result))
        else:
            # Wrap any other return value in the standard format
            # Always include at least one content item for consistency
            return ToolResultEvent(
                {
                    "toolUseId": tool_use_d,
                    "status": "success",
                    "content": [{"text": str(result)}],
                }
            )

    @property
    def supports_hot_reload(self) -> bool:
        """Check if this tool supports automatic reloading when modified.

        Returns:
            Always true for function-based tools.
        """
        return True

    @override
    def get_display_properties(self) -> dict[str, str]:
        """Get properties to display in UI representations.

        Returns:
            Function properties (e.g., function name).
        """
        properties = super().get_display_properties()
        properties["Function"] = self._tool_func.__name__
        return properties

supports_hot_reload property

Check if this tool supports automatic reloading when modified.

Returns:

Type Description
bool

Always true for function-based tools.

tool_name property

Get the name of the tool.

Returns:

Type Description
str

The tool name as a string.

tool_spec property

Get the tool specification.

Returns:

Type Description
ToolSpec

The tool specification dictionary containing metadata for Agent integration.

tool_type property

Get the type of the tool.

Returns:

Type Description
str

The string "function" indicating this is a function-based tool.

__call__(*args, **kwargs)

Call the original function with the provided arguments.

This method enables the decorated function to be called directly with its original signature, preserving the normal function call behavior.

Parameters:

Name Type Description Default
*args args

Positional arguments to pass to the function.

()
**kwargs kwargs

Keyword arguments to pass to the function.

{}

Returns:

Type Description
R

The result of the original function call.

Source code in strands/tools/decorator.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
    """Call the original function with the provided arguments.

    This method enables the decorated function to be called directly with its original signature,
    preserving the normal function call behavior.

    Args:
        *args: Positional arguments to pass to the function.
        **kwargs: Keyword arguments to pass to the function.

    Returns:
        The result of the original function call.
    """
    return self._tool_func(*args, **kwargs)

__get__(instance, obj_type=None)

Descriptor protocol implementation for proper method binding.

This method enables the decorated function to work correctly when used as a class method. It binds the instance to the function call when accessed through an instance.

Parameters:

Name Type Description Default
instance Any

The instance through which the descriptor is accessed, or None when accessed through the class.

required
obj_type Optional[Type]

The class through which the descriptor is accessed.

None

Returns:

Type Description
DecoratedFunctionTool[P, R]

A new DecoratedFunctionTool with the instance bound to the function if accessed through an instance,

DecoratedFunctionTool[P, R]

otherwise returns self.

Example
class MyClass:
    @tool
    def my_tool():
        ...

instance = MyClass()
# instance of DecoratedFunctionTool that works as you'd expect
tool = instance.my_tool
Source code in strands/tools/decorator.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def __get__(self, instance: Any, obj_type: Optional[Type] = None) -> "DecoratedFunctionTool[P, R]":
    """Descriptor protocol implementation for proper method binding.

    This method enables the decorated function to work correctly when used as a class method.
    It binds the instance to the function call when accessed through an instance.

    Args:
        instance: The instance through which the descriptor is accessed, or None when accessed through the class.
        obj_type: The class through which the descriptor is accessed.

    Returns:
        A new DecoratedFunctionTool with the instance bound to the function if accessed through an instance,
        otherwise returns self.

    Example:
        ```python
        class MyClass:
            @tool
            def my_tool():
                ...

        instance = MyClass()
        # instance of DecoratedFunctionTool that works as you'd expect
        tool = instance.my_tool
        ```
    """
    if instance is not None and not inspect.ismethod(self._tool_func):
        # Create a bound method
        tool_func = self._tool_func.__get__(instance, instance.__class__)
        return DecoratedFunctionTool(self._tool_name, self._tool_spec, tool_func, self._metadata)

    return self

__init__(tool_name, tool_spec, tool_func, metadata)

Initialize the decorated function tool.

Parameters:

Name Type Description Default
tool_name str

The name to use for the tool (usually the function name).

required
tool_spec ToolSpec

The tool specification containing metadata for Agent integration.

required
tool_func Callable[P, R]

The original function being decorated.

required
metadata FunctionToolMetadata

The FunctionToolMetadata object with extracted function information.

required
Source code in strands/tools/decorator.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
def __init__(
    self,
    tool_name: str,
    tool_spec: ToolSpec,
    tool_func: Callable[P, R],
    metadata: FunctionToolMetadata,
):
    """Initialize the decorated function tool.

    Args:
        tool_name: The name to use for the tool (usually the function name).
        tool_spec: The tool specification containing metadata for Agent integration.
        tool_func: The original function being decorated.
        metadata: The FunctionToolMetadata object with extracted function information.
    """
    super().__init__()

    self._tool_name = tool_name
    self._tool_spec = tool_spec
    self._tool_func = tool_func
    self._metadata = metadata

    functools.update_wrapper(wrapper=self, wrapped=self._tool_func)

get_display_properties()

Get properties to display in UI representations.

Returns:

Type Description
dict[str, str]

Function properties (e.g., function name).

Source code in strands/tools/decorator.py
651
652
653
654
655
656
657
658
659
660
@override
def get_display_properties(self) -> dict[str, str]:
    """Get properties to display in UI representations.

    Returns:
        Function properties (e.g., function name).
    """
    properties = super().get_display_properties()
    properties["Function"] = self._tool_func.__name__
    return properties

stream(tool_use, invocation_state, **kwargs) async

Stream the tool with a tool use specification.

This method handles tool use streams from a Strands Agent. It validates the input, calls the function, and formats the result according to the expected tool result format.

Key operations:

  1. Extract tool use ID and input parameters
  2. Validate input against the function's expected parameters
  3. Call the function with validated input
  4. Format the result as a standard tool result
  5. Handle and format any errors that occur

Parameters:

Name Type Description Default
tool_use ToolUse

The tool use specification from the Agent.

required
invocation_state dict[str, Any]

Caller-provided kwargs that were passed to the agent when it was invoked (agent(), agent.invoke_async(), etc.).

required
**kwargs Any

Additional keyword arguments for future extensibility.

{}

Yields:

Type Description
ToolGenerator

Tool events with the last being the tool result.

Source code in strands/tools/decorator.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
@override
async def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
    """Stream the tool with a tool use specification.

    This method handles tool use streams from a Strands Agent. It validates the input,
    calls the function, and formats the result according to the expected tool result format.

    Key operations:

    1. Extract tool use ID and input parameters
    2. Validate input against the function's expected parameters
    3. Call the function with validated input
    4. Format the result as a standard tool result
    5. Handle and format any errors that occur

    Args:
        tool_use: The tool use specification from the Agent.
        invocation_state: Caller-provided kwargs that were passed to the agent when it was invoked (agent(),
                          agent.invoke_async(), etc.).
        **kwargs: Additional keyword arguments for future extensibility.

    Yields:
        Tool events with the last being the tool result.
    """
    # This is a tool use call - process accordingly
    tool_use_id = tool_use.get("toolUseId", "unknown")
    tool_input: dict[str, Any] = tool_use.get("input", {})

    try:
        # Validate input against the Pydantic model
        validated_input = self._metadata.validate_input(tool_input)

        # Inject special framework-provided parameters
        self._metadata.inject_special_parameters(validated_input, tool_use, invocation_state)

        # Note: "Too few arguments" expected for the _tool_func calls, hence the type ignore

        # Async-generators, yield streaming events and final tool result
        if inspect.isasyncgenfunction(self._tool_func):
            sub_events = self._tool_func(**validated_input)  # type: ignore
            async for sub_event in sub_events:
                yield ToolStreamEvent(tool_use, sub_event)

            # The last event is the result
            yield self._wrap_tool_result(tool_use_id, sub_event)

        # Async functions, yield only the result
        elif inspect.iscoroutinefunction(self._tool_func):
            result = await self._tool_func(**validated_input)  # type: ignore
            yield self._wrap_tool_result(tool_use_id, result)

        # Other functions, yield only the result
        else:
            result = await asyncio.to_thread(self._tool_func, **validated_input)  # type: ignore
            yield self._wrap_tool_result(tool_use_id, result)

    except InterruptException as e:
        yield ToolInterruptEvent(tool_use, [e.interrupt])
        return

    except ValueError as e:
        # Special handling for validation errors
        error_msg = str(e)
        yield self._wrap_tool_result(
            tool_use_id,
            {
                "toolUseId": tool_use_id,
                "status": "error",
                "content": [{"text": f"Error: {error_msg}"}],
            },
        )
    except Exception as e:
        # Return error result with exception details for any other error
        error_type = type(e).__name__
        error_msg = str(e)
        yield self._wrap_tool_result(
            tool_use_id,
            {
                "toolUseId": tool_use_id,
                "status": "error",
                "content": [{"text": f"Error: {error_type} - {error_msg}"}],
            },
        )

PythonAgentTool

Bases: AgentTool

Tool implementation for Python-based tools.

This class handles tools implemented as Python functions, providing a simple interface for executing Python code as SDK tools.

Source code in strands/tools/tools.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
class PythonAgentTool(AgentTool):
    """Tool implementation for Python-based tools.

    This class handles tools implemented as Python functions, providing a simple interface for executing Python code
    as SDK tools.
    """

    _tool_name: str
    _tool_spec: ToolSpec
    _tool_func: ToolFunc

    def __init__(self, tool_name: str, tool_spec: ToolSpec, tool_func: ToolFunc) -> None:
        """Initialize a Python-based tool.

        Args:
            tool_name: Unique identifier for the tool.
            tool_spec: Tool specification defining parameters and behavior.
            tool_func: Python function to execute when the tool is invoked.
        """
        super().__init__()

        self._tool_name = tool_name
        self._tool_spec = tool_spec
        self._tool_func = tool_func

    @property
    def tool_name(self) -> str:
        """Get the name of the tool.

        Returns:
            The name of the tool.
        """
        return self._tool_name

    @property
    def tool_spec(self) -> ToolSpec:
        """Get the tool specification for this Python-based tool.

        Returns:
            The tool specification.
        """
        return self._tool_spec

    @property
    def supports_hot_reload(self) -> bool:
        """Check if this tool supports automatic reloading when modified.

        Returns:
            Always true for function-based tools.
        """
        return True

    @property
    def tool_type(self) -> str:
        """Identifies this as a Python-based tool implementation.

        Returns:
            "python".
        """
        return "python"

    @override
    async def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
        """Stream the Python function with the given tool use request.

        Args:
            tool_use: The tool use request.
            invocation_state: Context for the tool invocation, including agent state.
            **kwargs: Additional keyword arguments for future extensibility.

        Yields:
            Tool events with the last being the tool result.
        """
        if inspect.iscoroutinefunction(self._tool_func):
            result = await self._tool_func(tool_use, **invocation_state)
            yield ToolResultEvent(result)
        else:
            result = await asyncio.to_thread(self._tool_func, tool_use, **invocation_state)
            yield ToolResultEvent(result)

supports_hot_reload property

Check if this tool supports automatic reloading when modified.

Returns:

Type Description
bool

Always true for function-based tools.

tool_name property

Get the name of the tool.

Returns:

Type Description
str

The name of the tool.

tool_spec property

Get the tool specification for this Python-based tool.

Returns:

Type Description
ToolSpec

The tool specification.

tool_type property

Identifies this as a Python-based tool implementation.

Returns:

Type Description
str

"python".

__init__(tool_name, tool_spec, tool_func)

Initialize a Python-based tool.

Parameters:

Name Type Description Default
tool_name str

Unique identifier for the tool.

required
tool_spec ToolSpec

Tool specification defining parameters and behavior.

required
tool_func ToolFunc

Python function to execute when the tool is invoked.

required
Source code in strands/tools/tools.py
168
169
170
171
172
173
174
175
176
177
178
179
180
def __init__(self, tool_name: str, tool_spec: ToolSpec, tool_func: ToolFunc) -> None:
    """Initialize a Python-based tool.

    Args:
        tool_name: Unique identifier for the tool.
        tool_spec: Tool specification defining parameters and behavior.
        tool_func: Python function to execute when the tool is invoked.
    """
    super().__init__()

    self._tool_name = tool_name
    self._tool_spec = tool_spec
    self._tool_func = tool_func

stream(tool_use, invocation_state, **kwargs) async

Stream the Python function with the given tool use request.

Parameters:

Name Type Description Default
tool_use ToolUse

The tool use request.

required
invocation_state dict[str, Any]

Context for the tool invocation, including agent state.

required
**kwargs Any

Additional keyword arguments for future extensibility.

{}

Yields:

Type Description
ToolGenerator

Tool events with the last being the tool result.

Source code in strands/tools/tools.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@override
async def stream(self, tool_use: ToolUse, invocation_state: dict[str, Any], **kwargs: Any) -> ToolGenerator:
    """Stream the Python function with the given tool use request.

    Args:
        tool_use: The tool use request.
        invocation_state: Context for the tool invocation, including agent state.
        **kwargs: Additional keyword arguments for future extensibility.

    Yields:
        Tool events with the last being the tool result.
    """
    if inspect.iscoroutinefunction(self._tool_func):
        result = await self._tool_func(tool_use, **invocation_state)
        yield ToolResultEvent(result)
    else:
        result = await asyncio.to_thread(self._tool_func, tool_use, **invocation_state)
        yield ToolResultEvent(result)

ToolLoader

Handles loading of tools from different sources.

Source code in strands/tools/loader.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
class ToolLoader:
    """Handles loading of tools from different sources."""

    @staticmethod
    def load_python_tools(tool_path: str, tool_name: str) -> List[AgentTool]:
        """DEPRECATED: Load a Python tool module and return all discovered function-based tools as a list.

        This method always returns a list of AgentTool (possibly length 1). It is the
        canonical API for retrieving multiple tools from a single Python file.
        """
        warnings.warn(
            "ToolLoader.load_python_tool is deprecated and will be removed in Strands SDK 2.0. "
            "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        try:
            # Support module:function style (e.g. package.module:function)
            if not os.path.exists(tool_path) and ":" in tool_path:
                module_path, function_name = tool_path.rsplit(":", 1)
                logger.debug("tool_name=<%s>, module_path=<%s> | importing tool from path", function_name, module_path)

                try:
                    module = __import__(module_path, fromlist=["*"])
                except ImportError as e:
                    raise ImportError(f"Failed to import module {module_path}: {str(e)}") from e

                if not hasattr(module, function_name):
                    raise AttributeError(f"Module {module_path} has no function named {function_name}")

                func = getattr(module, function_name)
                if isinstance(func, DecoratedFunctionTool):
                    logger.debug(
                        "tool_name=<%s>, module_path=<%s> | found function-based tool", function_name, module_path
                    )
                    return [cast(AgentTool, func)]
                else:
                    raise ValueError(
                        f"Function {function_name} in {module_path} is not a valid tool (missing @tool decorator)"
                    )

            # Normal file-based tool loading
            abs_path = str(Path(tool_path).resolve())
            logger.debug("tool_path=<%s> | loading python tool from path", abs_path)

            # Load the module by spec
            spec = importlib.util.spec_from_file_location(tool_name, abs_path)
            if not spec:
                raise ImportError(f"Could not create spec for {tool_name}")
            if not spec.loader:
                raise ImportError(f"No loader available for {tool_name}")

            module = importlib.util.module_from_spec(spec)
            sys.modules[f"{_TOOL_MODULE_PREFIX}{tool_name}"] = module
            spec.loader.exec_module(module)

            # Collect function-based tools decorated with @tool
            function_tools: List[AgentTool] = []
            for attr_name in dir(module):
                attr = getattr(module, attr_name)
                if isinstance(attr, DecoratedFunctionTool):
                    logger.debug(
                        "tool_name=<%s>, tool_path=<%s> | found function-based tool in path", attr_name, tool_path
                    )
                    function_tools.append(cast(AgentTool, attr))

            if function_tools:
                return function_tools

            # Fall back to module-level TOOL_SPEC + function
            tool_spec = getattr(module, "TOOL_SPEC", None)
            if not tool_spec:
                raise AttributeError(
                    f"Tool {tool_name} missing TOOL_SPEC (neither at module level nor as a decorated function)"
                )

            tool_func_name = tool_name
            if not hasattr(module, tool_func_name):
                raise AttributeError(f"Tool {tool_name} missing function {tool_func_name}")

            tool_func = getattr(module, tool_func_name)
            if not callable(tool_func):
                raise TypeError(f"Tool {tool_name} function is not callable")

            return [PythonAgentTool(tool_name, tool_spec, tool_func)]

        except Exception:
            logger.exception("tool_name=<%s>, sys_path=<%s> | failed to load python tool(s)", tool_name, sys.path)
            raise

    @staticmethod
    def load_python_tool(tool_path: str, tool_name: str) -> AgentTool:
        """DEPRECATED: Load a Python tool module and return a single AgentTool for backwards compatibility.

        Use `load_python_tools` to retrieve all tools defined in a .py file (returns a list).
        This function will emit a `DeprecationWarning` and return the first discovered tool.
        """
        warnings.warn(
            "ToolLoader.load_python_tool is deprecated and will be removed in Strands SDK 2.0. "
            "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
            DeprecationWarning,
            stacklevel=2,
        )

        tools = ToolLoader.load_python_tools(tool_path, tool_name)
        if not tools:
            raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")
        return tools[0]

    @classmethod
    def load_tool(cls, tool_path: str, tool_name: str) -> AgentTool:
        """DEPRECATED: Load a single tool based on its file extension for backwards compatibility.

        Use `load_tools` to retrieve all tools defined in a file (returns a list).
        This function will emit a `DeprecationWarning` and return the first discovered tool.
        """
        warnings.warn(
            "ToolLoader.load_tool is deprecated and will be removed in Strands SDK 2.0. "
            "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
            DeprecationWarning,
            stacklevel=2,
        )

        tools = ToolLoader.load_tools(tool_path, tool_name)
        if not tools:
            raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")

        return tools[0]

    @classmethod
    def load_tools(cls, tool_path: str, tool_name: str) -> list[AgentTool]:
        """DEPRECATED: Load tools from a file based on its file extension.

        Args:
            tool_path: Path to the tool file.
            tool_name: Name of the tool.

        Returns:
            A single Tool instance.

        Raises:
            FileNotFoundError: If the tool file does not exist.
            ValueError: If the tool file has an unsupported extension.
            Exception: For other errors during tool loading.
        """
        warnings.warn(
            "ToolLoader.load_tools is deprecated and will be removed in Strands SDK 2.0. "
            "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        ext = Path(tool_path).suffix.lower()
        abs_path = str(Path(tool_path).resolve())

        if not os.path.exists(abs_path):
            raise FileNotFoundError(f"Tool file not found: {abs_path}")

        try:
            if ext == ".py":
                return cls.load_python_tools(abs_path, tool_name)
            else:
                raise ValueError(f"Unsupported tool file type: {ext}")
        except Exception:
            logger.exception(
                "tool_name=<%s>, tool_path=<%s>, tool_ext=<%s>, cwd=<%s> | failed to load tool",
                tool_name,
                abs_path,
                ext,
                os.getcwd(),
            )
            raise

load_python_tool(tool_path, tool_name) staticmethod

DEPRECATED: Load a Python tool module and return a single AgentTool for backwards compatibility.

Use load_python_tools to retrieve all tools defined in a .py file (returns a list). This function will emit a DeprecationWarning and return the first discovered tool.

Source code in strands/tools/loader.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@staticmethod
def load_python_tool(tool_path: str, tool_name: str) -> AgentTool:
    """DEPRECATED: Load a Python tool module and return a single AgentTool for backwards compatibility.

    Use `load_python_tools` to retrieve all tools defined in a .py file (returns a list).
    This function will emit a `DeprecationWarning` and return the first discovered tool.
    """
    warnings.warn(
        "ToolLoader.load_python_tool is deprecated and will be removed in Strands SDK 2.0. "
        "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
        DeprecationWarning,
        stacklevel=2,
    )

    tools = ToolLoader.load_python_tools(tool_path, tool_name)
    if not tools:
        raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")
    return tools[0]

load_python_tools(tool_path, tool_name) staticmethod

DEPRECATED: Load a Python tool module and return all discovered function-based tools as a list.

This method always returns a list of AgentTool (possibly length 1). It is the canonical API for retrieving multiple tools from a single Python file.

Source code in strands/tools/loader.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
@staticmethod
def load_python_tools(tool_path: str, tool_name: str) -> List[AgentTool]:
    """DEPRECATED: Load a Python tool module and return all discovered function-based tools as a list.

    This method always returns a list of AgentTool (possibly length 1). It is the
    canonical API for retrieving multiple tools from a single Python file.
    """
    warnings.warn(
        "ToolLoader.load_python_tool is deprecated and will be removed in Strands SDK 2.0. "
        "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    try:
        # Support module:function style (e.g. package.module:function)
        if not os.path.exists(tool_path) and ":" in tool_path:
            module_path, function_name = tool_path.rsplit(":", 1)
            logger.debug("tool_name=<%s>, module_path=<%s> | importing tool from path", function_name, module_path)

            try:
                module = __import__(module_path, fromlist=["*"])
            except ImportError as e:
                raise ImportError(f"Failed to import module {module_path}: {str(e)}") from e

            if not hasattr(module, function_name):
                raise AttributeError(f"Module {module_path} has no function named {function_name}")

            func = getattr(module, function_name)
            if isinstance(func, DecoratedFunctionTool):
                logger.debug(
                    "tool_name=<%s>, module_path=<%s> | found function-based tool", function_name, module_path
                )
                return [cast(AgentTool, func)]
            else:
                raise ValueError(
                    f"Function {function_name} in {module_path} is not a valid tool (missing @tool decorator)"
                )

        # Normal file-based tool loading
        abs_path = str(Path(tool_path).resolve())
        logger.debug("tool_path=<%s> | loading python tool from path", abs_path)

        # Load the module by spec
        spec = importlib.util.spec_from_file_location(tool_name, abs_path)
        if not spec:
            raise ImportError(f"Could not create spec for {tool_name}")
        if not spec.loader:
            raise ImportError(f"No loader available for {tool_name}")

        module = importlib.util.module_from_spec(spec)
        sys.modules[f"{_TOOL_MODULE_PREFIX}{tool_name}"] = module
        spec.loader.exec_module(module)

        # Collect function-based tools decorated with @tool
        function_tools: List[AgentTool] = []
        for attr_name in dir(module):
            attr = getattr(module, attr_name)
            if isinstance(attr, DecoratedFunctionTool):
                logger.debug(
                    "tool_name=<%s>, tool_path=<%s> | found function-based tool in path", attr_name, tool_path
                )
                function_tools.append(cast(AgentTool, attr))

        if function_tools:
            return function_tools

        # Fall back to module-level TOOL_SPEC + function
        tool_spec = getattr(module, "TOOL_SPEC", None)
        if not tool_spec:
            raise AttributeError(
                f"Tool {tool_name} missing TOOL_SPEC (neither at module level nor as a decorated function)"
            )

        tool_func_name = tool_name
        if not hasattr(module, tool_func_name):
            raise AttributeError(f"Tool {tool_name} missing function {tool_func_name}")

        tool_func = getattr(module, tool_func_name)
        if not callable(tool_func):
            raise TypeError(f"Tool {tool_name} function is not callable")

        return [PythonAgentTool(tool_name, tool_spec, tool_func)]

    except Exception:
        logger.exception("tool_name=<%s>, sys_path=<%s> | failed to load python tool(s)", tool_name, sys.path)
        raise

load_tool(tool_path, tool_name) classmethod

DEPRECATED: Load a single tool based on its file extension for backwards compatibility.

Use load_tools to retrieve all tools defined in a file (returns a list). This function will emit a DeprecationWarning and return the first discovered tool.

Source code in strands/tools/loader.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
@classmethod
def load_tool(cls, tool_path: str, tool_name: str) -> AgentTool:
    """DEPRECATED: Load a single tool based on its file extension for backwards compatibility.

    Use `load_tools` to retrieve all tools defined in a file (returns a list).
    This function will emit a `DeprecationWarning` and return the first discovered tool.
    """
    warnings.warn(
        "ToolLoader.load_tool is deprecated and will be removed in Strands SDK 2.0. "
        "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
        DeprecationWarning,
        stacklevel=2,
    )

    tools = ToolLoader.load_tools(tool_path, tool_name)
    if not tools:
        raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")

    return tools[0]

load_tools(tool_path, tool_name) classmethod

DEPRECATED: Load tools from a file based on its file extension.

Parameters:

Name Type Description Default
tool_path str

Path to the tool file.

required
tool_name str

Name of the tool.

required

Returns:

Type Description
list[AgentTool]

A single Tool instance.

Raises:

Type Description
FileNotFoundError

If the tool file does not exist.

ValueError

If the tool file has an unsupported extension.

Exception

For other errors during tool loading.

Source code in strands/tools/loader.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
@classmethod
def load_tools(cls, tool_path: str, tool_name: str) -> list[AgentTool]:
    """DEPRECATED: Load tools from a file based on its file extension.

    Args:
        tool_path: Path to the tool file.
        tool_name: Name of the tool.

    Returns:
        A single Tool instance.

    Raises:
        FileNotFoundError: If the tool file does not exist.
        ValueError: If the tool file has an unsupported extension.
        Exception: For other errors during tool loading.
    """
    warnings.warn(
        "ToolLoader.load_tools is deprecated and will be removed in Strands SDK 2.0. "
        "Use the `load_tools_from_string` or `load_tools_from_module` methods instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    ext = Path(tool_path).suffix.lower()
    abs_path = str(Path(tool_path).resolve())

    if not os.path.exists(abs_path):
        raise FileNotFoundError(f"Tool file not found: {abs_path}")

    try:
        if ext == ".py":
            return cls.load_python_tools(abs_path, tool_name)
        else:
            raise ValueError(f"Unsupported tool file type: {ext}")
    except Exception:
        logger.exception(
            "tool_name=<%s>, tool_path=<%s>, tool_ext=<%s>, cwd=<%s> | failed to load tool",
            tool_name,
            abs_path,
            ext,
            os.getcwd(),
        )
        raise

load_tool_from_string(tool_string)

Load tools follows strands supported input string formats.

This function can load a tool based on a string in the following ways: 1. Local file path to a module based tool: ./path/to/module/tool.py 2. Module import path 2.1. Path to a module based tool: strands_tools.file_read 2.2. Path to a module with multiple AgentTool instances (@tool decorated): tests.fixtures.say_tool 2.3. Path to a module and a specific function: tests.fixtures.say_tool:say

Source code in strands/tools/loader.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def load_tool_from_string(tool_string: str) -> List[AgentTool]:
    """Load tools follows strands supported input string formats.

    This function can load a tool based on a string in the following ways:
    1. Local file path to a module based tool: `./path/to/module/tool.py`
    2. Module import path
      2.1. Path to a module based tool: `strands_tools.file_read`
      2.2. Path to a module with multiple AgentTool instances (@tool decorated): `tests.fixtures.say_tool`
      2.3. Path to a module and a specific function: `tests.fixtures.say_tool:say`
    """
    # Case 1: Local file path to a tool
    # Ex: ./path/to/my_cool_tool.py
    tool_path = expanduser(tool_string)
    if os.path.exists(tool_path):
        return load_tools_from_file_path(tool_path)

    # Case 2: Module import path
    # Ex: test.fixtures.say_tool:say (Load specific @tool decorated function)
    # Ex: strands_tools.file_read (Load all @tool decorated functions, or module tool)
    return load_tools_from_module_path(tool_string)

load_tools_from_file_path(tool_path)

Load module from specified path, and then load tools from that module.

This function attempts to load the passed in path as a python module, and if it succeeds, then it tries to import strands tool(s) from that module.

Source code in strands/tools/loader.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def load_tools_from_file_path(tool_path: str) -> List[AgentTool]:
    """Load module from specified path, and then load tools from that module.

    This function attempts to load the passed in path as a python module, and if it succeeds,
    then it tries to import strands tool(s) from that module.
    """
    abs_path = str(Path(tool_path).resolve())
    logger.debug("tool_path=<%s> | loading python tool from path", abs_path)

    # Load the module by spec

    # Using this to determine the module name
    # ./path/to/my_cool_tool.py -> my_cool_tool
    module_name = os.path.basename(tool_path).split(".")[0]

    # This function imports a module based on its path, and gives it the provided name

    spec: ModuleSpec = cast(ModuleSpec, importlib.util.spec_from_file_location(module_name, abs_path))
    if not spec:
        raise ImportError(f"Could not create spec for {module_name}")
    if not spec.loader:
        raise ImportError(f"No loader available for {module_name}")

    module = importlib.util.module_from_spec(spec)
    # Load, or re-load, the module
    sys.modules[f"{_TOOL_MODULE_PREFIX}{module_name}"] = module
    # Execute the module to run any top level code
    spec.loader.exec_module(module)

    return load_tools_from_module(module, module_name)

load_tools_from_module(module, module_name)

Load tools from a module.

First checks if the passed in module has instances of DecoratedToolFunction classes as atributes to the module. If so, then it returns them as a list of tools. If not, then it attempts to load the module as a module based tool.

Source code in strands/tools/loader.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def load_tools_from_module(module: ModuleType, module_name: str) -> list[AgentTool]:
    """Load tools from a module.

    First checks if the passed in module has instances of DecoratedToolFunction classes as atributes to the module.
    If so, then it returns them as a list of tools. If not, then it attempts to load the module as a module based tool.
    """
    logger.debug("tool_name=<%s>, module=<%s> | loading tools from module", module_name, module_name)

    # Try and see if any of the attributes in the module are function-based tools decorated with @tool
    # This means that there may be more than one tool available in this module, so we load them all

    function_tools: List[AgentTool] = []
    # Function tools will appear as attributes in the module
    for attr_name in dir(module):
        attr = getattr(module, attr_name)
        # Check if the module attribute is a DecoratedFunctiontool
        if isinstance(attr, DecoratedFunctionTool):
            logger.debug("tool_name=<%s>, module=<%s> | found function-based tool in module", attr_name, module_name)
            function_tools.append(cast(AgentTool, attr))

    if function_tools:
        return function_tools

    # Finally, if no DecoratedFunctionTools are found in the module, fall back
    # to module based tools, and search for TOOL_SPEC + function
    module_tool_name = module_name
    tool_spec = getattr(module, "TOOL_SPEC", None)
    if not tool_spec:
        raise AttributeError(
            f"The module {module_tool_name} is not a valid module for loading tools."
            "This module must contain @tool decorated function(s), or must be a module based tool."
        )

    # If this is a module based tool, the module should have a function with the same name as the module itself
    if not hasattr(module, module_tool_name):
        raise AttributeError(f"Module-based tool {module_tool_name} missing function {module_tool_name}")

    tool_func = getattr(module, module_tool_name)
    if not callable(tool_func):
        raise TypeError(f"Tool {module_tool_name} function is not callable")

    return [PythonAgentTool(module_tool_name, tool_spec, tool_func)]

load_tools_from_module_path(module_tool_path)

Load strands tool from a module path.

Example module paths: my.module.path my.module.path:tool_name

Source code in strands/tools/loader.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def load_tools_from_module_path(module_tool_path: str) -> list[AgentTool]:
    """Load strands tool from a module path.

    Example module paths:
    my.module.path
    my.module.path:tool_name
    """
    if ":" in module_tool_path:
        module_path, tool_func_name = module_tool_path.split(":")
    else:
        module_path, tool_func_name = (module_tool_path, None)

    try:
        module = importlib.import_module(module_path)
    except ModuleNotFoundError as e:
        raise AttributeError(f'Tool string: "{module_tool_path}" is not a valid tool string.') from e

    # If a ':' is present in the string, then its a targeted function in a module
    if tool_func_name:
        if hasattr(module, tool_func_name):
            target_tool = getattr(module, tool_func_name)
            if isinstance(target_tool, DecoratedFunctionTool):
                return [target_tool]

        raise AttributeError(f"Tool {tool_func_name} not found in module {module_path}")

    # Else, try to import all of the @tool decorated tools, or the module based tool
    module_name = module_path.split(".")[-1]
    return load_tools_from_module(module, module_name)