@@ -51,16 +51,17 @@ def my_tool(param1: str, param2: int = 42) -> dict:
51
51
ParamSpec ,
52
52
Type ,
53
53
TypeVar ,
54
+ Union ,
54
55
cast ,
55
56
get_type_hints ,
56
57
overload ,
57
- override ,
58
58
)
59
59
60
60
import docstring_parser
61
61
from pydantic import BaseModel , Field , create_model
62
+ from typing_extensions import override
62
63
63
- from strands .types .tools import AgentTool , ToolResult , ToolSpec , ToolUse
64
+ from strands .types .tools import AgentTool , JSONSchema , ToolResult , ToolSpec , ToolUse
64
65
65
66
# Type for wrapped function
66
67
T = TypeVar ("T" , bound = Callable [..., Any ])
@@ -253,7 +254,7 @@ def validate_input(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
253
254
R = TypeVar ("R" ) # Return type
254
255
255
256
256
- class DecoratedFunctionTool (Generic [P , R ], AgentTool ):
257
+ class DecoratedFunctionTool (AgentTool , Generic [P , R ]):
257
258
"""An AgentTool that wraps a function that was decorated with @tool.
258
259
259
260
This class adapts Python functions decorated with @tool to the AgentTool interface. It handles both direct
@@ -292,7 +293,7 @@ def __init__(
292
293
293
294
functools .update_wrapper (wrapper = self , wrapped = self .original_function )
294
295
295
- def __get__ (self , instance : Any , obj_type = None ) -> "DecoratedFunctionTool[P, R]" :
296
+ def __get__ (self , instance : Any , obj_type : Optional [ Type ] = None ) -> "DecoratedFunctionTool[P, R]" :
296
297
"""Descriptor protocol implementation for proper method binding.
297
298
298
299
This method enables the decorated function to work correctly when used as a class method.
@@ -396,7 +397,8 @@ def invoke(self, tool: ToolUse, *args: Any, **kwargs: dict[str, Any]) -> ToolRes
396
397
if "agent" in kwargs and "agent" in self ._metadata .signature .parameters :
397
398
validated_input ["agent" ] = kwargs .get ("agent" )
398
399
399
- result = self .original_function (** validated_input )
400
+ # We get "too few arguments here" but because that's because fof the way we're calling it
401
+ result = self .original_function (** validated_input ) # type: ignore
400
402
401
403
# FORMAT THE RESULT for Strands Agent
402
404
if isinstance (result , dict ) and "status" in result and "content" in result :
@@ -456,8 +458,19 @@ def get_display_properties(self) -> dict[str, str]:
456
458
def tool (__func : Callable [P , R ]) -> DecoratedFunctionTool [P , R ]: ...
457
459
# Handle @decorator()
458
460
@overload
459
- def tool (** tool_kwargs : Any ) -> Callable [[Callable [P , R ]], DecoratedFunctionTool [P , R ]]: ...
460
- def tool (func : Optional [Callable [P , R ]] = None , ** tool_kwargs : Any ) -> DecoratedFunctionTool [P , R ]:
461
+ def tool (
462
+ description : Optional [str ] = None ,
463
+ inputSchema : Optional [JSONSchema ] = None ,
464
+ name : Optional [str ] = None ,
465
+ ) -> Callable [[Callable [P , R ]], DecoratedFunctionTool [P , R ]]: ...
466
+ # Suppressing the type error because we want callers to be able to use both `tool` and `tool()` at the
467
+ # call site, but the actual implementation handles that and it's not representable via the type-system
468
+ def tool ( # type: ignore
469
+ func : Optional [Callable [P , R ]] = None ,
470
+ description : Optional [str ] = None ,
471
+ inputSchema : Optional [JSONSchema ] = None ,
472
+ name : Optional [str ] = None ,
473
+ ) -> Union [DecoratedFunctionTool [P , R ], Callable [[Callable [P , R ]], DecoratedFunctionTool [P , R ]]]:
461
474
"""Decorator that transforms a Python function into a Strands tool.
462
475
463
476
This decorator seamlessly enables a function to be called both as a regular Python function and as a Strands tool.
@@ -472,25 +485,31 @@ def tool(func: Optional[Callable[P, R]] = None, **tool_kwargs: Any) -> Decorated
472
485
4. Formats return values according to the expected Strands tool result format
473
486
5. Provides automatic error handling and reporting
474
487
488
+ The decorator can be used in two ways:
489
+ - As a simple decorator: `@tool`
490
+ - With parameters: `@tool(name="custom_name", description="Custom description")`
491
+
475
492
Args:
476
- func: The function to decorate.
477
- **tool_kwargs: Additional tool specification options to override extracted values.
478
- E.g., `name="custom_name", description="Custom description"`.
493
+ func: The function to decorate. When used as a simple decorator, this is the function being decorated.
494
+ When used with parameters, this will be None.
495
+ description: Optional custom description to override the function's docstring.
496
+ inputSchema: Optional custom JSON schema to override the automatically generated schema.
497
+ name: Optional custom name to override the function's name.
479
498
480
499
Returns:
481
- The decorated function with attached tool specifications.
500
+ An AgentTool that also mimics the original function when invoked
482
501
483
502
Example:
484
503
```python
485
504
@tool
486
505
def my_tool(name: str, count: int = 1) -> str:
487
506
'''Does something useful with the provided parameters.
488
507
489
- " Args:
508
+ Args:
490
509
name: The name to process
491
510
count: Number of times to process (default: 1)
492
511
493
- " Returns:
512
+ Returns:
494
513
A message with the result
495
514
'''
496
515
return f"Processed {name} {count} times"
@@ -503,13 +522,25 @@ def my_tool(name: str, count: int = 1) -> str:
503
522
# "content": [{"text": "Processed example 3 times"}]
504
523
# }
505
524
```
525
+
526
+ Example with parameters:
527
+ ```python
528
+ @tool(name="custom_tool", description="A tool with a custom name and description")
529
+ def my_tool(name: str, count: int = 1) -> str:
530
+ return f"Processed {name} {count} times"
531
+ ```
506
532
"""
507
533
508
534
def decorator (f : T ) -> "DecoratedFunctionTool[P, R]" :
509
535
# Create function tool metadata
510
536
tool_meta = FunctionToolMetadata (f )
511
537
tool_spec = tool_meta .extract_metadata ()
512
- tool_spec .update (tool_kwargs )
538
+ if name is not None :
539
+ tool_spec ["name" ] = name
540
+ if description is not None :
541
+ tool_spec ["description" ] = description
542
+ if inputSchema is not None :
543
+ tool_spec ["inputSchema" ] = inputSchema
513
544
514
545
tool_name = tool_spec .get ("name" , f .__name__ )
515
546
@@ -520,6 +551,8 @@ def decorator(f: T) -> "DecoratedFunctionTool[P, R]":
520
551
521
552
# Handle both @tool and @tool() syntax
522
553
if func is None :
554
+ # Need to ignore type-checking here since it's hard to represent the support
555
+ # for both flows using the type system
523
556
return decorator
524
557
525
558
return decorator (func )
0 commit comments