春水碧于天,画船听雨眠。小伙伴们好,我是微信公众号《小窗幽记机器学习》的小编:卖五连鞭的小男孩。紧接前面几篇ChatGPT Prompt工程和应用系列文章:
更多、更新文章欢迎关注 微信公众号:小窗幽记机器学习。后续会持续整理模型加速、模型部署、模型压缩、LLM、AI艺术等系列专题,敬请关注。
今天这篇小作文以实战的方式介绍OpenAI最近升级的函数调用功能。
6月13日OpenAI重磅升级ChatGPT,主要包括新增 API函数调用、 更新和更可控制的gpt-4和gpt-3.5-turbo版本、新推出的gpt-3.5-turbo 支持16k的上下文输入、陆续开放gpt-4 API不再设置waitlist等等。今天这篇小作文主要着重介绍其中的 函数调用(Function calling) 功能。该功能一定程度上对LangChain构成威胁,新增的Function calling功能将使得LangChain里将近30%的代码可以删掉,且各个应用服务结果的稳定性将大大提升。当然,LangChain也迅速做出应对,在OpenAI官推发布更新的10分钟之内,LangChain立马宣布"已经在做兼容工作了",并且在不到一个小时就发布了新版本以支持OpenAI的这一新功能。此外,LangChain还提供将开发者已经写好的tools转换成OpenAI的functions的工具函数。关于LangChain的具体使用后续会补充介绍,小伙伴们可以关注留意下。
下面以天气问答为例,比如“今天深圳天气如何,适合爬山吗?”,介绍如何使用ChatGPT的API和函数调用实现一个天气问答服务。关于函数调用的更多细节可以查阅官方博客。
import json
import openai
import requests
import os
from tenacity import retry, wait_random_exponential, stop_after_attempt
from termcolor import colored
GPT_MODEL = "gpt-3.5-turbo-0613"
openai.api_key = "sk-xxx"
os.environ['HTTP_PROXY'] = "xxx"
os.environ['HTTPS_PROXY'] = "xxx"
@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):
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + openai.api_key,
}
json_data = {"model": model, "messages": messages}
if functions is not None:
json_data.update({"functions": functions})
print("functions json_data=", json_data)
if function_call is not None:
json_data.update({"function_call": function_call})
print("function_call json_data=", json_data)
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
将待调用的函数进行如下声明。其实就是接口定义,后续会根据description
的语义描述选择对应的目标函数。
functions = [
{
"name": "get_current_weather",
"description": "获取当前的天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "省市, 比如:深圳市",
},
"format": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,是摄氏温度还是华氏温度。从用户所在位置进行推断。",
},
},
"required": ["location", "format"],
},
},
{
"name": "get_n_day_weather_forecast",
"description": "获得未来N天的天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "省市, 比如:深圳市",
},
"format": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,是摄氏温度还是华氏温度。从用户所在位置进行推断。",
},
"num_days": {
"type": "integer",
"description": "预测的天数",
}
},
"required": ["location", "format", "num_days"]
},
},
]
为进一步说明函数调用的使用,以下介绍2个版本,函数未定义和真实定义函数之后ChatGPT的返回,小伙伴们记得对比两者的差异。
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "今天的天气如何?"})
chat_response = chat_completion_request(
messages, functions=functions
)
print("chat_response=", chat_response.json())
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print(assistant_message)
输出assistant_message
结果如下:
{'role': 'assistant',
'content': None,
'function_call': {'name': 'get_current_weather',
'arguments': '{\n "location": "深圳市",\n "format": "celsius"\n}'}}
可以看出,此时解析的结果是调用get_current_weather
这个函数,但是由于该函数未定义,所以content
字段为空。后文在定义待调用函数章节介绍。此外,如果在get_current_weather
中默认城市改为北京
,上述arguments
字段中对应的location
会自动变为北京
:
{'role': 'assistant',
'content': None,
'function_call': {'name': 'get_current_weather',
'arguments': '{\n"location": "北京市",\n"format": "celsius"\n}'}}
如果咨询某个城市未来X天的天气,那么此时需要2个信息:城市和时间。
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "深圳未来x天的天气如何?"})
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
assistant_message
输出结果如下:
{'role': 'assistant', 'content': '请问您要查询深圳未来几天的天气?'}
补充天数信息:
# 补充天数信息
messages.append({"role": "user", "content": "7天"})
chat_response = chat_completion_request(
messages, functions=functions
)
print(chat_response.json()["choices"][0])
输出结果如下:
{'index': 0,
'message': {'role': 'assistant',
'content': None,
'function_call': {'name': 'get_n_day_weather_forecast',
'arguments': '{\n"location": "深圳市",\n"format": "celsius",\n"num_days": 7\n}'}},
'finish_reason': 'function_call'}
同理,对于城市信息缺失也是如此处理。
# 提供完整提供天数
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "深圳未来5天的天气如何?"})
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print(assistant_message)
输出结果如下:
{'role': 'assistant',
'content': None,
'function_call': {'name': 'get_n_day_weather_forecast',
'arguments': '{\n "location": "深圳市",\n "format": "celsius",\n "num_days": 5\n}'}}
现在补充get_current_weather
的定义。
# 补充 get_current_weather 的定义
# 随意实现了天气查询, 实际应该对接气象局api或者其他提供天气查询的api
def get_current_weather(location="北京市", format="celsius"):
if location == "深圳市":
return "龙舟雨,很大,超级大!温度20-25摄氏度"
elif location == "北京市":
return "风和日丽,阳光明媚!温度35-40摄氏度"
return "安好,一切顺利,28摄氏度"
def get_current_weather_json(location="北京市", format="celsius"):
if location == "深圳市":
return { "temperature": 20, "unit": "celsius", "description": "龙舟雨,很大,超级大!温度20-25摄氏度"}
elif location == "北京市":
return { "temperature": 36, "unit": "celsius", "description": "风和日丽,阳光明媚!温度35-40摄氏度"}
return {"temperature": 28, "unit": "celsius", "description": "安好,一切顺利,28摄氏度"}
test_text = "今天深圳天气如何,适合爬山吗?"
# 需要使用过程手动调用上述函数,再将结果加进入
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": test_text})
chat_response = chat_completion_request(
messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print("assistant_message=",assistant_message)
输出结果如下:
assistant_message= {'role': 'assistant', 'content': None, 'function_call': {'name': 'get_current_weather', 'arguments': '{\n "location": "深圳市",\n "format": "celsius"\n}'}}
此时chat_response.json()["choices"][0]
内容如下:
{'index': 0,
'message': {'role': 'assistant',
'content': None,
'function_call': {'name': 'get_current_weather',
'arguments': '{\n "location": "深圳市",\n "format": "celsius"\n}'}},
'finish_reason': 'function_call'}
需要根据function_call
的信息做函数调用判断,具体如下。
full_chat_message = chat_response.json()["choices"][0]
print("full_chat_message=",full_chat_message)
fun_exec_result = ""
if full_chat_message["finish_reason"] == "function_call":
# 判断 assistant_message["function_call"]["name"]执行不同函数
if assistant_message["function_call"]["name"] == "get_current_weather":
arguments = eval(assistant_message["function_call"]["arguments"])
fun_exec_result = get_current_weather(arguments["location"])
print("fun_exec_result=", fun_exec_result)
else:
print("error, check")
if fun_exec_result:
#将函数调用结果补充到会话中
messages.append({"role": "user", "content": fun_exec_result})
#再一次调用GPT
next_chat_response = chat_completion_request(messages, functions=functions)
next_full_chatmessage = next_chat_response.json()["choices"][0]
print("next_full_chatmessage=", next_full_chatmessage)
输出结果如下:
next_full_chatmessage= {'index': 0, 'message': {'role': 'assistant', 'content': '根据当前天气情况,在深圳市天气很糟糕,下着大雨。温度在20-25摄氏度之间。这样的天气条件可能不适宜爬山,因为雨势大可能导致路滑、能见度低等不利条件。建议您等待天气好转后再考虑爬山。请注意安全。'}, 'finish_reason': 'stop'}
这篇小作文以天气咨询为例,说明如何在使用ChatGPT过程中融合自定义函数的使用,从而为用户提供更多样的服务,类似插件。将自定义函数的结果添加到与ChatGPT对话的上下文中,从而具有更好的模块编排效果。这一定程度上是对三方LangChain能力的稀释,LangChain危矣+1!