| # Action | |
| Actions, also called **tools**, provide a suite of functions LLM-driven agents can use to interact with the real world and perform complex tasks. | |
| ## Basic Concepts | |
| ### Tool & Toolkit | |
| There are two categories of tools: | |
| - tool: provide only one API to call. | |
| - toolkit: implement multiple APIs that undertake different sub-tasks. | |
| ### Tool Description | |
| In Lagent, the tool description is a dictionary containing the action's core information of usage, observed by LLMs for decision-making. | |
| For simple tools, the description can be created as follows | |
| ```python | |
| TOOL_DESCRIPTION = { | |
| 'name': 'bold', # name of the tool | |
| 'description': 'a function used to make text bold', # introduce the tool's function | |
| 'parameters': [ # a list of parameters the tool take. | |
| { | |
| 'name': 'text', 'type': 'STRING', 'description': 'input content' | |
| } | |
| ], | |
| 'required': ['text'], # specify names of parameters required | |
| } | |
| ``` | |
| In some situations there may be optional `return_data`, `parameter_description` keys describing the returns and argument passing format respectively. | |
| ```{attention} | |
| `parameter_description` is usually inserted into the tool description automatically by the action's parser. It will be introduced in [Interface Design](#interface-design) . | |
| ``` | |
| For toolkits, the description is very similar but nest submethods | |
| ```python | |
| TOOL_DESCRIPTION = { | |
| 'name': 'PhraseEmphasis', # name of the toolkit | |
| 'description': 'a toolkit which provides different styles of text emphasis', # introduce the tool's function | |
| 'api_list': [ | |
| { | |
| 'name': 'bold', | |
| 'description': 'make text bold', | |
| 'parameters': [ | |
| { | |
| 'name': 'text', 'type': 'STRING', 'description': 'input content' | |
| } | |
| ], | |
| 'required': ['text'] | |
| }, | |
| { | |
| 'name': 'italic', | |
| 'description': 'make text italic', | |
| 'parameters': [ | |
| { | |
| 'name': 'text', 'type': 'STRING', 'description': 'input content' | |
| } | |
| ], | |
| 'required': ['text'] | |
| } | |
| ] | |
| } | |
| ``` | |
| ## Make Functions Tools | |
| It's not necessary to prepare an extra description for a defined function. In Lagent we provide a decorator `tool_api` which can conveniently turn a function into a tool by automatically parsing the function's typehints and dosctrings to generate the description dictionary and binding it to an attribute `api_description`. | |
| ```python | |
| from lagent import tool_api | |
| @tool_api | |
| def bold(text: str) -> str: | |
| """make text bold | |
| Args: | |
| text (str): input text | |
| Returns: | |
| str: bold text | |
| """ | |
| return '**' + text + '**' | |
| bold.api_description | |
| ``` | |
| ```python | |
| {'name': 'bold', | |
| 'description': 'make text bold', | |
| 'parameters': [{'name': 'text', | |
| 'type': 'STRING', | |
| 'description': 'input text'}], | |
| 'required': ['text']} | |
| ``` | |
| Once `returns_named_value` is enabled you should declare the name of the return data, which will be processed to form a new field `return_data`: | |
| ```python | |
| @tool_api(returns_named_value=True) | |
| def bold(text: str) -> str: | |
| """make text bold | |
| Args: | |
| text (str): input text | |
| Returns: | |
| bold_text (str): bold text | |
| """ | |
| return '**' + text + '**' | |
| bold.api_description | |
| ``` | |
| ```python | |
| {'name': 'bold', | |
| 'description': 'make text bold', | |
| 'parameters': [{'name': 'text', | |
| 'type': 'STRING', | |
| 'description': 'input text'}], | |
| 'required': ['text'], | |
| 'return_data': [{'name': 'bold_text', | |
| 'description': 'bold text', | |
| 'type': 'STRING'}]} | |
| ``` | |
| Sometimes the tool may return a `dict` or `tuple`, and you want to elaborate each member in `return_data` rather than take them as a whole. Set `explode_return=True` and list them in the return part of docstrings. | |
| ```python | |
| @tool_api(explode_return=True) | |
| def list_args(a: str, b: int, c: float = 0.0) -> dict: | |
| """Return arguments in dict format | |
| Args: | |
| a (str): a | |
| b (int): b | |
| c (float): c | |
| Returns: | |
| dict: input arguments | |
| - a (str): a | |
| - b (int): b | |
| - c: c | |
| """ | |
| return {'a': a, 'b': b, 'c': c} | |
| ``` | |
| ```python | |
| {'name': 'list_args', | |
| 'description': 'Return arguments in dict format', | |
| 'parameters': [{'name': 'a', 'type': 'STRING', 'description': 'a'}, | |
| {'name': 'b', 'type': 'NUMBER', 'description': 'b'}, | |
| {'name': 'c', 'type': 'FLOAT', 'description': 'c'}], | |
| 'required': ['a', 'b'], | |
| 'return_data': [{'name': 'a', 'description': 'a', 'type': 'STRING'}, | |
| {'name': 'b', 'description': 'b', 'type': 'NUMBER'}, | |
| {'name': 'c', 'description': 'c'}]} | |
| ``` | |
| ```{warning} | |
| Only Google style Python docstrings is currently supported. | |
| ``` | |
| ## Interface Design | |
| `BaseAction(description=None, parser=JsonParser, enable=True)` is the base class all actions should inherit from. It takes three initialization arguments | |
| - **description**: a tool description dictionary, used set instance attribute `description`. Mostly you don't need explicitly pass this argument since the meta class of `BaseAction` will search methods decorated by `tool_api` and assemble their `api_description` as a class attribute `__tool_description__`, and if the initial `description` is left null, then `__tool_description__` will be copied as `description`. | |
| - **parser**: `BaseParser` class. It will instantialize a parser used to validate the arguments of APIs in `description`. | |
| For example, `JsonParser` requires arguments passed in the format of JSON or `dict`. To make LLMs aware of this, It inserts a field `parameter_description` into the `description`. | |
| ```python | |
| from lagent import BaseAction | |
| action = BaseAction( | |
| { | |
| 'name': 'bold', | |
| 'description': 'a function used to make text bold', | |
| 'parameters': [ | |
| { | |
| 'name': 'text', 'type': 'STRING', 'description': 'input content' | |
| } | |
| ], | |
| 'required': ['text'] | |
| } | |
| ) | |
| action.description | |
| ``` | |
| ```python | |
| {'name': 'bold', | |
| 'description': 'a function used to make text bold', | |
| 'parameters': [{'name': 'text', | |
| 'type': 'STRING', | |
| 'description': 'input content'}], | |
| 'required': ['text'], | |
| 'parameter_description': '如果调用该工具,你必须使用Json格式 {key: value} 传参,其中key为参数名称'} | |
| ``` | |
| - **enable**: specify whether the tool is available. | |
| ### Custom Action | |
| A simple tool must have its `run` method implemented, while APIs of toolkits should avoid naming conflicts with this reserved word. | |
| ```{tip} | |
| `run` is allowed not to be decorated by `tool_api` for simple tools unless you want to hint the return data. | |
| ``` | |
| ```python | |
| class Bold(BaseAction): | |
| def run(self, text: str): | |
| """make text bold | |
| Args: | |
| text (str): input text | |
| Returns: | |
| str: bold text | |
| """ | |
| return '**' + text + '**' | |
| class PhraseEmphasis(BaseAction): | |
| """a toolkit which provides different styles of text emphasis""" | |
| @tool_api | |
| def bold(self, text): | |
| """make text bold | |
| Args: | |
| text (str): input text | |
| Returns: | |
| str: bold text | |
| """ | |
| return '**' + text + '**' | |
| @tool_api | |
| def italic(self, text): | |
| """make text italic | |
| Args: | |
| text (str): input text | |
| Returns: | |
| str: italic text | |
| """ | |
| return '*' + text + '*' | |
| # Inspect the default description | |
| # Bold.__tool_description__, PhraseEmphasis.__tool_description__ | |
| ``` | |
| ### Auto-registration | |
| Any subclass of `BaseAction` will be registered automatically. You can use `list_tools()` and `get_tool()` to view all tools and initialize by name. | |
| ```python | |
| from lagent import list_tools, get_tool | |
| list_tools() | |
| ``` | |
| ```python | |
| ['BaseAction', | |
| 'InvalidAction', | |
| 'NoAction', | |
| 'FinishAction', | |
| 'ArxivSearch', | |
| 'BINGMap', | |
| 'GoogleScholar', | |
| 'GoogleSearch', | |
| 'IPythonInterpreter', | |
| 'PPT', | |
| 'PythonInterpreter', | |
| 'Bold', | |
| 'PhraseEmphasis'] | |
| ``` | |
| Create a `PhraseEmphasis` object | |
| ```python | |
| action = get_tool('PhraseEmphasis') | |
| action.description | |
| ``` | |
| ```python | |
| {'name': 'PhraseEmphasis', | |
| 'description': 'a toolkit which provides different styles of text emphasis', | |
| 'api_list': [{'name': 'bold', | |
| 'description': 'make text bold', | |
| 'parameters': [{'name': 'text', | |
| 'type': 'STRING', | |
| 'description': 'input text'}], | |
| 'required': ['text'], | |
| 'parameter_description': '如果调用该工具,你必须使用Json格式 {key: value} 传参,其中key为参数名称'}, | |
| {'name': 'italic', | |
| 'description': 'make text italic', | |
| 'parameters': [{'name': 'text', | |
| 'type': 'STRING', | |
| 'description': 'input text'}], | |
| 'required': ['text'], | |
| 'parameter_description': '如果调用该工具,你必须使用Json格式 {key: value} 传参,其中key为参数名称'}]} | |
| ``` | |
| ## Tool Calling | |
| ### Run a Tool | |
| `__call__` method of `Action` takes two arguments | |
| - `inputs`: It depends on the action's parser. Often a string in specific formats generated by LLMs. | |
| - `JsonParser`: Allow passing arguments in the format of JSON string or Python `dict`. | |
| - `TupleParser`: Allow passing arguments in the format of tuple string format or Python `tuple`. | |
| - `name`: Which API to call. Default is `run`. | |
| It returns an `ActionReturn` object which encapsulates calling details | |
| - `args`: Dictionary of action inputs. | |
| - `type`: Action name. | |
| - `result`: List of dicts. Each contains two keys: 'type' and 'content'. when errors occur, it is `None`. | |
| - `errmsg`: Error message. Default is `None`. | |
| Below is an example | |
| ```python | |
| from lagent import IPythonInterpreter, TupleParser | |
| action1 = IPythonInterpreter() | |
| ret = action1('{"command": "import math;math.sqrt(100)"}') | |
| print(ret.result) | |
| ret = action1({'command': 'import math;math.sqrt(100)'}) | |
| print(ret.result) | |
| action2 = IPythonInterpreter(parser=TupleParser) | |
| ret = action2('("import math;math.sqrt(100)", )') | |
| print(ret.result) | |
| ret = action2(('import math;math.sqrt(100)',)) | |
| print(ret.result) | |
| ``` | |
| ```python | |
| [{'type': 'text', 'content': '10.0'}] | |
| [{'type': 'text', 'content': '10.0'}] | |
| [{'type': 'text', 'content': '10.0'}] | |
| [{'type': 'text', 'content': '10.0'}] | |
| ``` | |
| ### Dynamic Invocation | |
| Lagent provides an `ActionExecutor` to manage multiple tools. It will flatten `api_list` of toolkits and rename each `{tool_name}.{api_name}`. | |
| ```python | |
| from lagent import ActionExecutor, ArxivSearch, IPythonInterpreter | |
| executor = ActionExecutor(actions=[ArxivSearch(), IPythonInterpreter()]) | |
| executor.get_actions_info() # This information is fed to LLMs as the tool meta prompt | |
| ``` | |
| ```python | |
| [{'name': 'ArxivSearch.get_arxiv_article_information', | |
| 'description': 'Run Arxiv search and get the article meta information.', | |
| 'parameters': [{'name': 'query', | |
| 'type': 'STRING', | |
| 'description': 'the content of search query'}], | |
| 'required': ['query'], | |
| 'return_data': [{'name': 'content', | |
| 'description': 'a list of 3 arxiv search papers', | |
| 'type': 'STRING'}], | |
| 'parameter_description': '如果调用该工具,你必须使用Json格式 {key: value} 传参,其中key为参数名称'}, | |
| {'name': 'IPythonInterpreter', | |
| 'description': "When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 60.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is disabled. Do not make external web requests or API calls as they will fail.", | |
| 'parameters': [{'name': 'command', | |
| 'type': 'STRING', | |
| 'description': 'Python code'}, | |
| {'name': 'timeout', | |
| 'type': 'NUMBER', | |
| 'description': 'Upper bound of waiting time for Python script execution.'}], | |
| 'required': ['command'], | |
| 'parameter_description': '如果调用该工具,你必须使用Json格式 {key: value} 传参,其中key为参数名称'}] | |
| ``` | |
| Trigger an action through the executor | |
| ```python | |
| ret = executor('IPythonInterpreter', '{"command": "import math;math.sqrt(100)"}') | |
| ret.result | |
| ``` | |
| ```python | |
| [{'type': 'text', 'content': '10.0'}] | |
| ``` | |