File size: 11,966 Bytes
f8d0193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# 动作

动作,也被称为工具,提供了一套LLM驱动的智能体用来与真实世界交互并执行复杂任务的函数。

## 基本概念

### 工具 & 工具包

有两种类型的工具:

- 简单工具: 只提供一个API接口供调用。
- 工具包: 实现多个API接口,承担不同的子任务。

### 工具描述

在Lagent中,工具描述是一个刻画工具调用方式的字典,能够被LLM观察并用于决策。

对于简单工具,描述可按如下格式声明:

```python
TOOL_DESCRIPTION = {
    'name': 'bold',  # 工具名称
    'description': 'a function used to make text bold',  # 介绍工具的功能
    'parameters': [  # 这个工具所需要的参数列表
        {
            'name': 'text', 'type': 'STRING', 'description': 'input content'
        }
    ],
    'required': ['text'],  # 指定必需的参数名
}
```

在某些情况下,可能还包含 `return_data``parameter_description` 字段,分别描述返回内容及参数传递格式。

```{attention}
`parameter_description` 通常被动作的解析器自动插入到工具描述中,这部分将在[接口设计](#id6)中进行介绍。
```

对于工具包,描述非常相似,但嵌套了子方法

```python
TOOL_DESCRIPTION = {
    '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 content'
                }
            ],
            'required': ['text']
        },
        {
            'name': 'italic',
            'description': 'make text italic',
            'parameters': [
                {
                    'name': 'text', 'type': 'STRING', 'description': 'input content'
                }
            ],
            'required': ['text']
        }
    ]
}
```

## 将函数转换为工具

对于已定义好的函数,无需人工添加额外的描述。在 Lagent 中,我们提供了一个修饰器 `tool_api`,它可以通过自动解析函数的类型提示和文档字符串来生成描述字典,并将其绑定到属性 `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']}
```

一旦启用 `returns_named_value`,您应当声明返回值的名称,这将被处理成一个新的字段 `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'}]}
```

有时工具可能返回一个 `dict``tuple`,如果你想在 `return_data` 中详细说明每个成员的含义而不是把它们当作一个整体,设置 `explode_return=True` 并在文档字符串的 Returns 部分中罗列它们。

```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}
目前仅支持 Google 格式的 Python 文档字符串。
```

## 接口设计

`BaseAction(description=None, parser=JsonParser, enable=True)` 是所有动作应该继承的基类,它接收三个初始化参数:

- **description**:一个工具描述的字典,用于设置实例属性 `description`。通常不需要显式地传递这个参数,因为 `BaseAction` 的元类将查找被 `tool_api` 装饰的方法,并组装它们的 `api_description` 构造一个类属性 `__tool_description__`,如果实例化时 `description` 为空,那么该实例属性将置为 `__tool_description__`- **parser**`BaseParser` 类,用于实例化一个动作解析器校验 `description` 所描述的工具的参数。例如,`JsonParser` 会要求模型在调用工具时传入一个 JSON 格式字符串或者 Python 字典,为了让 LLM 感知到该指令,它会在 `description` 中插入一个 `parameter_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**: 指明该动作是否生效。

### 自定义动作

一个简单工具必须实现 `run` 方法,而工具包则应当避免将各子API名称定义为该保留字段。

```{tip}
对于非工具包的 Action,`run` 允许不被 `tool_api` 装饰,除非你想提示返回信息。
```

```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 + '*'

# 查看默认工具描述
# Bold.__tool_description__, PhraseEmphasis.__tool_description__
```

### 自动注册

任何 `BaseAction` 的子类都会自动被注册。你可以使用 `list_tools()``get_tool()` 来查看所有工具类并通过工具名进行初始化。

```python
from lagent import list_tools, get_tool

list_tools()
```

```python
['BaseAction',
 'InvalidAction',
 'NoAction',
 'FinishAction',
 'ArxivSearch',
 'BINGMap',
 'GoogleScholar',
 'GoogleSearch',
 'IPythonInterpreter',
 'PPT',
 'PythonInterpreter',
 'Bold',
 'PhraseEmphasis']
```

创建一个 `PhraseEmphasis` 对象。

```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为参数名称'}]}
```

## 工具调用

### 执行工具

`Action``__call__` 方法需要传入两个参数

- `inputs`: 其类型与动作绑定的 `BaseParser` 相关,通常是由大语言模型生成的字符串。
  - `JsonParser`: 允许传入 JSON 格式字符串或 Python 字典。
  - `TupleParser`: 允许传入字面量为元组的字符串或 Python 元组。
- `name`: 调用哪个 API,默认为 `run`。

工具会返回一个封装了调用细节的 `ActionReturn` 对象。

- `args`: 一个字典,表示该动作的入参。
- `type`: 动作名称。
- `result`: 以字典为成员的列表,每个字典包含两个键——'type' 和 'content',发生异常时该字段为 `None`- `errmsg`: 错误信息,默认为 `None`。

以下是一个例子:

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

### 动态触发

Lagent 提供 `ActionExecutor` 接口管理多个工具,它会将工具包的 `api_list` 平展并将各 API 更名为 `{tool_name}.{api_name}````python
from lagent import ActionExecutor, ArxivSearch, IPythonInterpreter

executor = ActionExecutor(actions=[ArxivSearch(), IPythonInterpreter()])
executor.get_actions_info()  # 该结果会作为LLM系统提示词的一部分
```

```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为参数名称'}]
```

通过动作执行器来触发一个工具

```python
ret = executor('IPythonInterpreter', '{"command": "import math;math.sqrt(100)"}')
ret.result
```

```python
[{'type': 'text', 'content': '10.0'}]
```