Skip to content

Update @tool to return an AgentTool that also acts as a function #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 24, 2025

Conversation

zastrowm
Copy link
Member

@zastrowm zastrowm commented Jun 19, 2025

Description

Update tools decorated with @tool to be subclasses of AgentTool instead of callable that look similar to python modules.

This means that the following:

@tool
def my_tool(a_string: str, a_number: number):
   ...

my_tool is actually an implementation of AgentTool (specifically DecoratedFunctionTool).

If you call my_tool() directly it acts just like a function call. But it also has the methods and properties of an AgentTool:

my_tool("a string", 13) # Works
my_tool.tool_spec       # Contains the tool spec of the AgentTool

This would allow us to include a helper utility in the future that act on AgentTool(s) without needing to special case decorated functions.

One such example is a bind utility which would use a constant value for a tool:

new_tool = tool.bind(my_tool, { a_str: "A value" })
new_tool(a_number = 3) # only expects a_number

though this is just an example of how unification benefits

Behavior Change:

We no longer allow passing ToolUse directly into the decorated functions (we do allow this right now for backwards compatibility but emit a warning - in the future we will not allow this):

tool_use = {"toolUseId": "test-id", "input": {"union_param": [1, 2, 3]}}

my_tool(tool_use)        # does not work
my_tool.invoke(tool_use) # does work

This was previously allowed because the tool decorator didn't differentiate between calls that were going to the function and calls that were being done as "tool"; instead this is now implemented via invoke like other AgentTools

Implementation Notes:

  • Typing was a bit harder to implement here to satisfy mypy and the typechecker so in a couple place we ignore errors. The primary problem was around:
    • Implementing DecoratedFunctionTool in a way that typed it as both the original function and an AgentTool
    • Implementing a decorator that was usable as @tool() and @tool but was also typed correctly
    • MyPy seemingly can't detect that DecoratedFunctionTool derives from AgentTool, so I had to add a couple overloads around that
  • I kept FunctionTool around for backwards compatibly with the idea that we'll remove it in the future. This is up for debate, but let me know if you disagree

Related Issues

N/A

Documentation PR

n/A

Type of Change

Refactor

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Right now when you decorate a function you get back another function that mimics the behavior of a python module. Going forward, we're discussing adding helper/utility methods for AgentTool and in anticipatation of those changes return an AgentTool implementation that also acts like the original function.
@zastrowm zastrowm marked this pull request as ready for review June 20, 2025 17:48
pgrayy
pgrayy previously approved these changes Jun 20, 2025
To preserve backwards compatibility, allow passing tool_use into `__call__` for now with a warning on usage.  In a future release, we can remove
Removing the need for an overload
pgrayy
pgrayy previously approved these changes Jun 23, 2025
cagataycali
cagataycali previously approved these changes Jun 24, 2025
@zastrowm zastrowm dismissed stale reviews from cagataycali and pgrayy via ea5d980 June 24, 2025 15:11
@zastrowm zastrowm merged commit df7c327 into strands-agents:main Jun 24, 2025
12 checks passed
@yimuniao
Copy link

This commit caused a problem. Previously, we can add multiple tools with different parameters, but now it can not, it report build error

@zastrowm
Copy link
Member Author

zastrowm commented Jun 30, 2025

@yimuniao thanks for reaching out - do you have an example code of what broke?

@zastrowm
Copy link
Member Author

zastrowm commented Jul 1, 2025

Discussed offline; the crux of the issue was that the inferred types of tools changed

@tool
def example1(value: str): ...

@tool
def example2(value: str): ...

# Previously would have been typed more generically
tools = [example1]
# And this would have worked
tools.extend([example2])

Now, assigning tools requires typing it more generally:

tools: list[AgentTool] =  [example1]
# or
tools: list[any] =  [example1]

This is primarily a problem if someone is using implicitly typed lists and type-checking using mypy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants