Skip to main content

工具使用

在 Colab 中打开 在 GitHub 上打开

在前一章中,我们探索了代码执行器,它赋予了代理程序编程的超能力。代理程序编写任意代码是有用的,然而,控制代理程序编写的代码可以是具有挑战性的。这就是工具的作用。

工具是预定义的函数,代理程序可以使用它们。代理程序可以调用工具来执行操作,例如搜索网络、进行计算、读取文件或调用远程 API,而不是编写任意代码。因为您可以控制哪些工具对代理程序可用,所以您可以控制代理程序可以执行的操作。

note

目前只有支持 OpenAI 兼容工具调用 API 的 LLMs 可以使用工具。

创建工具

工具可以像普通的 Python 函数一样创建。例如,让我们创建一个计算器工具,它一次只能执行一个操作。

from typing import Annotated, Literal

Operator = Literal["+", "-", "*", "/"]


def calculator(a: int, b: int, operator: Annotated[Operator, "operator"]) -> int:
if operator == "+":
return a + b
elif operator == "-":
return a - b
elif operator == "*":
return a * b
elif operator == "/":
return int(a / b)
else:
raise ValueError("无效的操作符")

上面的函数接受三个参数:ab 是要进行操作的整数;operator 是要执行的操作。我们使用类型提示来定义参数和返回值的类型。

tip

始终使用类型提示来定义参数和返回值的类型,因为它们为代理程序提供了关于工具使用的有用提示。

注册工具

一旦您创建了一个工具,您可以将其注册到参与对话的代理程序中。

import os

from autogen import ConversableAgent

# 首先定义一个助手代理,用于提供工具调用建议。
assistant = ConversableAgent(
name="助手",
system_message="你是一个有用的AI助手。你可以帮助进行简单的计算。当任务完成时,请返回'TERMINATE'。",
llm_config={"config_list": [{"model": "gpt-4", "api_key": os.environ["OPENAI_API_KEY"]}]},
)

# 用户代理用于与助手代理进行交互并执行工具调用。
user_proxy = ConversableAgent(
name="用户",
llm_config=False,
is_termination_msg=lambda msg: msg.get("content") is not None and "TERMINATE" in msg["content"],
human_input_mode="NEVER",
)

# 使用助手代理注册工具签名。
assistant.register_for_llm(name="calculator", description="一个简单的计算器")(calculator)

# 使用用户代理注册工具函数。
user_proxy.register_for_execution(name="calculator")(calculator)
<function __main__.calculator(a: int, b: int, operator: Annotated[Literal['+', '-', '*', '/'], 'operator']) -> int>

在上面的代码中,我们将 calculator 函数注册为助手和用户代理的工具。我们还为工具提供了一个名称和描述,以便助手代理能够理解其用途。

tip

始终为工具提供清晰简明的描述,这有助于助手的底层LLM理解工具的用途。

与代码执行器类似,工具必须在至少两个代理中注册,才能在对话中发挥作用。通过 register_for_llm 将工具的签名注册给代理,代理可以调用该工具;通过 register_for_execution 将工具的函数对象注册给代理,代理可以执行该工具的函数。

或者,您可以使用 autogen.register_function 函数一次性将工具注册给两个代理。

from autogen import register_function

# 将 calculator 函数注册给两个代理。
register_function(
calculator,
caller=assistant, # 助手代理可以建议调用 calculator。
executor=user_proxy, # 用户代理可以执行 calculator 的调用。
name="calculator", # 默认情况下,使用函数名作为工具名。
description="一个简单的计算器", # 工具的描述。
)

使用工具

一旦工具注册成功,我们就可以在对话中使用它。在下面的代码中,我们要求助手使用 calculator 工具进行一些算术计算。

chat_result = user_proxy.initiate_chat(assistant, message="What is (44232 + 13312 / (232 - 32)) * 5?")
用户 (对助手说):

(44232 + 13312 / (232 - 32)) * 5 等于多少?

--------------------------------------------------------------------------------

>>>>>>>> 使用自动回复...
助手 (对用户说):

