其中对开发者而言,最兴奋的就是 function calling 的功能了,这个基本就是 ChatGPT Plugin 底层实现的基础了,有了这个能力我们也可以打造自己的“插件商城”了。下面我们就一起看一下 function calling 的具体功能。
OpenAI 的 GPT 本身具有很强的智能,但也有知识陈旧以及缺少“手脚”问题。比如,gpt-4 模型的训练数据都是 2021 年 9 月之前的数据,所以他并不知道 2023 年苹果发布了 visionOS 系统;另外,现在的 ChatGPT 并不能执行一些动作,比如帮忙发送邮件,现在能做的仅仅是编写邮件。
简单讲 Function Calling 功能其实就是在 Chat Completions 接口中新增了一个 functions 的可选入参,该参数是一个列表可以支持传入多个函数的声明,在声明中告诉 GPT 有这些函数可以被调用,他可以借助这些函数完成自己不能完成的的一些事情,或者说把自己的任务完成的更加出色。从这角度来理解 Function Calling 和 ChatGPT 中的 Plugin 并没有本质区别。前者是面对开发者的基础能力,后者是面对用户的基础交互体验。
下面我们就来详细看一下 Function Calling 到底是什么,怎么用,以及一些常见的使用案例。
Function Calling 是 Chat Completions 接口(下文统一称 Chat 接口)中一个参数,想要了解 Function Calling 我们需要先了解一下 Chat 接口的一些基本概念。
import openai openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who won the world series in 2020?"}, {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, {"role": "user", "content": "Where was it played?"} ] )messages 表示的是对话列表,包含对话的角色以及对应的内容,此次的更新中新增了 function 这个角色,目前共有以下内容:
curl <https://api.openai.com/v1/chat/completions> -u :$OPENAI_API_KEY -H 'Content-Type: application/json' -d '{ "model": "gpt-3.5-turbo-0613", "messages": [ {"role": "user", "content": "What is the weather like in Boston?"} ], "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"] } } ] }'返回的 JONS 形式大致如下(下文会详细讲解字段含义):
{ "id": "chatcmpl-123", ... "choices": [{ "index": 0, "message": { "role": "assistant", "content": null, "function_call": { "name": "get_current_weather", "arguments": "{ \"location\": \"Boston, MA\"}" } }, "finish_reason": "function_call" }] }Function Calling 调用流程
下面就以用户询问当前的天气的场景来详细讲解下 Function Calling 是如何运行的。
{ "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"] } }使用上述函数声明请求 OpenAI 接口的 Python 代码大致如下:
def chat(): # Step 1: send the conversation and available functions to GPT messages = [{"role": "user", "content": "What's the weather like in Boston?"}] 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"], }, } ] response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, functions=functions, function_call="auto", # auto is default, but we'll be explicit )注意:
{ "id": "chatcmpl-123", ... "choices": [{ "index": 0, "message": { "role": "assistant", "content": null, "function_call": { "name": "get_current_weather", "arguments": "{ \"location\": \"Boston, MA\"}" } }, "finish_reason": "function_call" }] }其解析的代码逻辑大致如下:
def parse_response(response): response_message = response["choices"][0]["message"] # 堆代码 duidaima.com # Step 2: check if GPT wanted to call a function if response_message.get("function_call"): # Step 3: call the function # Note: the JSON response may not always be valid; be sure to handle errors available_functions = { "get_current_weather": get_current_weather, } # only one function in this example, but you can have multiple function_name = response_message["function_call"]["name"] fuction_to_call = available_functions[function_name] function_args = json.loads(response_message["function_call"]["arguments"]) function_response = fuction_to_call( location=function_args.get("location"), unit=function_args.get("unit"), )代码的大致逻辑如下:
注意,OpenAI 返回的 JSON 信息并不能始终保证其有效性,需要自己做容错处理。
def get_current_weather(location, unit="fahrenheit"): """Get the current weather in a given location""" weather_info = { "location": location, "temperature": "72", "unit": unit, "forecast": ["sunny", "windy"], } return json.dumps(weather_info)首先函数在调用时 location 时必传的,unit 有默认值可以不用传递。这里为了方便起见并没有真正的去请求天气的接口,而是写死了一个 mock 的数据。在自己的业务中,需要将其替换成自己的真实业务逻辑。
下一步就是需要将这里的执行数据上报给 OpenAI 进行进一步的整合了。
def chat_function_call(): # 第 4 步: 将函数执行的结果上报给 GPT messages.append(response_message) # extend conversation with assistant's reply messages.append( { "role": "function", "name": function_name, "content": function_response, } ) # extend conversation with function response second_response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, ) # get a new response from GPT where it can see the function response return second_response完整的的 curl 代码大致如下:
curl <https://api.openai.com/v1/chat/completions> -u :$OPENAI_API_KEY -H 'Content-Type: application/json' -d '{ "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"] } } ] }'对应的返回结果如下:
{ "id": "chatcmpl-123", ... "choices": [{ "index": 0, "message": { "role": "assistant", "content": "The weather in Boston is currently sunny with a temperature of 22 degrees Celsius.", }, "finish_reason": "stop" }] }
解析上面的 message 中的 content 内容,就可以展示响应的信息给到用户了。