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

Allow for introspection and type hints over class attributes when defining tool classes #574

Open
cmungall opened this issue Feb 9, 2025 · 2 comments
Labels
enhancement New feature or request

Comments

@cmungall
Copy link

cmungall commented Feb 9, 2025

Is your feature request related to a problem? Please describe.

I love smolagents, but defining my own tools as classes feels clunky

Currently when using classes over functions, class definitions are not very idiomatic python, e.g.

class HFModelDownloadsTool(Tool):
    name = "model_download_counter"
    description = """
    This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub.
    It returns the name of the checkpoint."""
    inputs = {
        "task": {
            "type": "string",
            "description": "the task category (such as text-classification, depth-estimation, etc)",
        }
    }
    output_type = "string"

    def forward(self, task: str):
        from huggingface_hub import list_models

        model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
        return model.id

all of the 4 required class attributes could instead be introspected from docstrings and type hints (as they are when using tool function decorators).

Additionally, the example is confusing, because it looks like there is some introspection of the forward happening method:

signature = inspect.signature(self.forward)
if not set(signature.parameters.keys()) == set(self.inputs.keys()):
raise Exception(
"Tool's 'forward' method should take 'self' as its first argument, then its next arguments should match the keys of tool attribute 'inputs'."
)
json_schema = _convert_type_hints_to_json_schema(self.forward, error_on_missing_type_hints=False)[
"properties"

but it's not really clear how that relates to the class attributes, if one takes precedence over the other, particularly the (less expressive) type in output_type. It also looks like it's possible to have contradictory type hints and class attributes, and it's not clear what the overall effect is on prompt generation and result interpretation.

Describe the solution you'd like

More idiomatic python would look like:

class HFModelDownloadsTool(Tool):
    """
    This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub.
    It returns the name of the checkpoint."""

    def forward(self, task: str) -> string:
        """
        Args:
          task: the task category (such as text-classification, depth-estimation, etc)
        Returns:
           name of the checkpoint
        """
        from huggingface_hub import list_models

        model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
        return model.id

I could try a PR for this but I don't know where this fits on your roadmap

Is this not possible with the current options.

As an interim step it would be good to have more docs here that explain best practice:

https://huggingface.co/docs/smolagents/tutorials/tools

In particular this isn't clear:

An output_type attribute, which specifies the output type. The types for both inputs and output_type should be Pydantic formats, they can be either of these: ~AUTHORIZED_TYPES().

The pydantic docs are for generating json schema, and it looks like the ~AUTHORIZED_TYPES() is meant to auto-expand.

Describe alternatives you've considered

An alternative would be to encourage use of decorated functions, but to add a context argument, similar to pydantic-ai, which would bypass the current limitations of this (simpler) approach

https://ai.pydantic.dev/#tools-dependency-injection-example

@cmungall cmungall added the enhancement New feature or request label Feb 9, 2025
@sysradium
Copy link
Contributor

I guess in theory this can be done, since there is already a decorator which does it for functions: https://github.com/huggingface/smolagents/blob/main/src/smolagents/tools.py#L848-L848

But maybe it is a bit easier to keep it explicit as it is to store/load them from spaces 🤔

@accupham
Copy link

Am running into this as well. Most (non-trivial) tools have some kind of initialization suited for class instantiation. The tool decorator doesn't seem to handle patching methods into tools yet because the self function signature is hardcoded in the signature inspection logic.

I think the best way to define class tools is like this:

from smolagents import tool

class MyAPICallingTool:
    def __init__(self, api_key: str):
        self.client = Client(key=api_key)

    @tool
    def do_api_thing(self, args: str) -> str:
        """
        Args:
          ...
        Returns:
           ...
        """
        return self.client.something(args)

Pass in the tool as tools=[MyAPICallingTool.do_api_thing] into agents.

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

No branches or pull requests

3 participants