Skip to content

feat: add tool decorator#387

Open
akihikokuroda wants to merge 6 commits intogenerative-computing:mainfrom
akihikokuroda:issue382
Open

feat: add tool decorator#387
akihikokuroda wants to merge 6 commits intogenerative-computing:mainfrom
akihikokuroda:issue382

Conversation

@akihikokuroda
Copy link
Member

@akihikokuroda akihikokuroda commented Jan 30, 2026

Misc PR

Type of PR

  • Bug Fix
  • New Feature
  • Documentation
  • Other

Description

Testing

  • Tests added to the respective file if code was changed
  • New code has 100% coverage if code as added
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

@mergify
Copy link

mergify bot commented Jan 30, 2026

Merge Protections

Your pull request matches the following merge protections and will not be merged until they are valid.

🟢 Enforce conventional commit

Wonderful, this rule succeeded.

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert|release)(?:\(.+\))?:

@github-actions
Copy link
Contributor

The PR description has been updated. Please fill out the template for your PR to be reviewed.

Comment on lines 9 to 32
# Example: Define a custom tool using the @tool decorator
@tool
def get_weather(location: str, days: int = 1) -> dict:
"""Get weather forecast for a location.

Args:
location: City name
days: Number of days to forecast (default: 1)
"""
# Mock implementation
return {"location": location, "days": days, "forecast": "sunny", "temperature": 72}


@tool(name="custom_calculator")
def calculate(expression: str) -> float:
"""Evaluate a mathematical expression.

Args:
expression: Mathematical expression to evaluate
"""
# Simple mock - in production, use safe evaluation
return eval(expression)


Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove these tool definitions. I don't think they are used in this file.

Comment on lines 52 to 53
# Before the decorator, you had to do:
# tools = [MelleaTool.from_callable(get_weather), MelleaTool.from_callable(search_web)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe change this comment to Without the decorator, you can add tools using:

Comment on lines 94 to 99
def tool(func: Callable | None = None, *, name: str | None = None) -> Any:
"""Decorator to mark a function as a Mellea tool.

This decorator wraps a function to make it usable as a tool without
requiring explicit MelleaTool.from_callable() calls. The decorated
function can be used both as a normal callable and as a MelleaTool.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not certain about this implementation. I see two main issues:

  1. It diverges from our existing tool implementation. For existing tools, you use .run() to call the underlying function with the appropriate arguments. We should unify this wrapper with that approach. It might require using generics with ParamSpec to get the typing correct for run().
  2. The returned object isn't typed as a Tool by IDEs. I think this confusing because there is no real way to know if what you are passing in is a tool or a regular callable function.

Comment on lines 210 to 212
# Extract MelleaTool from decorated functions
if hasattr(tool_instance, "_mellea_tool"):
tool_instance = tool_instance._mellea_tool
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be able to pass in the decorated function directly since it's typed as a MelleaTool and has all the same properties?

Comment on lines 220 to 222
# Extract MelleaTool from decorated functions
if hasattr(tool_instance, "_mellea_tool"):
tool_instance = tool_instance._mellea_tool
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

@akihikokuroda
Copy link
Member Author

@jakelorocco Thanks for review. I think all comments are addressed.

Comment on lines 102 to 104
def tool(
func: Callable | None = None, *, name: str | None = None
) -> MelleaTool | Callable[[Callable], MelleaTool]:
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't work because of your typing. If you do something like:

@tool
def tooling():
    ...

tooling()

You get type hinting errors on tooling() since it thinks it's a regular MelleaTool. For now, I think we should just return a regular MelleaTool and force it to be called with <tool>.run() like the other tools created from from_callable. If users get annoyed, we can add this functionality here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I made changes. Thanks.

Copy link
Contributor

@jakelorocco jakelorocco left a comment

Choose a reason for hiding this comment

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

Sorry, I think I missed this in the last review or the overloading changed it. I think after this change we should be good. Thank you!

Comment on lines +94 to +95
@overload
def tool(func: Callable) -> MelleaTool: ...
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add , *, name: str | None = None to this function definition so that you can still change the tool name when you call it on a function directly:

def new_tool(): ...

differently_named_tool = tool(new_tool, name="different_name")

Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
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.

feat: add decorators for mellea tools

2 participants