函数乐趣:GPT API函数的(几乎)所有内容
OpenAI宣布支持"GPT-3.5"和"GPT-4"API中的"函数",这是一个新功能。
一直都有人对这些感到兴奋莫名和困惑不解,那么让我们深入了解一下,首先进行一个高层次概述,然后进入实际代码以及工作示例。
![ChatGPT中文站](/posts/1$PDQgWfhMDg93N9sVvZGf0Q.png)
什么是函数?
您可以把函数视为一个“帮助程序”,它会监视您的GPT API提示,并在注意到可以“帮助”的提示时立即采取行动。
你作为开发人员,定义哪些功能可以提供给用户,并决定何时使用。
以下是一个例子。假设你正在构建一个聊天机器人,它使用后端的GPT-3.5 API来处理用户查询。你想要添加一个功能,如果用户问起天气的话就告诉他们——但这并不是你的应用所能做的唯一事情。这只是一个更大型聊天机器人的一个方面。
函数“helper”的作用是监视来自用户的信息,以寻找看起来能够使用额外代码回答或处理的信息。对于普通查询,不会运行额外的代码,GPT API将正常响应。
但是——如果用户突然提出一个似乎很适合你设置的功能的问题,这个“帮助程序”会介入。它会告诉你它可以使用代码来有用,它会按照一些预设要求格式化文本(或数据)。
以下是天气的例子:如果用户输入“企鹅游泳的速度有多快”,这将像处理其他提示一样由GPT API处理。
但是,如果它看到用户输入“洛杉矶的天气如何?”它会介入帮助,因为它认识到这个查询与你要求它注意的一种功能相匹配。
当发生这种情况时,API 返回一个触发器或钩子,供您的代码用于特别处理该函数。它不会自动运行任何代码(除非您已编写您的应用程序来这样做)。但它假定您可能想使用该函数并准备好要处理的数据。
什么不是函数?
让我们先说明几点。函数和ChatGPT中可用的插件并不完全相同。
它们有些是相同的概念,但目前这些是不同的东西。例如,您不能使用函数直接从GPT API调用现有插件。但插件的一般工作方式类似于函数的工作方式。
一个函数也不是在OpenAI这边运行的东西。继续天气的例子,不是GPT本身会获取天气数据。
当一个函数被激活时,你想要运行的实际代码是你的应用程序的一部分 - 你可以控制每当函数触发时会发生什么或不会发生什么。
它所做的就是生成文本并将其放入JSON中。你如何使用它取决于你。来自官方OpenAI文档:
“Chat Completions API 不会调用该函数;相反,模型会生成 JSON,您可以将其用于在代码中调用该函数。” 这一点非常重要。
一个函数是什么样子的?
一个函数的主要部分是一个数组。没错,就是一个由文本数据组成的大型数组,在每次调用时都会传递给GPT API。以下是它在CURL中的示例:
'{
"model": "gpt-3.5-turbo-0613",
"messages": [
{"role": "user", "content": "What is the weather like in Boston?"},
{"role": "assistant", "content": null, "function_call": {"name": "get_current_weather", "arguments": "{ \"location\": \"Boston, MA\"}"}},
{"role": "function", "name": "get_current_weather", "content": "{\"temperature\": "22", \"unit\": \"celsius\", \"description\": \"Sunny\"}"}
],
"functions": [
{
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
]
}'
... 这里是一个稍微不同的版本,作为 Python 字典:
functions = [
{
"name": "get_current_weather",
"description": "Get the current weather.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The four-digit ICAO Airport code for a METAR weather station. \
Example: KSEA. If Airport code is not provided, infer it from other available location data \
and your knowledge.",
}
},
"required": ["location"]
}
}
]
如果你有多个函数,你需要在这个数组中定义每一个函数。(这可能很快就会变得很长。稍后会有更多关于这个的信息。)
当您进行 API 调用时,GPT 实际上会阅读每个函数所需完成的纯文本以及它所处理的数据格式。
名称:这是该功能的具体和精确名称,用于在其他时间引用该功能。它必须在您的程序中是唯一的。
描述:一个自然语言的描述,说明您的函数应该做什么。
参数:这些定义了在函数运行时,GPT 代码需要通过什么标题、数据类型和格式向您的代码提供。
在上面的Python示例中,代码(未显示)期望一个四位数的机场代码,例如KSEA代表西雅图 - 塔科马国际机场 - 因此我们告诉API需要提供这样的内容(作为文本字符串)来填写“位置”参数。
以上示例还告诉API,“位置”是必需的 - 毕竟,我们不知道在哪里,就无法获取任何天气数据。
代码示例
好了,足够的胡言乱语,让我们看一些实际的代码示例。下面,我们还将详细查看API响应,以便您了解发生了什么。
Python: Python。
query = input("Enter your query: ")
apiKey = "YOUR-KEY"
url = "https://api.openai.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + apiKey
}
# GPT will read this to understand what Functions we define.
functions = [{
"name": "get_current_weather",
"description": "Get the current weather",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The four-digit ICAO Airport code for a METAR weather station. \
Example: KSEA. If Airport code is not provided, infer it from other available location data \
and your knowledge."
},
},
"required": ["location"]
}
}]
# session is a dictionary
messages = session.get('messages', [])
if not messages:
messages.append({"role": "system", "content": "You are a helpful assistant."})
messages.append({"role": "user", "content": query})
# Put all the data together to make the API call
data = {
"model": "gpt-3.5-turbo-0613",
"messages": messages,
"functions": functions
}
如果您以前使用过GPT API,这应该是普遍熟悉的。我们正常准备API调用——认证密钥放在头部中,设置URL端点,包括消息上下文(如果有)。
显然,重要的事情包括:函数巨块、将模型设置为“gpt-3.5-turbo-0613”以便使用函数,以及将“functions”作为数据参数添加进去。
更多的样板 Python 代码,实际发起 API 调用并获取响应:
import requests
import json
print(json.dumps(data, indent=4))
response = requests.post(url, headers=headers, json=data)
if response.status_code != 200:
print('Error:', response.text)
return 'Error: ' + response.text, 500
response_body = response.json()
响应示例
在我们看更多的代码之前,让我们看看响应示例实际上会是什么样子。
这些是API的真实响应,经过漂亮的格式化,以便您了解正在发生的情况。(这来自Python测试脚本,但响应结构应该是相同的,无论您使用PHP、JS还是其他任何东西。)
"model": "gpt-3.5-turbo-0613",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello."
}
]
再次提醒,如果您以前使用过GPT API,就应该能够识别这些模式。会话始于系统消息(“您是一个有用的助手”),然后我们向API发送用户消息(“你好。”)。
以下是“Hello”消息的完整API响应:
"id": "chatcmpl-7REdgBnrwaIPQrkZhWPBRrP27K1wI",
"object": "chat.completion",
"created": 1686725484,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hi there! How can I assist you today?"
},
"finish_reason": "stop"
}
]
GPT API 响应为:“你好!我今天可以为您提供什么帮助?”这是我们预期的 - 用户的查询中没有提到天气,因此它不会触发定义在 API 数据中的那个功能。
做一个备注 [finish_reason] => 停止。这是一个有用的参数,小会儿我们会用到它来检测API是否想使用一个函数。
现在让我们询问它天气的情况:
data = [
{
"role": "user",
"content": "Get weather for Detroit."
}
]
以下是完整的API响应:
"id": "chatcmpl-7REiQGQ1WEzRcB0CrIV3GVMxRUnwa",
"object": "chat.completion",
"created": 1686725778,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"function_call": {
"name": "get_current_weather",
"arguments": {
"location": "KDTW"
}
}
},
"finish_reason": "function_call"
}
]
现在那就不同了!注意到一些新的东西了吗?
它仍然响应为[role] => assistant,但现在有一个新的响应参数称为[function_call] —— 它包含了这个数组:
"name": "get_current_weather",
"arguments": {
"location": "KDTW"
}
这就是我们想要的!这个回应告诉我们的是,GPT看到用户正在问天气的情况,并且它知道我们有一个叫做get_current_weather的功能,我们可能希望使用它。
以下是 GPT API 响应返回函数时的 HTML 结构:然后您可以使用“名称”和“参数”来运行编写的函数。
![ChatGPT中文站](/posts/1$QlYV2j0zarI_8SZvkZvksA.png)
它已将“位置”预处理为“KDTW”。但是……我们要求底特律。为什么它不说底特律?
这就是函数非常聪明的地方。当我们定义函数时,我们告诉它:在那里。
"location": {
"type": "string",
"description": "The four-digit ICAO Airport code for a METAR weather station. \
Example: KSEA. If Airport code is not provided, infer it from other available location data \
and your knowledge.",
}
你看到发生了什么,知道它为什么很重要吗?
我们在函数的定义中告诉它期望一个四位数字的ICAO机场代码作为位置。如果用户没有明确输入该代码,我们也告诉它利用自己的知识。
GPT聪明到足以意识到底特律的ICAO机场代码是——你猜对了——KDTW。因此,不需要我们手动编写,GPT准备了与之对应的函数参数。整洁!
![ChatGPT中文站](/posts/1$5H1jhTnIJA59UkzVZsuRCg.png)
回顾完整的 API 响应。您看到这个了吗?
"finish_reason": "function_call"
那很重要。那就是API告诉我们,“我停止的原因是因为我认为我们想在这里调用一个函数”。
我们可以用这个!那是我们代码的完美触发器。当API响应将“finish_reason”列为“function_call”时,我们可以轻松地检测到它,并执行我们的代码分支来处理它。
回到我们的Python:
#navigate down the response array / dicionary tree structure
if 'choices' in responseBody and isinstance(responseBody['choices'], list) and len(responseBody['choices']) > 0:
firstChoice = responseBody['choices'][0]
#take the 'finish_reason' parameter and put it in a variable
finishReason = firstChoice.get('finish_reason', None)
# This is what we're looking for!
# if finishReason is 'function_call' do some stuff:
if finishReason == 'function_call':
print('GPT thinks this is a Function call.')
#save the function name from the API response into a variable
function_name = response['choices'][0]['message']['function_call']['name']
现在我们可以执行更多的代码来处理函数响应,因为 API 响应中的 “finish_reason”:“function_call” 告诉我们这就是我们需要处理的内容。
这是一个重要的观点:GPT 不会执行任何来自函数的代码(自动或其他)。
它所做的就是作为API响应的一部分返回文本,然后您可以使用它来做任何您想做的事情,或者选择根本不执行任何操作。
再说一遍:GPT 不会自动执行任何代码。你需要自己编写检查和逻辑来处理响应。
你的代码中的功能
因此,一旦程序识别出我们可能要使用一个函数,它实际上会做什么呢?这取决于你!
这是你需要在应用程序中编写的函数 - 一个真正的、适当的函数。代码函数,而不是GPT函数。(是的,这有点令人困惑。)
这是一些示例Python代码,展示了如何使用代码来处理这个问题:
#put the API response for location into a variable location
location = response['choices'][0]['message']['function_call']['arguments']['location']
if function_name == "get_current_weather":
# Do stuff for this function
print("The function name is get_current_weather.")
当然,你可能想在代码中执行更高级的操作,比如获取外部天气 API 并传递位置(在我们的示例中是 KDTW),然后读取响应。
这是一个快速的例子,展示如何调用外部API获取机场代码的天气数据:
if function_name == "get_current_weather":
# Do stuff for this function
print("The function name is get_current_weather.")
import requests
import urllib.parse
# Set up the URL, passing on the location variable such as KDTW
url = f'https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString={location}'
# Use requests to fetch the URL
response = requests.get(url)
# Check if the request was successful
if response.status_code == 200:
function_response = response.text
print(function_response)
else:
print("Error making the request.")
这将从天气API返回响应,可能如下所示:
KDTW 140415Z 自动 00000KT 10SM 晴 09/09 A2958 注释 AO2 T00910086
(不要担心,这是METAR格式,GPT可以很好地理解它。)
将回应返回给GPT
太好了!所以现在我们已经确认用户想要天气数据,调用了一个代码段来处理它,从外部API获取了真实的天气...现在呢?!
我们可以将该天气数据传回GPT API,将其标记为功能响应,以便将其解析为普通英语,供用户使用。
model="gpt-3.5-turbo-0613",
messages=[
{"role": "user", "content": "Get weather for Detroit."},
message,
{
"role": "function",
"name": function_name,
"content": function_response,
},
]
你随后可以使用上述设置进行标准的GPT API调用。
消息参数
“role”: “功能”, “name”: 功能名称, “content”: 功能响应
告诉GPT这是来自函数的返回响应,函数名称仍然保持之前的“获取当前天气”的值,函数响应持有METAR天气数据。
{
"role": "function",
"name": "get_current_weather",
"content": "KDTW 140415Z AUTO 00000KT 10SM CLR 09/09 A2958 RMK AO2 T00910086"
}
这是GPT API的回应!它可以读取天气数据,并将其有用地解析为易于阅读的文本:
[0] => Array
(
[index] => 0
[message] => Array
(
[role] => assistant
[content] => The current weather at the location with the airport code KDTW is as follows:
- Temperature: 9°C
- Dewpoint: 9°C
- Visibility: 10 miles
- Wind: Calm
- Sky conditions: Clear
- Altimeter: 29.58 inHg
Please note that this information is subject to change.
)
[finish_reason] => stop
令牌使用
记住,我们提到GPT函数定义每次发送请求时都会传递给API?
这是一个要考虑的事情:这段文字算作令牌(因此需要支付API费用)。您定义的功能越多,这段文字就越长,您就会使用更多的令牌。
对于像GPT-3.5这样相对便宜的模型,这对大多数用户来说可能不是一个巨大的问题。然而,如果你使用的是更昂贵的模型,比如GPT-4,额外的令牌使用就是一个需要考虑的因素。
结论
GPT API 功能可能不是一个魔法解决方案,但它们可以是将特性添加到您的应用程序并以优雅的方式处理用户请求的有用方式。
尝试一下吧 - 编码愉快!