MetaGPT icon indicating copy to clipboard operation
MetaGPT copied to clipboard

Can I register my own tool, using @register_tool? I think so, but it is not working.

Open krish240574 opened this issue 1 year ago • 13 comments

Bug description I've defined my own class, and registered it as a tool, using the @register_tool decorator. However, di does not use the tool.

Auxillary information I've even tried to tell metagpt to use the particular tool, by name in the prompt, still it does not see it.

Environment information

  • LLM type and model name: got-4-turbo-preview
  • System version: Ubuntu 20.04
  • Python version: 3.10
  • packages version:
  • installation method: pip install metagpt

Screenshots or logs

krish240574 avatar Mar 19 '24 03:03 krish240574

There are some conditions need to met before di using the tool:

  1. Proper comments for the tool. Here is a demo:
    async def create_repo(self, idea:str) -> Path:
        """
        Creates a new software repository with user requirements.

        Args:
            idea (str): User requirement.

        Returns:
            Path: The path to the newly created software repository.


        Example:
            To create a software repository for "snake game":

            ```python
            todo = PrepareDocuments()
            repo_path = await todo.create_repo("snake game")
            print("Repository created at:", repo_path)
            ```
        """
        self._override_context()
        await self.run(with_messages=[Message(content=idea)])
        return self.context.repo.workdir

The google docstring is used as the tool description during the di searches for a tool.

  1. The task description needs to match the tool description. Here is a demo:
import fire

from metagpt.roles.di.data_interpreter import DataInterpreter


async def main():
    prompt = f"""This is a software requirement:\n```text\nwrite a snake game\n```\n .
        1. Create a software repository.\n
        2. Write a PRD.\n
        3. Write a design.\n
        Note: All required dependencies and environments have been fully installed and configured."""
    di = DataInterpreter(tools=["GPTvGenerator", "PrepareDocuments", "WritePRD", "WriteDesign"])

    await di.run(prompt)


if __name__ == "__main__":
    fire.Fire(main)

Currently, the tool search ability is not perfect, if you want to test the process, you can specify the description of the tool to use in the prompt

iorisa avatar Mar 19 '24 04:03 iorisa

@iorisa Thank you very much for your response. I'm guessing the run() call is local to your code, not absolutely necessary(not all examples implement the run()) - do correct me if I'm wrong. I've added the docstring, and made sure the task description includes a clear indication about the tool and the method to be used. I've indicated tools=["toolname"] , in the call to DataInterpreter()- my specific toolname. Still no luck, I can't get it to see the tool at all.

krish240574 avatar Mar 19 '24 04:03 krish240574

metagpt/tools/tool_recommend.py line 95, set a breakpoint and see: 截屏2024-03-19 16 59 10

iorisa avatar Mar 19 '24 09:03 iorisa

register_tool should be called at initialization. You can add your tool api into metagpt/tools/libs/__init__.py

iorisa avatar Mar 19 '24 09:03 iorisa

@iorisa Thank you for the detailed response. From your messages, I'm understanding that each tool I create would need to go in as a patch to the source code and that there is no general purpose interface to add my tool , correct? Your suggestion to add to __init__.py seems to be alluding to that.

For now, I can work with that, but I'm hoping each time I do a pip install --upgrade metagpt, the patches don't get clobbered, which they will, I guess.

Maybe implementing this as a general purpose interface to register my own tool would be the way to go.

krish240574 avatar Mar 19 '24 11:03 krish240574

  1. You can register your tool api through TOOL_REGISTRY.register_tool.
def register_tool(tags: list[str] = None, schema_path: str = "", **kwargs):
    """register a tool to registry"""

    def decorator(cls):
        # Get the file path where the function / class is defined and the source code
        file_path = inspect.getfile(cls)
        if "metagpt" in file_path:
            # split to handle ../metagpt/metagpt/tools/... where only metapgt/tools/... is needed
            file_path = "metagpt" + file_path.split("metagpt")[-1]
        source_code = inspect.getsource(cls)

        TOOL_REGISTRY.register_tool(
            tool_name=cls.__name__,
            tool_path=file_path,
            schema_path=schema_path,
            tool_code=source_code,
            tags=tags,
            tool_source_object=cls,
            **kwargs,
        )
        return cls

    return decorator
  1. About pip install --upgrade metagpt: You can create a new project to solve your business problem, and put all your tool apis within it. Then using metaGPT through import. In this way, the upgrade of MetaGPT will not affect existing codes.

