使用GPT功能调用编写AI助手

ChatGPT中文站

OpenAI 的 GPT 系列现在支持函数调用,这是一个强大的功能,允许开发人员通过向其描述函数来从模型中获取结构化数据。这是人工智能开发领域的一个重大转变,因为它赋予了 AI 模型使用工具的能力,扩展了它生成文本之外的能力。

函数调用有什么用?

请想象你正在建设一个聊天机器人。没有函数调用,这个聊天机器人可以根据它接受的信息回答问题。但如果用户问的是需要实时数据的问题,例如当前天气或股市价格呢?或者用户想要执行一个动作,比如预订酒店或发送电子邮件呢?

这就是函数调用的作用。通过向 GPT 描述您的自定义函数,您使其具有与您的代码交互的能力。然后,该模型可以生成一个 JSON 对象,其中包含调用您的函数的参数。

这意味着你的聊天机器人现在可以根据用户的请求获取实时数据或执行操作。以下是您可以使用函数调用的一些方式:

  • 创建交互式聊天机器人:您的聊天机器人现在可以执行像预订酒店房间、发送电子邮件或根据用户请求获取实时数据这样的操作。
  • 将自然语言转换为API调用:您可以将用户请求(例如“谁是我的顶级客户?”)转换为对您的后端的API调用。
  • 从文本中提取结构化数据:例如,您可以定义一个函数来提取维基百科文章中提到的所有人。
  • 自动化数据分析:假设您有一个可以分析数据集并提供洞察力的函数,例如识别趋势、异常值或进行预测。通过函数调用,您可以让用户以自然语言问AI分析特定的数据集,然后AI可以调用您的函数执行分析并返回结果。
  • 电子商务:如果您正在运行电子商务平台,您可以为添加物品到购物车、应用折扣代码或结账等操作定义功能。然后,AI可以根据用户请求调用这些功能,提供对话式购物体验。
  • 教育工具:对于教育应用程序,您可以定义生成测验题、提供提示或评估答案的功能。人工智能可以与这些功能进行交互,提供交互式学习体验。
  • 医疗应用程序: 在医疗环境中,您可以定义功能,以拉取病人记录,预约或基于症状提供医疗建议。人工智能可以调用这些功能,以帮助医疗专业人员或患者。 (请记住,任何敏感数据都应在您的本地功能中处理,并不包括在与模型的对话中,以确保隐私和遵守HIPAA等法规。)
  • 交互式游戏:在游戏中,您可以定义控制玩家动作的函数,例如向某个方向移动、攻击或使用道具。 AI可以根据玩家的自然语言命令调用这些函数,创造出独特的语音控制游戏体验。

实质上,函数调用可以通过连接AI模型和您的自定义代码,创建更多交互性和有用性的AI应用程序。

让我们用一个简单但有用的例子来演示这种新功能:让GPT具备执行算术运算的能力,因为我们知道数学是大型语言模型的一个弱点。

我将从OpenAI函数调用菜谱中调整代码,并添加更多的注释,以便您可以跟随操作。

设置

第一步是安装和导入所需的库:

pip install openai tenacity termcolor requests json

然后导入库并设置您的GPT模型和API密钥:

import json
import openai
import requests

# For retrying an API call if it fails
from tenacity import retry, wait_random_exponential, stop_after_attempt

# For color coding a conversation
from termcolor import colored

GPT_MODEL = "gpt-3.5-turbo-0613"
openai.api_key = "YOUR_API_KEY"

现在让我们定义一些助手函数。您可以在创建的任何项目中保持它们相同。

首先,一个发送API请求的功能:

@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, functions=None, function_call=None, model=GPT_MODEL):
"""
This function sends a POST request to the OpenAI API to generate a
chat completion.
Parameters:
- messages (list): A list of message objects. Each object should have a
'role' (either 'system', 'user', 'function', or 'assistant')
and 'content' (the content of the message).
- functions (list, optional): A list of function objects that describe the functions that the model can call.
- function_call (str or dict, optional): If it's a string, it can be either 'auto' (the model decides whether to call a function) or 'none'
(the model will not call a function). If it's a dict, it should describe the function to call.
- model (str): The ID of the model to use.
Returns:
- response (requests.Response): The response from the OpenAI API. If the request was successful, the response's JSON will contain the chat completion.
"""
# Set up the headers for the API request
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + openai.api_key,
}
# Set up the data for the API request
json_data = {"model": model, "messages": messages}
# If functions were provided, add them to the data
if functions is not None:
json_data.update({"functions": functions})
# If a function call was specified, add it to the data
if function_call is not None:
json_data.update({"function_call": function_call})
# Send the API request
try:
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
json=json_data,
)
return response
except Exception as e:
print("Unable to generate ChatCompletion response")
print(f"Exception: {e}")
return e

