前言
作为一名非科班出身的AI工程师爱好者,很多跟我类似的朋友,都是通过半年前OpenAI提供的ChatGPT来入门AI的。
因为在AI面前,你我都还是孩子;所以在面对OpenAI新鲜出炉的核心功能function calling时,大多数人都是一知半解,懵懵懂懂。
很多营销号、自媒体都会表示,function calling牛逼!但它是干嘛的呢?很多人点到为止,很多人讲不明白。
我斗胆描述一下场景,带你过一个例子,你就懂了。
ChatGPT v.s. OpenAI API
开始之前,我简单介绍一下ChatGPT和OpenAI API(以下简称API)的区别。
ChatGPT是一款产品,面向大众用户(即不会写代码的人);而API是一个接口/函数,面向开发者。
ChatGPT是由OpenAI通过调用公有(public)API + 尚未提供的私有(private)API + 后端、前端、客户端API来开发实现的。
举一个不恰当的比方,微信是一款产品,它不提供公有API,只能通过逆向工程的方式来获取私有API,例如[CMessageMgr AsyncOnAddMsg:MsgWrap:]
。
痛点
毋庸置疑,ChatGPT/API的功能非常强大。但它当前的核心痛点之一是信息不能实时更新,也不能联网,所以一些相对“小众”的信息,ChatGPT/API回答不了,如:
- 我问:Who is snakeninny?
- AI答:I’m sorry, but I don’t have any information about “snakeninny”. It’s possible that it might be a username or nickname for someone on a particular platform or community, but without further context, I cannot provide a more accurate answer.
为了解决这个痛点,OpenAI提供了两种解决方案。
方案一:ChatGPT Plugins
它能让ChatGPT“联网”,通过第三方插件(plugin)的方式供ChatGPT调用;第三方插件需要提前注册,并提供足够多的信息,以便让ChatGPT了解这个插件是干嘛的。当ChatGPT认为有必要让“外援”出场时,就会调用第三方插件,获取到第三方插件提供的信息,然后回复给用户。
方案二:Function Calling
它能让API“联网”,通过第三方函数(function)的方式供新模型gpt-4-0613
或gpt-3.5-turbo-0613
调用;第三方函数不需要提前注册,但需要提供足够多的信息,以便让gpt-4-0613
或gpt-3.5-turbo-0613
了解这个函数是干嘛的。当gpt-4-0613
或gpt-3.5-turbo-0613
认为有必要让“外援”出场时,就会调用第三方函数,获取到第三方函数提供的信息,然后回复给用户。
简而言之,ChatGPT Plugins和Function calling的出发点类似,都是“联网”。最大的区别是场景,前者服务于ChatGPT,而后者服务于API。
大致流程
以API为例,当没有function calling时,API的大致流程是这样的:
- 用户提问 → API搞不搞得定,都擅自回答 → 用户收到
而有了function calling后,API的流程变成了这样:
- 用户提问 → API如果搞的定,则直接回答 → 用户收到
- 用户提问 → API如果搞不定,则向“外援”求助 → Function Calling回答 → OpenAI API收到然后润色 → 用户收到
注意,API搞不搞得定,是由gpt-4-0613
或gpt-3.5-turbo-0613
自行决定的,而不是由开发者决定的。
上demo
还是一知半解?没关系。我写个demo给你看,你就懂了。
使用Function Calling的流程
上demo前,我先把function calling的流程梳理一下。
- 开发者定义一个第三方函数。
- 在调用API时,开发者把第三方函数作为参数传给模型。
- 模型来判断何时需要调用第三方函数(注意:模型只判断,然后由开发者来执行;就像ChatGPT/API只是大脑,四肢和躯干需要第三方来执行)。具体而言,开发者需要关注模型的返回值。
- 开发者调用第三方函数后,要把返回值传给模型。具体而言,开发者要再次调用API。
- 此时模型的返回值则是最终的回答,可以发给用户了。
demo
参考官方文档
import openai
import json
openai.api_key = "改成你自己的API_KEY"
# 用户问:“这TM谁啊??”
messages = [
{"role": "user", "content": "Who is snakeninny?"}
]
# 第三方函数,这里我是本地调用,不联网;当然你可以改代码,联网
def who_is_snakeninny(value):
if value == "snakeninny":
result = {"result" : "He's an AI n00b."}
elif value == "大名狗剩":
result = {"result" : "He's an AI h0bby1st."}
elif value == "沙梓社":
result = {"result" : "He's an AI enthus1ast."}
else:
result = {"result" : "I don't know him."}
return json.dumps(result) # 官方文档里的返回值格式是JSON
# 第三方函数的介绍,供AI理解和使用
who_is_snakeninny_description = {
"name": "who_is_snakeninny",
"description": "Describe snakeninny", # 这个函数是干嘛的
"parameters": {
"type": "object",
"properties": {
"value": {
"type": "string",
"description": "The name of a person", # 这个参数是干嘛的
}
},
"required": ["value"],
},
}
# 调用API,把第三方函数作为参数传给模型
def get_completion(messages):
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613", # 模型可选,目前有gpt-4-0613和gpt-3.5-turbo-0613
messages=messages, # 其实就是prompt
functions=[who_is_snakeninny_description], # 如果需要“外援”,就需要这个参数。如果没有这个参数,就不会调用“外援”
)
return completion
first_response = get_completion(messages)
# 关注返回值
finish_reason = first_response["choices"][0]["finish_reason"]
message = first_response["choices"][0]["message"] # 其实就是completion
if finish_reason == "stop": # 这时模型可以自己hold住,不需要“外援”
content = message["content"] # 可以直接返回给用户了
elif finish_reason == "function_call": # 模型感觉自己hold不住,需要“外援”上场
function_call = message["function_call"]
function_name = function_call["name"] # 第三方函数名
function = locals()[function_name] # 第三方函数
arguments = function_call["arguments"] # 第三方函数的参数名
name = json.loads(arguments)["value"] # 第三方函数的参数
function_response = function(name) # 调用第三方函数拿到的返回值
messages.append(message) # 上下文的固定用法
messages.append( # 上下文固定用法
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
second_response = get_completion(messages) # 再次调用API,模型根据我们收集的messages再次组织语言
message = second_response["choices"][0]["message"]
content = message["content"] # 可以直接返回给用户了
print(content)
效果
-
问:Who is snakeninny?
-
答:Snakeninny is an AI n00b.
-
问:Who is 大名狗剩?
-
答:大名狗剩 is an AI hobbyist.
-
问:Who is 沙梓社?
-
答:沙梓社 is an AI enthusiast.
-
问:Who is 大名snake?
-
答:I’m sorry, but I don’t have any information about 大名snake.
-
问:Who is Jack Ma?
-
答:I’m sorry, but I don’t have any information about Jack Ma.
结尾
可以看到,gpt-3.5-turbo-0613
认识了snakeninny,但不认识Jack Ma了。但是如果我注释掉这一行:
# 调用API,把第三方函数作为参数传给模型
def get_completion(messages):
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613", # 模型可选,目前有gpt-4-0613和gpt-3.5-turbo-0613
messages=messages, # 其实就是prompt
# functions=[who_is_snakeninny_description], # 如果需要“外援”,就需要这个参数。如果没有这个参数,就不会调用“外援”
)
return completion
gpt-3.5-turbo-0613
就认识Jack Ma了:
Jack Ma, whose full name is Ma Yun, is a Chinese entrepreneur and philanthropist. He is the co-founder and former executive chairman of Alibaba Group, one of the world’s largest e-commerce companies. Born on September 10, 1964, in Hangzhou, China, Ma grew up in a middle-class family and faced several rejections before finding success. He launched Alibaba in 1999, initially as an online marketplace connecting Chinese exporters with overseas buyers. Under his leadership, the company expanded its services to include various platforms such as AliExpress, Taobao, and Tmall. Jack Ma is known for his inspirational leadership style, innovative approaches to business, and his vision of making it easy to do business anywhere in the world. He stepped down from his role as Alibaba’s executive chairman in 2019 but continues to be involved in philanthropic initiatives and investments in various industries.
我还没有深究,猜测是因为我的第三方函数造成了“污染”;模型还区分不出“describe snakeninny”和“describe a person”的区别,在“外援”和自己间“左右为难”。
或许进一步prompt engineering,能让模型更清晰地理解我的描述,选择更合适的解决方案。
留待有空再测吧。
谢谢!