iorisa avatar Mar 19 '24 13:03 iorisa

Hi, a very good question. In fact, you can register any tool any where in your local file. Say you have a function or class in a.py which you want to use as a tool. You can register it as follow

from metagpt.tools.tool_registry import register_tool

@register_tool()
def your_function(arg1: str, arg2: int) -> dict:
    """
    Some docstring, recommended to be Google-style

    Args:
        arg1 (str): ...
        arg2 (int): ...

    Returns:
        dict: ...
    """

Then, in a script to use DataInterpreter, say b.py, you can make DataInterpreter to use your tool

import a  # this is to make sure the tool is registered before running DataInterpreter
...

di = DataInterpreter(tools=["your_function"])
...

Additional references potentially helpful: https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/machine_learning_with_tools.html#mechanism

garylin2099 avatar Mar 21 '24 03:03 garylin2099

@garylin2099 - thank you very much for the detailed message. I've done exactly what you said, still, no luck. The tool just doesn't get registered. I'll check again, in great detail to see if I've missed anything. Cheers.

krish240574 avatar Mar 22 '24 01:03 krish240574

@krish240574 @garylin2099 I solidified this example into example and the test passed

geekan avatar Mar 22 '24 03:03 geekan

I've tried the exact code, that your example has, from custom_tool.py and here is the output : (the tool does not get registered) What am I missing? magic_function is defined as follows, in custom_tool.py : @register_tool() def magic_function(arg1: str, arg2: int) -> dict: """ The magic function that does something. Args: arg1 (str): ... arg2 (int): ... Returns: dict: ... """ return {"arg1": arg1 * 3, "arg2": arg2 * 5}

====================OUTPUT=====================

[
    {
        "task_id": "1",
        "dependent_task_ids": [],
        "instruction": "Call the magic function with arg1 set to 'A' and arg2 set to 2."
    },
    {
        "task_id": "2",
        "dependent_task_ids": ["1"],
        "instruction": "Report the result of the function call."
    }
]

2024-03-22 05:21:09.385 | INFO | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.005 | Max budget: $10.000 | Current cost: $0.005, prompt_tokens: 244, completion_tokens: 84 2024-03-22 05:21:09.386 | INFO | metagpt.roles.role:_plan_and_act:494 - ready to take on task task_id='1' dependent_task_ids=[] instruction="Call the magic function with arg1 set to 'A' and arg2 set to 2." task_type='' code='' result='' is_success=False is_finished=False 2024-03-22 05:21:09.386 | INFO | metagpt.roles.di.data_interpreter:_write_code:79 - ready to WriteCodeWithoutTools 2024-03-22 05:21:12.723 | INFO | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.006 | Max budget: $10.000 | Current cost: $0.006, prompt_tokens: 471, completion_tokens: 47 1 def magic_function(arg1, arg2):
2 return arg1 * arg2
3
4 result = magic_function('A', 2)
5 result
'AA' 2024-03-22 05:21:13.687 | INFO | metagpt.roles.role:_plan_and_act:494 - ready to take on task task_id='2' dependent_task_ids=['1'] instruction='Report the result of the function call.' task_type='' code='' result='' is_success=False is_finished=False 2024-03-22 05:21:13.687 | INFO | metagpt.roles.di.data_interpreter:_write_code:79 - ready to WriteCodeWithoutTools 2024-03-22 05:21:14.998 | INFO | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.005 | Max budget: $10.000 | Current cost: $0.005, prompt_tokens: 494, completion_tokens: 11

The result of the function call is 'AA'.

The result of the function call is 'AA'.

krish240574 avatar Mar 22 '24 05:03 krish240574

After re-reading the issue, I understand. Our data interpreter has not been officially released to pypi. Currently, these work are in the main branch. You need to install the main branch.

  1. Install main branch: git clone https://github.com/geekan/MetaGPT && cd MetaGPT && pip install --upgrade -e .
  2. Execute the example: python examples/di/custom_tool.py

geekan avatar Mar 22 '24 05:03 geekan

Ahh, awesome! Let me try that out and get back to you.

krish240574 avatar Mar 23 '24 03:03 krish240574

This run looks really weird, after a tool is registered, why is the execution proceeding by generating code, to use the tool? SHould the agent not use the tool directly? This approach leads to so many errors and retries :

