File size: 12,144 Bytes
e3d4122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# 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'}]
```