|
# 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'}] |
|
``` |
|
|