想象一下创建一个AI助手,你可以对它说类似这样的话:“为我预订最近的泰国餐厅最早的预约,并更新我的日历。”语言模型不断突破界限和发展。OpenAI是ChatGPT背后的公司,最近在他们的GPT模型中引入了一个强大的新功能,称为函数调用。函数调用简化了与外部工具和API通信的聊天机器人的创建,为基于人工智能的应用开辟了新的可能性。
在本文中,我们将深入探讨函数调用的概念、其影响以及它对我们与AI系统互动方式的转变的影响,通过创建一个名为NewsGPT的聊天机器人,为您提供全球热点新闻。
NewsGPT的示例运行。命令行界面显示用户询问:“美国最新的新闻是什么?”并收到一个包含链接到期刊或来源的五篇文章的列表。
什么是函数调用?
函数调用是OpenAI的GPT-4-0613和GPT-3.5 Turbo-0613模型中的一个新功能。这些AI模型经过训练,能够根据用户的提示检测到函数调用的需求,并以结构化的调用请求而不是普通文本进行回应。
函数调用允许聊天机器人与其他系统进行交互,使得GPT模型能够回答它们在训练集中没有包含的需要实时信息或数据的问题。换句话说,函数调用提供了另一种方法来教导AI模型如何与外部世界进行交互。
函数调用的目的是什么?
在函数调用之前,只有两种方法可以增强GPT语言模型的能力:
-
微调:通过提供示例响应来进一步训练语言模型。微调是一种强大的技术,但需要大量的工作(和成本)来准备训练数据。此外,在OpenAI在GPT-3.5和GPT-4模型中启用此功能之前,只有少数旧模型可以进行微调。
-
嵌入:通过上下文数据丰富提示,可以扩展机器人的知识并创建更准确的响应。不足之处在于这种上下文可能占用大量的令牌,增加成本,并留下较少的令牌可用于构建复杂的响应。
函数调用通过允许GPT模型要求我们代表它运行函数,增加了扩展GPT能力的第三种方式。然后,模型可以获取函数的结果,并构建一个符合当前对话的可读文本响应。
如何使用函数调用
函数调用的引入改变了我们与GPT API的互动方式。在这些功能之前,互动很简单:
-
向API发送一个提示。
-
接收一个响应。
-
重复。
一个显示在函数调用之前与GPT API的基本互动的图表。
有了函数调用,序列变得更加复杂:
-
发送用户提示以及可调用函数的列表。
-
GPT模型以普通文本响应或函数调用请求的方式进行回应。
-
如果模型请求函数调用,你的聊天机器人的任务就是执行它并将结果返回给API。
-
使用提供的数据,模型然后形成一个连贯的文本响应。然而,在某些情况下,API可能会请求一个新的函数调用。
一个显示使用函数调用与GPT API进行更复杂互动的图表。
使用聊天补全API进行函数调用
为了让模型调用函数,我们必须使用聊天补全API。API接受一个带有JSON负载的POST请求,其中包含要处理的消息列表。发送到API的典型提示如下所示:
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "太阳系有多少颗行星?"
}
]
}
role: user
通知API这个 content
是用户生成的。GPT API可能会以类似以下的方式回复:
{
"id": "chatcmpl-7WVo3fYwerpAptzeqU46JamOvgBzh",
"object": "chat.completion",
"created": 1687983115,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "太阳系中有八大行星。它们是:\n\n1. 水星\n2. 金星\n3. 地球\n4. 火星\n5. 木星\n6. 土星\n7. 天王星\n8. 海王星"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 44,
"total_tokens": 59
}
}
role: assistant
对应GPT模型生成的消息。为了保持会话流程,我们必须在每个请求中向API提供完整的消息历史记录。例如,如果我们想进一步探讨之前的问题,相应的JSON载荷将如下所示:
{
"model":"gpt-3.5-turbo",
"messages":[
{
"role":"user",
"content":"太阳系有多少颗行星?"
},
{
"role":"assistant",
"content":"太阳系中有八大行星。它们是:\n\n1. 水星\n2. 金星\n3. 地球\n4. 火星\n5. 木星\n6. 土星\n7. 天王星\n8. 海王星"
},
{
"role":"user",
"content":"告诉我更多关于第二颗行星的信息。"
}
]
}
为了让语言模型知道它可以调用函数,我们需要将函数列表添加到载荷中。例如:
{
"model":"gpt-3.5-turbo-0613",
"messages":[
{
"role":"user",
"content":"纽约的天气如何?"
}
],
"functions":[
{
"name":"get_current_weather",
"description":"获取指定位置的当前天气",
"parameters":{
"type":"object",
"properties":{
"location":{
"type":"string",
"description":"城市和州,例如:旧金山,加利福尼亚州"
},
"unit":{
"type":"string",
"enum":[
"摄氏度",
"华氏度"
]
}
},
"required":[
"location"
]
}
}
]
}
您可能已经注意到,我们将模型切换为”gpt-3.5-turbo-0613″,因为它支持函数调用。如果模型决定调用该函数,我们将收到一个类型为role: assistant
且定义了function_call
属性的响应,如下所示:
{
"id":"chatcmpl-7WWG94C1DCFlAk5xmUwrZ9OOhFnOq",
"object":"chat.completion",
"created":1687984857,
"model":"gpt-3.5-turbo-0613",
"choices":[
{
"index":0,
"message":{
"role":"assistant",
"content":null,
"function_call":{
"name":"get_current_weather",
"arguments":"{\n \"location\": \"纽约州纽约\"\n}"
}
},
"finish_reason":"function_call"
}
],
"usage":{
"prompt_tokens":81,
"completion_tokens":19,
"total_tokens":100
}
}
我们的任务是使用提供的参数执行get_current_weather
函数。OpenAI不会执行该函数,而是我们的聊天机器人的工作是运行它并解析返回的数据。
一旦我们获取到天气数据,我们使用一个名为function
的新类型的角色将其发送回模型。例如:
{
"model": "gpt-3.5-turbo-0613",
"messages": [
{
"role": "user",
"content": "纽约的天气怎么样?"
},
{
"role": "assistant",
"content": null,
"function_call": {
"name": "get_current_weather",
"arguments": "{\n \"location\": \"纽约, 纽约\"\n}"
}
},
{
"role": "function",
"name": "get_current_weather",
"content": "温度:57华氏度,条件:下雨"
}
],
"functions": [
{
"name": "get_current_weather",
"description": "获取给定位置的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如旧金山,加利福尼亚"
},
"unit": {
"type": "string",
"enum": [
"摄氏度",
"华氏度"
]
}
},
"required": [
"location"
]
}
}
]
}
请注意,我们将整个消息历史记录传递给API,包括我们的初始提示、模型的函数调用以及在我们的代码中执行天气函数的结果。这使得语言模型能够理解函数调用的上下文。
最后,模型可能会以适当格式的答案回复,回答我们最初的问题:
{
"id": "chatcmpl-7WWQUccvLUfjhbIcuvFrj2MDJVEiN",
"object": "chat.completion",
"created": 1687985498,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "纽约市的天气目前下雨,温度为57华氏度。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 119,
"completion_tokens": 19,
"total_tokens": 138
}
}
构建NewsGPT
为了了解更多关于函数调用的内容,我们将构建一个名为NewsGPT的Python聊天机器人,能够实时访问最新的新闻。该机器人将使用函数调用来确定从NewsAPI.org获取何种数据。
构建该机器人,您需要以下内容:
-
OpenAI API密钥。这需要信用卡,因为API请求是需要付费的。但是,新账户在前三个月内可获得5美元的信用额。
-
NewsAPI API密钥。在NewsAPI.org注册并获得一个免费的入门密钥。
-
Python 3。
设置项目
安装所需的依赖项:
$ pip install openai tiktoken
该项目只由一个文件组成,我们将其命名为newsgpt.py
。我将开始添加所有必要的导入:
import openai
import tiktoken
import json
import os
import requests
接下来,我将定义一些常量:
-
要使用的GPT模型。我将使用
gpt-3.5-turbo-16k
,因为它有16k的令牌限制,可以处理更长的具有更多上下文的对话。 -
系统提示,它指示模型其基本目的。
-
用于计算字符串和消息中的令牌的编码;这是为了确保我们不超过语言模型的限制。
-
在一个链中调用的函数的最大数量(稍后会详细说明)。
llm_model = "gpt-3.5-turbo-16k"
llm_max_tokens = 15500
llm_system_prompt = "您是一个助手,提供用户请求的新闻和头条。始终尝试使用可用的函数调用获取最新的突发新闻。"
encoding_model_messages = "gpt-3.5-turbo-0613"
encoding_model_strings = "cl100k_base"
function_call_limit = 3
所有的OpenAI模型都有一个令牌限制。如果超过了这个限制,API将会抛出一个错误而不是响应我们的请求。因此,我们需要一个函数来计算令牌的数量。我将使用官方示例文档中的这个函数:
def num_tokens_from_messages(messages):
"""返回消息列表使用的令牌数量。"""
try:
encoding = tiktoken.encoding_for_model(encoding_model_messages)
except KeyError:
encoding = tiktoken.get_encoding(encoding_model_strings)
num_tokens = 0
for message in messages:
num_tokens += 4
for key, value in message.items():
num_tokens += len(encoding.encode(str(value)))
if key == "name":
num_tokens += -1
num_tokens += 2
return num_tokens
定义一个调用函数
现在,我将定义一个函数来查询NewsAPI.org API以获取最新新闻:
def get_top_headlines(query: str = None, country: str = None, category: str = None):
"""从newsapi.org检索头条新闻(需要API密钥)"""
base_url = "https://newsapi.org/v2/top-headlines"
headers = {
"x-api-key": os.environ['NEWS_API_KEY']
}
params = { "category": "general" }
if query is not None:
params['q'] = query
if country is not None:
params['country'] = country
if category is not None:
params['category'] = category
# 从newsapi.org获取数据 - 参考:https://newsapi.org/docs/endpoints/top-headlines
response = requests.get(base_url, params=params, headers=headers)
data = response.json()
if data['status'] == 'ok':
print(f"正在处理来自newsapi.org的{data['totalResults']}篇文章")
return json.dumps(data['articles'])
else:
print("请求失败,消息:", data['message'])
return '未找到文章'
为了让GPT了解这个函数,我们需要使用特定的JSON结构进行描述。格式在官方文档中描述如下:
signature_get_top_headlines = {
"name":"get_top_headlines",
"description":"按国家和/或类别获取头条新闻",
"parameters":{
"type":"object",
"properties":{
"query":{
"type":"string",
"description":"自由格式的关键词或搜索短语。"
},
"country":{
"type":"string",
"description":"您想要获取头条新闻的国家的2字母ISO 3166-1代码"
},
"category":{
"type":"string",
"description":"您想要获取头条新闻的类别",
"enum":[
"business",
"entertainment",
"general",
"health",
"science",
"sports",
"technology"
]
}
},
"required":[
]
}
}
使用带有函数调用的聊天完成API
接下来,我将定义complete
函数,该函数执行以下几个任务:
-
在消息末尾添加系统提示。这个系统消息有助于定义GPT模型的角色。
-
如果总令牌数超过模型的限制,则删除旧消息。
-
将请求发送到GPT API。
-
从列表中删除系统消息。
def complete(messages, function_call: str = "auto"):
"""从OpenAI的GPT获取完成结果"""
messages.append({"role": "system", "content": llm_system_prompt})
# 删除旧的完成结果以保持对话在令牌限制内
while num_tokens_from_messages(messages) >= llm_max_tokens:
messages.pop(0)
print('正在处理...')
res = openai.ChatCompletion.create(
model=llm_model,
messages=messages,
functions=[signature_get_top_headlines],
function_call=function_call
)
# 删除系统消息并附加LLM的响应
messages.pop(-1)
response = res["choices"][0]["message"]
messages.append(response)
# 调用模型请求的函数
if response.get("function_call"):
function_name = response["function_call"]["name"]
if function_name == "get_top_headlines":
args = json.loads(response["function_call"]["arguments"])
headlines = get_top_headlines(
query=args.get("query"),
country=args.get("country"),
category=args.get("category")
)
messages.append({ "role": "function", "name": "get_top_headline", "content": headlines})
为了完成机器人,我们将创建一个主循环,不断提示用户输入并提供答案:
print("\n嗨,我是NewsGPT,一个突发新闻AI助手。我可以为您提供各个国家在各个类别上的新闻。")
print("以下是一些示例提示:\n - 告诉我最近的科学发现\n - 美国最新的新闻是什么?\n - 最近埃隆·马斯克在忙些什么?")
messages = []
while True:
prompt = input("\n您想了解什么? => ")
messages.append({"role": "user", "content": prompt})
complete(messages)
# LLM可以链接函数调用,这里实现了一个限制
call_count = 0
while messages[-1]['role'] == "function":
call_count = call_count + 1
if call_count < function_call_limit:
complete(messages)
else:
complete(messages, function_call="none")
# 打印最后一条消息
print("\n\n==回复==\n")
print(messages[-1]["content"].strip())
print("\n==回复结束==")
接下来,我们需要为OpenAI和NewsAPI的API密钥定义环境变量:
export OPENAI_API_KEY=YOUR_API_KEY
export NEWS_API_KEY=YOUR_API_KEY
最后,加载文件并运行聊天机器人。
$ source .env
$ python newsgpt.py
测试NewsGPT
我们可以查询特定的主题或人物。例如:
=> 埃隆·马斯克最近在忙些什么?
正在处理...
从newsapi.org处理1篇文章
正在处理...
==回复==
埃隆·马斯克最近在与Meta的首席执行官马克·扎克伯格发生争执的新闻中频繁出现。根据《华盛顿邮报》的一篇文章,马克·扎克伯格试图让自己在科技精英中更具影响力,他与埃隆·马斯克之间似乎存在一种竞争关系。文章讨论了他们之间的持续斗争以及扎克伯格如何试图与马斯克的在线对抗和离线行为竞争。您可以在[这里](https://www.washingtonpost.com/technology/2023/06/24/elon-musk-mark-zuckerberg-fight-meta-twitter-rival/)阅读更多相关信息。
==回复结束==
该机器人还能够识别类别,使我们能够请求与健康相关的新闻:
健康领域有重要的进展吗?
正在处理...
从newsapi.org处理1000篇文章
正在处理...
==回复==
根据健康类别的最新头条新闻,以下是一些重要进展:
1. [最近移植肾脏的患者患带状疱疹眼病的病例](https://www.cureus.com/articles/164429-a-case-of-herpes-zoster-ophthalmicus-in-a-recently-transplanted-renal-patient?score_article=true) - 该病例研究介绍了一名51岁男性进行肾脏移植并患上带状疱疹眼病的临床过程。
2. [如何通过良好的饮食改善心情的主要建议](https://news.google.com/rss/articles/CBMilQFodHRwczovL3d3dy5jYW5hbDI2LmNvbS9nZW5lcmFsL2N1YWxlcy1zb24tbG9zLXByaW5jaXBhbGVzLWNvbnNlam9zLWEtc2VndWlyLXBhcmEtbWVqb3Jhci1lbC1lc3RhZG8tZGUtYW5pbW8tYS1iYXNlLWRlLXVuYS1idWVuYS1hbGltZW50YWNpb24tLTM0NTMwM9IBAA?oc=5)(西班牙语) - 本文提供了通过良好的饮食改善心情的建议。
3. [如何在夏天减掉腹部脂肪](https://news.google.com/rss/articles/CBMiXmh0dHBzOi8vbS5hei1qZW5hdGEuYmcvYS81LXpkcmF2ZS1pLWtyYXNvdGEvNjM1Mzkta2FrLWRhLXN0b3BpdGUta29yZW1uaXRlLW1hem5pbmktemEtbGlhdG90by_SAWJodHRwczovL20uYXotamVuYXRhLmJnL2EvNS16ZHJhdmUtaS1rcmFzb3RhLzYzNTM5LWthay1kYS1zdG9waXRlLWtvcmVtbml0ZS1tYXpuaW5pLXphLWxpYXRvdG8vYW1wLw?oc=5)(保加利亚语) - 本文提供了减掉夏天腹部脂肪的建议。
4. [回顾:从RCC诊断到治疗、毒性管理以及更多](https://news.google.com/rss/articles/CBMiZ2h0dHBzOi8vd3d3LmNhbmNlcm5ldHdvcmsuY29tL3ZpZXcvcmVjYXAtZnJvbS1yY2MtZGlhZ25vc2lzLXRvLXRyZWF0bWVudC10b3hpY2l0eS1tYW5hZ2VtZW50LWFuZC1iZXlvbmTSAQA?oc=5) - 本文讨论了肾细胞癌的诊断、治疗和管理。
5. [回顾突出了癌症免疫治疗中的进展和挑战](https://news.google.com/rss/articles/CBMiYmh0dHBzOi8vd3d3LmFqbWMuY29tL3ZpZXcvcmV2aWV3LWhpZ2hsaWdodHMtcHJvZ3Jlc3MtY2hhbGxlbmdlcy13aXRoLWNycy1pbi1jYW5jZXItaW1tdW5vdGhlcmFwaWVz0gEA?oc=5) - 本文重点介绍了癌症免疫治疗中的进展和挑战,重点关注CRS(细胞因子释放综合征)。
这些只是健康领域的一些最新进展。要获取更详细的信息,您可以点击提供的链接阅读完整文章。
==回复结束==
它也可以适用于各个国家,使我们能够查询有关特定地区的新闻:
最新的法国新闻是什么?
正在工作...
从newsapi.org处理34篇文章
正在工作...
==响应==
这里是法国的一些最新新闻头条:
1. "Diplôme national du brevet session 2023" - 国家教育部宣布2023年国家文凭考试。[阅读更多](https://news.google.com/rss/articles/CBMiTGh0dHBzOi8vd3d3LmVkdWNhdGlvbi5nb3V2LmZyL2RpcGxvbWUtbmF0aW9uYWwtZHUtYnJldmV0LXNlc3Npb24tMjAyMy0zNzg1NjDSAQA?oc=5)
2. "Cyclisme: la Nordiste Victoire Berteau sacrée championne de France après sa victoire sur les routes de Cassel" - Nord的Victoire Berteau在法国自行车锦标赛上获得冠军。[阅读更多](https://news.google.com/rss/articles/CBMiiQFodHRwczovL3d3dy5mcmFuY2V0dmluZm8uZnIvc3BvcnRzL2N5Y2xpc21lL2N5Y2xpc21lLXZpY3RvaXJlLWJlcnRlYXUtc2FjcmVlLWNoYW1waW9ubmUtZGUtZnJhbmNlLWFwcmVzLXNhLXZpY3RvaXJlLWEtY2Fzc2VsXzU5MDg4NDcuaHRtbNIBAA?oc=5)
3. "Guerre en Ukraine: comment les capitales étrangères réagissent-elles à la rébellion de la milice Wagner en Ru" - 外国首都对乌克兰Wagner民兵的反叛作出反应。[阅读更多](https://news.google.com/rss/articles/CBMiwAFodHRwczovL3d3dy5mcmFuY2V0dmluZm8uZnIvbW9uZGUvZXVyb3BlL21hbmlmZXN0YXRpb25zLWVuLXVrcmFpbmUvZ3VlcnJlLWVuLXVrcmFpbmUtY29tbWVudC1sZXMtY2FwaXRhbGVzLWV0cmFuZ2VyZXMtcmVhZ2lzc2VudC1lbGxlcy1hLXJlYmVsbGlvbi1kZS1sYS1taWxpY2Utd2FnbmVyLWVuLXJ1c3NpZV81OTA4NzY2Lmh0bWzSAQA?oc=5)
4. "Marche des fiertés LGBT+: six jeunes mineurs interpellés pour homophobie" - 六名未成年人因同性恋恐惧症被捕。[阅读更多](https://news.google.com/rss/articles/CBMifmh0dHBzOi8vd3d3LnJ0bC5mci9hY3R1L2p1c3RpY2UtZmFpdHMtZGl2ZXJzL21hcmNoZS1kZXMtZmllcnRlcy1sZ2J0LXNpeC1qZXVuZXMtbWluZXVycy1pbnRlcnBlbGxlcy1wb3VyLWhvbW9waG9iaWUtNzkwMDI3Nzg4M9IBAA?oc=5)
5. "ATP 500 Queen's - De Minaur a dominé Rune avec autorité: le film de la demi-finale" - Alex de Minaur在ATP 500 Queen's半决赛中以威权战胜Rune。[阅读更多](https://news.google.com/rss/articles/CBMimwFodHRwczovL3d3dy5ldXJvc3BvcnQuZnIvdGVubmlzL2F0cC1sb25kcmVzLzIwMjMvYXRwLTUwMC1xdWVlbi1zLXN1aXZlei1sYS1kZW1pLWZpbmFsZS1lbnRyZS1hbGV4LWRlLW1pbmF1ci1ldC1ob2xnZXItcnVuZS1lbi1kaXJlY3Rfc3RvOTY3MTM4My9zdG9yeS5zaHRtbNIBAA?oc=5)
这只是法国的一些最新新闻头条。如果您想了解有关任何具体新闻文章的更多信息,请告诉我。
==响应结束==
改进的想法
这个简单的机器人非常强大,即使只是一个函数调用。现在,想象一下如果我们集成更多的功能,会有什么可能性。以下是一些增强NewsGPT的想法:
-
检索原始文章以获取摘要和分析新闻。我们需要突破付费墙,进行网页抓取,或者检查是否有RSS提供内容。
-
添加更多的端点。NewsAPI提供了按日期、类别搜索新闻和通过来源进行过滤的端点。
-
整合额外的集成,例如从天气或金融等来源获取实时数据。
结论
函数调用是OpenAI的GPT模型中的一个强大功能,使它们能够以更确定性和结构化的方式与外部工具和API进行交互。这个功能为更具动态和响应能力的人工智能应用奠定了基础,能够提供实时信息并执行以前无法完成的任务。
祝您愉快开发!