跳到主要内容

工具使用

Open In Colab Open on 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("Invalid operator")

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

tip

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

注册工具

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

import os

from autogen import ConversableAgent

# Let's first define the assistant agent that suggests tool calls.
assistant = ConversableAgent(
name="Assistant",
system_message="You are a helpful AI assistant. "
"You can help with simple calculations. "
"Return 'TERMINATE' when the task is done.",
llm_config={"config_list": [{"model": "gpt-4", "api_key": os.environ["OPENAI_API_KEY"]}]},
)

# The user proxy agent is used for interacting with the assistant agent
# and executes tool calls.
user_proxy = ConversableAgent(
name="User",
llm_config=False,
is_termination_msg=lambda msg: msg.get("content") is not None and "TERMINATE" in msg["content"],
human_input_mode="NEVER",
)

# Register the tool signature with the assistant agent.
assistant.register_for_llm(name="calculator", description="A simple calculator")(calculator)

# Register the tool function with the user proxy agent.
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

# Register the calculator function to the two agents.
register_function(
calculator,
caller=assistant, # The assistant agent can suggest calls to the calculator.
executor=user_proxy, # The user proxy agent can execute the calculator calls.
name="calculator", # By default, the function name is used as the tool name.
description="A simple calculator", # A description of the tool.
)

使用工具

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

chat_result = user_proxy.initiate_chat(assistant, message="What is (44232 + 13312 / (232 - 32)) * 5?")
User (to Assistant):

What is (44232 + 13312 / (232 - 32)) * 5?

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

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

***** Suggested tool call (call_4rElPoLggOYJmkUutbGaSTX1): calculator *****
Arguments:
{
"a": 232,
"b": 32,
"operator": "-"
}
***************************************************************************

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

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

User (to Assistant):

***** Response from calling tool (call_4rElPoLggOYJmkUutbGaSTX1) *****
200
**********************************************************************

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

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

***** Suggested tool call (call_SGtr8tK9A4iOCJGdCqkKR2Ov): calculator *****
Arguments:
{
"a": 13312,
"b": 200,
"operator": "/"
}
***************************************************************************

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

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

User (to Assistant):

***** Response from calling tool (call_SGtr8tK9A4iOCJGdCqkKR2Ov) *****
66
**********************************************************************

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

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

***** Suggested tool call (call_YsR95CM1Ice2GZ7ZoStYXI6M): calculator *****
Arguments:
{
"a": 44232,
"b": 66,
"operator": "+"
}
***************************************************************************

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

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

User (to Assistant):

***** Response from calling tool (call_YsR95CM1Ice2GZ7ZoStYXI6M) *****
44298
**********************************************************************

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

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

***** Suggested tool call (call_oqZn4rTjyvXYcmjAXkvVaJm1): calculator *****
Arguments:
{
"a": 44298,
"b": 5,
"operator": "*"
}
***************************************************************************

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

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

User (to Assistant):

***** Response from calling tool (call_oqZn4rTjyvXYcmjAXkvVaJm1) *****
221490
**********************************************************************

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

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

The result of the calculation is 221490. TERMINATE

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

让我们验证一下答案:

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

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

工具模式

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

assistant.llm_config["tools"]
[{'type': 'function',
'function': {'description': 'A simple calculator',
'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="The first number.")]
b: Annotated[int, Field(description="The second number.")]
operator: Annotated[Operator, Field(description="The operator.")]


def calculator(input: Annotated[CalculatorInput, "Input to the calculator."]) -> 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("Invalid operator")

和之前一样,我们使用名称"calculator"向代理注册该工具。

tip

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

assistant.register_for_llm(name="calculator", description="A calculator tool that accepts nested expression as input")(
calculator
)
user_proxy.register_for_execution(name="calculator")(calculator)
<function __main__.calculator(input: typing.Annotated[__main__.CalculatorInput, 'Input to the calculator.']) -> int>

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

assistant.llm_config["tools"]
[{'type': 'function',
'function': {'description': 'A calculator tool that accepts nested expression as input',
'name': 'calculator',
'parameters': {'type': 'object',
'properties': {'input': {'properties': {'a': {'description': 'The first number.',
'title': 'A',
'type': 'integer'},
'b': {'description': 'The second number.',
'title': 'B',
'type': 'integer'},
'operator': {'description': 'The operator.',
'enum': ['+', '-', '*', '/'],
'title': 'Operator',
'type': 'string'}},
'required': ['a', 'b', 'operator'],
'title': 'CalculatorInput',
'type': 'object',
'description': 'Input to the calculator.'}},
'required': ['input']}}}]

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

chat_result = user_proxy.initiate_chat(assistant, message="What is (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’s Assistant的代理,它在内部执行内置工具。

为了实现这一点,你可以使用嵌套聊天。嵌套聊天允许你在一个代理中创建“内部独白”来调用和执行工具。这同样适用于代码执行。查看使用嵌套聊天的工具示例了解更多。

总结

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