在我撰写此文的时候,Python API似乎不支持函数调用,因此我们必须使用POST请求。上述函数将大大简化该过程。

接下来我们可以使用一个函数给对话加上颜色编码,从而更容易阅读:

# This function supplied by OpenAI doesn't work in Colab; I have an alternative function for this in the next cell

def original_pretty_print_conversation(messages):
"""
This function takes a list of messages as input.
Each message is a dictionary with a `role` and `content`.
The `role` can be "system", "user", "assistant", or "function".
This function formats each message based on its role
and appends it to the `formatted_messages` list.
Finally, it prints each formatted message in a color corresponding
to its role.
"""

# Define a dictionary to map roles to colors
role_to_color = {
"system": "red",
"user": "green",
"assistant": "blue",
"function": "magenta",
}

# Initialize an empty list to store the formatted messages
formatted_messages = []

# Iterate over each message in the messages list
for message in messages:
# Check the role of the message and format it accordingly
if message["role"] == "system":
formatted_messages.append(f"system: {message['content']}\n")
elif message["role"] == "user":
formatted_messages.append(f"user: {message['content']}\n")
elif message["role"] == "assistant" and message.get("function_call"):
formatted_messages.append(f"assistant: {message['function_call']}\n")
elif message["role"] == "assistant" and not message.get("function_call"):
formatted_messages.append(f"assistant: {message['content']}\n")
elif message["role"] == "function":
formatted_messages.append(f"function ({message['name']}): {message['content']}\n")

# Print each formatted message in its corresponding color
for formatted_message in formatted_messages:
print(
colored(
formatted_message,
role_to_color[messages[formatted_messages.index(formatted_message)]["role"]],
)
)

请注意,这种文本格式化方法在Google Colab中不起作用,但我的附带笔记本有另一种可以使用的函数。

现在让我们定义一个GPT要使用的函数:

def add_numbers(num1, num2):
return num1 + num2

足够简单,对吧?但是现在我们需要编写一个JSON规范来解释GPT的功能:

functions = [
{
"name": "add_numbers",
"description": "Add two numbers",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "The first number",
},
"num2": {
"type": "number",
"description": "The second number",
},
},
"required": ["num1", "num2"],
},
},
]

这里需要注意几点:

  • 这些数据是GPT所看到的 - 而不是你的Python函数。因此,你需要清楚描述你的变量和函数的作用。
  • 你可能会使用多个功能,但它们都放在这里,因为它们会被转换为 JSON 并随请求一起发送。
  • GPT 实际上非常擅长自己编写这些规范!因此,给它一个例子,并展示它您的功能,它应该能够帮助您正确地编写这些规范。
  • 这里的“type”是“number”(而不是“float”),因为JSON架构使用“number”来表示整数和浮点数。

接下来我们需要一种方法来调用GPT请求使用的函数。一个简单的方法是拥有一个字典,将函数名作为字符串映射回原始函数:

functions_dict = {
"add_numbers": add_numbers,
}

最后,让我们开始与GPT进行对话:

messages = []
messages.append({"role": "system", "content": "You are a helpful assistant."})

使用您的新功能

这个功能如果不能处理含糊的用户请求,就没有什么帮助。所以,与其给它构建一个数学测试,不如看看它能否自己判断何时使用所拥有的工具:

messages.append(
{
"role": "user",
"content": "I have a bill for $323 and another for $295. How much do I owe?"
}
)

现在我们使用聊天功能生成一个回应:

# Generate a response
chat_response = chat_completion_request(
messages, functions=functions
)

# Save the JSON to a variable
assistant_message = chat_response.json()["choices"][0]["message"]

# Append response to conversation
messages.append(assistant_message)

如果我们打印刚刚得到的assistant_message,我们会看到这个:

{'role': 'assistant',
'content': None,
'function_call': {'name': 'add_numbers',
'arguments': '{\n "num1": 323,\n "num2": 295\n}'}}

这是GPT告诉我们它已经准备好使用一个功能而不是发送基于文本的响应。那么让我们运行它!

if assistant_message["function_call"]:

# Retrieve the name of the relevant function
function_name = assistant_message["function_call"]["name"] # `add_numbers`

# Retrieve the arguments to send the function
function_args = json.loads(assistant_message["function_call"]["arguments"])
# 323 and 295

# Look up the function in our dictionary and
# call it with the provided arguments
result = functions_dict[function_name](**function_args)

print(result)

我们得到了618,这确实是正确的!

接下来我们需要做的是在对话中添加新消息,使用函数结果:

messages.append({
"role": "function",
"name": function_name,
"content": str(result), # Convert the result to a string
})

最后,我们需要告诉用户总数是多少:

# Call the model again to generate a user-facing message 
# based on the function result
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)

# Print the final conversation
pretty_print_conversation(messages)

我们收到了这个。

system: You are a helpful assistant.

user: I have a bill for $323 and another for $295. How much do I owe?

assistant: {'name': 'add_numbers', 'arguments': '{\n "num1": 323,\n "num2": 295\n}'}

function (add_numbers): 618

assistant: You owe a total of $618.

因此,助手实际上能够用自然语言报告结果。(并且,对于正在使用中的应用程序,您可能会选择不显示没有内容的消息,以便用户只看到“用户”和“助手”消息。)

增加更多功能

当然,你可能会让GPT访问不止一个功能,所以让我们用另一个可用的工具——减法来重复这个过程:这个例子将有助于展示GPT如何成功地从一个任意的功能选择中做出选择。

下面的代码将会很熟悉:我们正在为减法函数做与加法函数相同的事情。

# Define in Python
def subtract_numbers(num1, num2):
return num1 - num2


# Define the function specification
# — notice we're appending this to our previous function list using +=
functions += [
{
"name": "subtract_numbers",
"description": "Subtract two numbers",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "The first number",
},
"num2": {
"type": "number",
"description": "The second number",
},
},
"required": ["num1", "num2"],
},
},
]

# Add to our function dictionary
functions_dict['subtract_numbers'] = subtract_numbers

现在让我们尝试一个更加模糊的提示来看看GPT的表现:“我的支票账户里有8013美元。支付账单后我会剩下多少钱?”

messages.append({"role": "user", "content": "I have $8013 in my checking account. How much will I have after I pay my bills?"})

然后,我们之前看到的同样的代码:

  • 产生一个响应 (这将会是一个要求使用函数的请求)
  • 将该回复添加到对话中。
  • 从JSON响应中检索相关函数的名称。
  • 检索参数,然后查找函数并使用这些参数运行它。
  • 使用函数结果添加到对话中的新消息
  • 生成一个用户界面的消息,包含该结果。
# Generate a response
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]

# Append response to conversation
messages.append(assistant_message)

# I like putting an if statement here, but it's up to you
if assistant_message["function_call"]:

# Retrieve the name of the relevant function
function_name = assistant_message["function_call"]["name"]

# Retrieve the arguments to send the function
function_args = json.loads(assistant_message["function_call"]["arguments"])

# Look up the function and call it with the provided arguments
result = functions_dict[function_name](**function_args)

# Add a new message to the conversation with the function result
messages.append({
"role": "function",
"name": function_name,
"content": str(result), # Convert the result to a string
})

# Call the model again to generate a user-facing message
# based on the function result
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)

# Print the final conversation
pretty_print_conversation(messages)

这一次更加棘手,因为减法不像加法那样满足交换律:实际上num1和num2的顺序是有影响的。但是当我们打印最终的对话结果时,我们会看到:

system: You are a helpful assistant.

user: I have a bill for $323 and another for $295. How much do I owe?

assistant: {'name': 'add_numbers', 'arguments':
'{\n "num1": 323,\n "num2": 295\n}'}

function (add_numbers): 618

assistant: You owe a total of $618.

user: I have $8013 in my checking account. How much will I have after
I pay my bills?

assistant: {'name': 'subtract_numbers', 'arguments':
'{\n "num1": 8013,\n "num2": 618\n}'}

function (subtract_numbers): 7395

assistant: After paying your bills, you will have $7395 remaining in your
checking account.

因此,GPT 能够正确地推断 num1 应该是 8013, num2 应该是之前提到的账单的总和,即 618。

结论

这个简单的例子非常好地展示了GPT模型中函数调用的强大和灵活性。即使是含糊不清的信息也可以被正确分析,并选择和执行适当的函数。这为开发人员创造更多交互性和有用性的人工智能应用程序开创了一片新天地。

一旦你浏览代码并尝试实现你自己的例子,你会发现这个过程相当简单。你可以定义任何想要的功能,将其描述给GPT,然后在对话中使用它。这为创建更交互式和动态的人工智能应用程序打开了一扇可能的大门。

OpenAI CEO Sam Altman据报道评论说,ChatGPT插件似乎没有产品市场适配性。而开发人员最初认为他们想要将他们的应用程序放在ChatGPT中,实际上他们想要将ChatGPT放在他们的应用程序中。这个新特性允许开发人员做到这一点。通过将GPT集成到您的应用程序中,您可以利用人工智能的力量来增强用户体验,自动化任务并提供实时数据和见解。

所以,使用我的Colab笔记本电脑,并编写实现函数调用的代码。我们将会看到一系列有趣的新服务的涌现,希望你的其中之一。

2023-10-20 16:56:21 AI中文站翻译自原文