***** 建议的工具调用 (call_4rElPoLggOYJmkUutbGaSTX1): calculator *****
参数:
{
"a": 232,
"b": 32,
"operator": "-"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 执行函数 calculator...
用户 (对助手说):

用户 (对助手说):

***** 调用工具的响应 (call_4rElPoLggOYJmkUutbGaSTX1) *****
200
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 使用自动回复...
助手 (对用户说):

***** 建议的工具调用 (call_SGtr8tK9A4iOCJGdCqkKR2Ov): calculator *****
参数:
{
"a": 13312,
"b": 200,
"operator": "/"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 执行函数 calculator...
用户 (对助手说):

用户 (对助手说):

***** 调用工具的响应 (call_SGtr8tK9A4iOCJGdCqkKR2Ov) *****
66
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 使用自动回复...
助手 (对用户说):

***** 建议的工具调用 (call_YsR95CM1Ice2GZ7ZoStYXI6M): calculator *****
参数:
{
"a": 44232,
"b": 66,
"operator": "+"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 执行函数 calculator...
用户 (对助手说):

用户 (对助手说):

***** 调用工具的响应 (call_YsR95CM1Ice2GZ7ZoStYXI6M) *****
44298
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 使用自动回复...
助手 (对用户说):

***** 建议的工具调用 (call_oqZn4rTjyvXYcmjAXkvVaJm1): calculator *****
参数:
{
"a": 44298,
"b": 5,
"operator": "*"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 执行函数 calculator...
用户 (对助手说):

用户 (对助手说):

***** 调用工具的响应 (call_oqZn4rTjyvXYcmjAXkvVaJm1) *****
221490
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> 使用自动回复...
助手 (对用户说):

计算结果为 221490。终止。

--------------------------------------------------------------------------------

让我们验证一下答案:

(44232 + int(13312 / (232 - 32))) * 5
221490

答案是正确的。你可以看到助手能够理解工具的使用方法并正确进行计算。

工具模式

如果你熟悉 OpenAI 的工具使用 API,你可能会想知道为什么我们没有创建工具模式。实际上,工具模式是根据函数签名和类型提示自动生成的。你可以通过检查 agent 的 llm_config 属性来查看工具模式。

assistant.llm_config["tools"]
[{'type': 'function',
'function': {'description': '一个简单的计算器',
'name': 'calculator',
'parameters': {'type': 'object',
'properties': {'a': {'type': 'integer', 'description': 'a'},
'b': {'type': 'integer', 'description': 'b'},
'operator': {'enum': ['+', '-', '*', '/'],
'type': 'string',
'description': 'operator'}},
'required': ['a', 'b', 'operator']}}}]

你可以看到工具模式是根据函数签名、类型提示和描述自动生成的。这就是为什么使用类型提示并为工具提供清晰的描述非常重要,因为LLM使用它们来理解工具的使用方法。

你还可以使用 Pydantic 模型来提供更复杂的类型模式。在下面的示例中,我们使用 Pydantic 模型来定义计算器的输入。

from pydantic import BaseModel, Field


class CalculatorInput(BaseModel):
a: Annotated[int, Field(description="第一个数字。")]
b: Annotated[int, Field(description="第二个数字。")]
operator: Annotated[Operator, Field(description="运算符。")]


def calculator(input: Annotated[CalculatorInput, "计算器的输入。"]) -> int:
if input.operator == "+":
return input.a + input.b
elif input.operator == "-":
return input.a - input.b
elif input.operator == "*":
return input.a * input.b
elif input.operator == "/":
return int(input.a / input.b)
else:
raise ValueError("无效的运算符")

与之前一样,我们使用名称 "calculator" 将工具注册到代理中。

tip

将工具注册到相同的名称将覆盖之前的工具。

assistant.register_for_llm(name="calculator", description="一个接受嵌套表达式作为输入的计算器工具")(
calculator
)
user_proxy.register_for_execution(name="calculator")(calculator)
<function __main__.calculator(input: typing.Annotated[__main__.CalculatorInput, '计算器的输入。']) -> int>

你可以看到工具模式已经更新以反映新的类型模式。

assistant.llm_config["tools"]
[{'type': 'function',
'function': {'description': '一个接受嵌套表达式作为输入的计算器工具',
'name': 'calculator',
'parameters': {'type': 'object',
'properties': {'input': {'properties': {'a': {'description': '第一个数字。',
'title': 'A',
'type': 'integer'},
'b': {'description': '第二个数字。',
'title': 'B',
'type': 'integer'},
'operator': {'description': '运算符。',
'enum': ['+', '-', '*', '/'],
'title': 'Operator',
'type': 'string'}},
'required': ['a', 'b', 'operator'],
'title': 'CalculatorInput',
'type': 'object',
'description': '计算器的输入。'}},
'required': ['input']}}}]

让我们在对话中使用这个工具。

chat_result = user_proxy.initiate_chat(assistant, message="(1423 - 123)/ 3 + (32 + 23)* 5 是多少?")
User (to Assistant):

What is (1423 - 123) / 3 + (32 + 23) * 5?

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_Uu4diKtxlTfkwXuY6MmJEb4E): calculator *****
Arguments:
{
"input": {
"a": (1423 - 123) / 3,
"b": (32 + 23) * 5,
"operator": "+"
}
}
***************************************************************************

--------------------------------------------------------------------------------
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_Uu4diKtxlTfkwXuY6MmJEb4E) *****
Error: Expecting value: line 1 column 29 (char 28)
You argument should follow json format.
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

I apologize for the confusion, I seem to have made a mistake. Let me recalculate the expression properly.

First, we need to do the calculations within the brackets. So, calculating (1423 - 123), (32 + 23), and then performing remaining operations.
***** Suggested tool call (call_mx3M3fNOwikFNoqSojDH1jIr): calculator *****
Arguments:
{
"input": {
"a": 1423,
"b": 123,
"operator": "-"
}
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_mx3M3fNOwikFNoqSojDH1jIr) *****
1300
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_hBAL2sYi6Y5ZtTHCNPCmxdN3): calculator *****
Arguments:
{
"input": {
"a": 32,
"b": 23,
"operator": "+"
}
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_hBAL2sYi6Y5ZtTHCNPCmxdN3) *****
55
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_wO3AP7EDeJvsVLCpvv5LohUa): calculator *****
Arguments:
{
"input": {
"a": 1300,
"b": 3,
"operator": "/"
}
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_wO3AP7EDeJvsVLCpvv5LohUa) *****
433
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_kQ2hDhqem8BHNlaHaE9ezvvQ): calculator *****
Arguments:
{
"input": {
"a": 55,
"b": 5,
"operator": "*"
}
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_kQ2hDhqem8BHNlaHaE9ezvvQ) *****
275
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_1FLDUdvAZmjlSD7g5GFFJOpO): calculator *****
Arguments:
{
"input": {
"a": 433,
"b": 275,
"operator": "+"
}
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_1FLDUdvAZmjlSD7g5GFFJOpO) *****
708
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

The calculation result of the expression (1423 - 123) / 3 + (32 + 23) * 5 is 708. Let's proceed to the next task.
TERMINATE

--------------------------------------------------------------------------------

让我们验证一下答案:

int((1423 - 123) / 3) + (32 + 23) * 5
708

再次,答案是正确的。你可以看到助手能够理解新的工具模式并正确执行计算。

如何在单个代理中隐藏工具使用和代码执行?

有时候,我们希望将工具使用隐藏在单个代理内部,即工具调用和工具响应消息对外部不可见,代理以“内部独白”的形式响应外部消息并使用工具。例如,你可能想构建一个类似于OpenAI的助手的代理,它在内部执行内置工具。

要实现这一点,你可以使用嵌套对话。嵌套对话允许你在代理内部创建“内部独白”,以调用和执行工具。这对于代码执行也适用。请参考nested chats for tool use中的示例。

总结

在本章中,我们向你展示了如何创建、注册和使用工具。工具使代理能够在不编写任意代码的情况下执行操作。在下一章中,我们将介绍对话模式,并展示如何使用对话的结果。