官方文档:openai.com/blog/functi…
流程
- 用户调用gpt输入会话
- gpt提取参数和要执行的方法名 (重点: gpt理解用户会话,和函数列表,结构化信息)
- 本地执行方法获得返回值
- 系统再次调用gpt返回最终信息;
细节
- 最好在会话开始设置system对话, 告诉gpt接下来的回答要优先从问题里找到函数的入参,没有入参的时候不要预设入参 ;(system信息不对用户展示,可以理解为chatgpt脑海中的低语)
[
{ "role": "system", "content": " dont make assumptions about what values to plug into function. ask fro clarification if a user request is ambiguous"},
... // 后续进行正常的对话
]
- 当没有指定函数的时候,chatgpt会从function里根据description找到符合的方法;
functions=[{}...],
function_call="auto"
- 函数的定义OpenAI直接采用了JSON-Schema的规范;
- node的openai sdk的最新v4.0.0 beta版本:github.com/openai/open…(6.19日发布)支持了函数调用以及一系列文件,图片相关的api能力,暂时还不能从npm里下载,等作者回复一下什么时候可以体验;
- 函数调用流程伪代码:
// 定义实际调用函数的映射关系
const functionsMap = {
"get_text_from_url": get_text_from_url,
}
const response1 = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions,
function_call="auto"
)
const response_message = response1["choices"][0]["message"]
let rtn_message = response1["choices"][0]["message"]
// 如果发现有调用函数的情况,返回信息里会有function_call
if( response_message.indexOf("function_call")>-1){
// 找到需要调用的函数,并将ChatGPT给的参数传进去
const function_name = response_message["function_call"]["name"]
const fuction_to_call = functionsMap[function_name]
const function_args = json.parse(response_message["function_call"]["arguments"])
// 用这种方式可以调起任意python函数,不用像官网那样还要指定参数名
const function_response = fuction_to_call.call(function_args)
// 获取到函数调用结果后,需要将结果拼接到对话记录里,并再次调用ChatGPT
messages.append(response_message)
messages.append(
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
// 二次调用的返回结果里就是我们预期的结果了
const response2 = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
)
rtn_message = response2["choices"][0]["message"]
}
return rtn_message['content']
不管是从官网查询天气的示例,还是从我这个抓取网页的示例来看,接入函数调用的能力并不复杂。另外需要注意到的一点,虽然这个功能叫函数调用,但ChatGPT并不会帮你去调这些函数,而是帮你判断何时去调用这些函数,给你调用函数所需要的参数,最终肯定得是由你自己去调用函数的。从它的返回数据格式可以看出,当前版本的函数调用还有个局限点,就是当前版本只能在一次对话中调起一个函数,如果遇到那种需要多次调用的操作,就只能通过多轮对话的方式实现了,这里也提供一点多轮会话多轮递归调用的实践代码
// 首次调用后执行
const handleFunc = async (chatRes, messages) => {
if (chatRes?.indexOf('function_call') > -1) {
const parseData = JSON.parse(chatRes || '{}');
const function_name = parseData['function_call']['name'];
const function_args = parseData['function_call']['arguments'];
const prompt = functionsMap[function_name]?.desc;
const hasMore = functionsMap[function_name]?.hasMore;
const executed = functionsMap[function_name]?.executed;
if (function_name) {
const data = await executeFunctions(function_name, function_args);
if (!executed) {
messages.push({ role: Role.function, name: function_name, content: JSON.stringify(data), });
const funcRes = await callAI({ messages, functions, functionCall: hasMore ? 'auto' : 'none', prompt, });
setChatList((pre) => { return pre.concat([ { role: Role.function, name: function_name, content: { text: JSON.stringify(data) }, }, ]); });
const res = await handleFunc(funcRes?.data, messages);
return res;
}
} else {
return chatRes;
}
}
return chatRes;
}
另外对于决策的可靠性还有待验证,按官网的建议:
We strongly recommend building in user confirmation flows before taking actions that impact the world on behalf of users (sending an email, posting something online, making a purchase, etc). 我们强烈建议在代表用户采取影响世界的动作(发送电子邮件、在线发布内容、进行购买等)之前,先由用户确认。