===================== Start run ================================ python custom_tool.py 2024-04-05 17:20:22.611 | INFO | metagpt.const:get_metagpt_package_root:29 - Package root set to /media/krishna/K/smith/MetaGPT Building prefix dict from the default dictionary ... Loading model from cache /tmp/jieba.cache Loading model cost 0.647 seconds. Prefix dict has been built successfully.

[
    {
        "task_id": "1",
        "dependent_task_ids": [],
        "instruction": "Call the magic function with arg1 'A' and arg2 2",
        "task_type": "other"
    }
]

2024-04-05 17:20:29.237 | INFO | metagpt.utils.cost_manager:update_cost:57 - Total running cost: $0.006 | Max budget: $10.000 | Current cost: $0.006, prompt_tokens: 412, completion_tokens: 54 2024-04-05 17:20:29.238 | INFO | metagpt.roles.role:_plan_and_act:486 - ready to take on task task_id='1' dependent_task_ids=[] instruction="Call the magic function with arg1 'A' and arg2 2" task_type='other' code='' result='' is_success=False is_finished=False 2024-04-05 17:20:29.238 | INFO | metagpt.tools.tool_recommend:recall_tools:195 - Recalled tools: ['magic_function']; Scores: [-5.988676009656986]

["magic_function"]

2024-04-05 17:20:30.454 | INFO | metagpt.utils.cost_manager:update_cost:57 - Total running cost: $0.002 | Max budget: $10.000 | Current cost: $0.002, prompt_tokens: 168, completion_tokens: 8 2024-04-05 17:20:30.455 | INFO | metagpt.tools.tool_recommend:recommend_tools:101 - Recommended tools: ['magic_function'] 2024-04-05 17:20:30.456 | INFO | metagpt.roles.di.data_interpreter:_write_code:153 - ready to WriteAnalysisCode

from /media/krishna/K/smith/MetaGPT/examples/di/custom_tool.py import magic_function

result = magic_function('A', 2)
print(result)

2024-04-05 17:20:32.381 | INFO | metagpt.utils.cost_manager:update_cost:57 - Total running cost: $0.006 | Max budget: $10.000 | Current cost: $0.006, prompt_tokens: 431, completion_tokens: 41 1 from /media/krishna/K/smith/MetaGPT/examples/di/custom_tool.py import magic_function
2
3 result = magic_function('A', 2)
4 print(result)
5
Cell In[1], line 1 from /media/krishna/K/smith/MetaGPT/examples/di/custom_tool.py import magic_function ^ SyntaxError: invalid syntax

2024-04-05 17:20:34.316 | INFO | metagpt.roles.di.data_interpreter:_write_code:153 - ready to WriteAnalysisCode

from media.krishna.K.smith.MetaGPT.examples.di.custom_tool import magic_function

result = magic_function('A', 2)
print(result)

2024-04-05 17:20:36.477 | INFO | metagpt.utils.cost_manager:update_cost:57 - Total running cost: $0.012 | Max budget: $10.000 | Current cost: $0.006, prompt_tokens: 519, completion_tokens: 36 1 from media.krishna.K.smith.MetaGPT.examples.di.custom_tool import magic_function
2
3 result = magic_function('A', 2)
4 print(result)
5

ModuleNotFoundError Traceback (most recent call last) Cell In[2], line 1 ----> 1 from media.krishna.K.smith.MetaGPT.examples.di.custom_tool import magic_function 3 result = magic_function('A', 2) 4 print(result)

ModuleNotFoundError: No module named 'media' 2024-04-05 17:20:37.498 | INFO | metagpt.roles.di.data_interpreter:_write_code:153 - ready to WriteAnalysisCode

import sys
sys.path.append('/media/krishna/K/smith/MetaGPT/examples/di')
from custom_tool import magic_function

result = magic_function('A', 2)
print(result)

2024-04-05 17:20:40.661 | INFO | metagpt.utils.cost_manager:update_cost:57 - Total running cost: $0.020 | Max budget: $10.000 | Current cost: $0.008, prompt_tokens: 632, completion_tokens: 47 1 import sys
2 sys.path.append('/media/krishna/K/smith/MetaGPT/examples/di')
3 from custom_tool import magic_function
4
5 result = magic_function('A', 2)
6 print(result)
7
,{'arg1': 'AAA', 'arg2': 10}

krish240574 avatar Apr 05 '24 11:04 krish240574