前端玩转function call

491 阅读3分钟

官方文档:openai.com/blog/functi…

流程

  1. 用户调用gpt输入会话
  2. gpt提取参数和要执行的方法名 (重点: gpt理解用户会话,和函数列表,结构化信息)
  3. 本地执行方法获得返回值
  4. 系统再次调用gpt返回最终信息;

细节

  1. 最好在会话开始设置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"},
   ... // 后续进行正常的对话
]
  1. 当没有指定函数的时候,chatgpt会从function里根据description找到符合的方法;
functions=[{}...],
function_call="auto" 
  1. 函数的定义OpenAI直接采用了JSON-Schema的规范;
  2. node的openai sdk的最新v4.0.0 beta版本:github.com/openai/open…(6.19日发布)支持了函数调用以及一系列文件,图片相关的api能力,暂时还不能从npm里下载,等作者回复一下什么时候可以体验;
  3. 函数调用流程伪代码:
// 定义实际调用函数的映射关系
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). 我们强烈建议在代表用户采取影响世界的动作(发送电子邮件、在线发布内容、进行购买等)之前,先由用户确认。