Skip to content
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

Decorator for easier tool building #33439

Merged
merged 11 commits into from
Sep 18, 2024

Conversation

aymeric-roucher
Copy link
Contributor

What does this PR do?

We need an easier way to create tools than the heavy Tool class : but at the same time this class is useful since it's flexible. Se I just add a @tool decorator to make it easy to create a tool from a function, provided that it has the proper type hints and description.

@HuggingFaceDocBuilderDev

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@aymeric-roucher aymeric-roucher changed the title Decorator for tool building Decorator for easier tool building Sep 11, 2024
@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from a1522dd to 86aa070 Compare September 11, 2024 17:30
@aymeric-roucher
Copy link
Contributor Author

cc @Rocketknight1
@LysandreJik I replace all input types "text" with "string" to conform with the common JSON schema type that we also use in apply_chat_template.

@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from 86aa070 to 98f5463 Compare September 11, 2024 17:37
@aymeric-roucher aymeric-roucher marked this pull request as ready for review September 12, 2024 09:02
Copy link
Member

@LysandreJik LysandreJik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌


if is_torch_available():
import torch


class FinalAnswerToolTester(unittest.TestCase, ToolTesterMixin):
class FinalAnswerToolTester(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected to remove the tool tester mixin here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had removed it because FinalAnswerTool does not need the input/output type testing that other tools do need, but indeed we can keep it (and it may be useful if some tests are added to `ToolTes) so I added it back!

@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from 56abb9b to d6f703f Compare September 13, 2024 16:54
@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from 4ade18f to 66d5283 Compare September 16, 2024 10:22
@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from f5e5d0c to 43fdb5f Compare September 16, 2024 12:30
Comment on lines 849 to 880
def tool(tool_function: Callable) -> Tool:
"""
Decorator that turns a function into an instance of a specific Tool subclass

Args:
tool_function: Your function. Should have type hints for each input and a type hint for the output.
Should also have a docstring description including an 'Args:' part where each argument is described.
"""
parameters = get_json_schema(tool_function)["function"]
if "return" not in parameters:
raise TypeHintParsingException("Tool return type not found: make sure your function has a return type hint!")
class_name = f"{parameters['name'].capitalize()}Tool"

class SpecificTool(Tool):
name = parameters["name"]
description = parameters["description"]
inputs = parameters["parameters"]["properties"]
output_type = parameters["return"]["type"]

@wraps(tool_function)
def forward(self, *args, **kwargs):
return tool_function(*args, **kwargs)

original_signature = inspect.signature(tool_function)
new_parameters = [inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD)] + list(
original_signature.parameters.values()
)
new_signature = original_signature.replace(parameters=new_parameters)
SpecificTool.forward.__signature__ = new_signature

SpecificTool.__name__ = class_name
return SpecificTool()
Copy link
Member

@Rocketknight1 Rocketknight1 Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this a little on Slack but I've been thinking about it: Do you think users will get confused because of the different approaches taken in chat templates / agents?

Chat templates: Tools are passed as JSON schema. Users can also directly pass Python functions, and these will be automatically converted to JSON schema.

Agents: Tools are passed as Tool objects. Users can convert functions to Tool objects using the @tool decorator.

Do we need both approaches? We could have a more unified API by, for example, removing the tool decorator and instead converting functions to Tool objects if they're passed to agents, the same way we do in chat templates. That way, even if the internal objects are different, the API for users would be the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking a look @Rocketknight1 !

So it seems to me that a fundamental difference is that in Chat templates, you can pass a JSON scheme : this is completely impossible for our tools since the JSON cannot encode the internal logic of the tool's forward pass.

But we can add the functionality to accept raw functions as tools, and only convert them to tools upon agent initialization! So we would support both Tool objects and passing raw functions.

I think we can keep supporting the decorator as well, since applying a decorator directly to the tool has the advantage to let the users try out the tool before it's passed to the agent initialization (which I personnally often do to make sure they work properly). Anyway this decorator is just the tool function, which we will need in any case for converting functions to Tools.

So I'd say let's keep the usage of tool as a decorator and add support for raw functions on agent initialization (which will also use tool behind the hood for conversion to Tool objects)! Wdyt as well @LysandreJik ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok let's merge this for now, since the solution you propose @Rocketknight1 only needs to add support for raw functions in the tools list upon agent initialization, which we can do in a later PR!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch 2 times, most recently from ab80049 to d42c4e4 Compare September 18, 2024 08:02
@aymeric-roucher aymeric-roucher force-pushed the agents-function-like-tool-building branch from d42c4e4 to 1004967 Compare September 18, 2024 08:06
@aymeric-roucher aymeric-roucher merged commit e6d9f39 into main Sep 18, 2024
24 checks passed
@aymeric-roucher aymeric-roucher deleted the agents-function-like-tool-building branch September 18, 2024 09:07
itazap pushed a commit to NielsRogge/transformers that referenced this pull request Sep 20, 2024
amyeroberts pushed a commit to amyeroberts/transformers that referenced this pull request Oct 2, 2024
BernardZach pushed a commit to BernardZach/transformers that referenced this pull request Dec 5, 2024
BernardZach pushed a commit to innovationcore/transformers that referenced this pull request Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants