OpenAI 函数调用(1) – ChatGPT Plugin 是如何实现的?现已开放

OpenAI 最新发布的 函数调用是什么?怎么使用?和 ChatGPT Plugin 的关系是怎样的?一文带你全部掌握。

背景

最近 OpenAI 发布了一些模型及 API 的更新,主要有以下内容:

  1. Chat Completions 接口中新增对 function calling(函数调用)的支持;
  2. gpt-3.5 发布的新的支持 16k 上下文的模型 gpt-3.5-turbo-16k ,直接变成了原来的 4 倍;
  3. 发布了新的支持函数调用的模型 gpt-3.5-turbo-0613gpt-4-0613

其中对开发者而言,最兴奋的就是 function calling 的功能了,这个基本就是 ChatGPT Plugin 底层实现的基础了,有了这个能力我们也可以打造自己的“插件商城”了。下面我们就一起看一下 function calling 的具体功能。

概述

GPT 本身的局限

OpenAI 的 GPT 本身具有很强的智能,但也有知识陈旧以及缺少“手脚”问题。比如,gpt-4 模型的训练数据都是 2021 年 9 月之前的数据,所以他并不知道 2023 年苹果发布了 visionOS 系统;另外,现在的 ChatGPT 并不能执行一些动作,比如帮忙发送邮件,现在能做的仅仅是编写邮件。

具体表现为:

  1. LLM 的知识是基于模型训练时使用的数据及知识,对于后续的新的事实其并不知道;
  2. LLM 类似于“缸中之脑”,其有很高的智能但是缺少一些行动的能力;

目前的解决方案

上述问题社区中也有一些解决方案,如 LangChain 之类的工具。除此之外,OpenAI 官方也推出了 Plugin 的功能,允许开发者可以给 LLM 提供一些能力来解决上述问题,甚至是增强其能力提供了更多模态的信息输出。

上述的解决方案中也都有各自的问题:

  • LangChain 的解决方案毕竟不是官方方案,不可避免的存在一些稳定性的隐患,除此之外,LangChain 目前仅支持 Python 以及 JS 代码,其他的语言没有现成的解决方案;
  • OpenAI Plugin 的方式是正解,但是只能在 ChatGPT 这个封闭的生态中使用,对于一些垂直领域的 LLM 应用开发而言,并不能直接使用;

Function Calling

简单讲 Function Calling 功能其实就是在 Chat Completions 接口(下文统一称 Chat 接口)中新增了一个 functions 的可选入参,该参数是一个列表可以支持传入多个函数的声明,在声明中告诉 GPT 有这些函数可以被调用,他可以借助这些函数完成自己不能完成的的一些事情,或者说把自己的任务完成的更加出色。

从这角度来理解 Function Calling 和 ChatGPT 中的 Plugin 并没有本质区别。前者是面对开发者的基础能力,后者是面对用户的基础交互体验。

Function Calling 的出现基本上可以很好的解决之前遗留的一些问题:

  1. 突破编程语言的限制,目前发布的功能是云端接口,只要可以进行网络请求的语言都可以实现此功能;
  2. 提升函数调用的稳定性,官方出品稳定性有保证,但并不是没有稳定性的问题还是要添加对应容错逻辑;
  3. 提示 API 易用性,垂直领域的 LLM 应用开发少不了 API 调用,这部分的使用越简单其上层的 LLM 应用月繁荣;

下面我们就来详细看一下 Function Calling 到底是什么,怎么用,以及一些常见的使用案例。

Chat Completions

Function Calling 是 Chat 接口中一个参数,想要了解 Function Calling 我们需要先了解一下 Chat 接口的一些基本概念。

接口简介

Chat 接口ChatGPT 聊天应用中使用的接口,区别于之前的 Completions 接口,它将用户和 GPT 的对话以列表的形式进行进行管理,并不是将所有的对话的统一拼接在一个字符串中。除此之外,Chat 接口还明确了 role 的概念,系统的上下文信息通过 system 来表示。

目前接口仅支持 gpt-3.5 和 gpt-4 的系列模型。其形式大致如下:

python
复制代码
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 这个角色,目前共有以下内容:

  1. system:表示系统上下文信息,确定当前对话的基本格调,一般接口只包含一条该内容;
  2. user:用户的提问的问题;
  3. assistant:GPT 返回的回答内容;
  4. function:函数执行的结果,需要将此信息传递给 GPT,以便 GPT 更加次内容做出进一步的回答;

Function Calling 基本定义

这次的 Function Calling 功能并不是新接口,而是在 Chat 接口上扩展而来的。在请求接口以及接口返回上都有调整。主要内容如下:

  1. 接口请求:新增 functions 参数和messages平级,role 中新增 function 枚举标识是函数执行的结果;
  2. 接口返回:choices 的 message 中新增 function_call 字段,包含函数执行必须的参数信息,与文本内容 content 平级,但基本不会同时出现;

Curl 请求形式大致如下(下文会详细讲解字段含义):

bash
复制代码
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 形式大致如下(下文会详细讲解字段含义):

json
复制代码
{ "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 整个功能的调用顺序大致如下:

  1. 声明函数并请求 API:定义当前函数的名称,描述,以及对应的参数信息,并执行 OpenAI 的 Chat 接口;
  2. 执行本地函数:接受接口返回,并解析对应的函数参数信息,根据对应的参数信息调用本地函数;
  3. 上报执行结果:将本地函数执行的结果上报给 OpenAI Chat 接口进行汇总、总结;

详细的时序图如下:

OpenAI 函数调用(1) - ChatGPT Plugin 是如何实现的?现已开放

下面就以用户询问当前的天气的场景来详细讲解下 Function Calling 是如何运行的。

基本使用

第 1 步:声明函数并请求 API

其中 functions 是一个列表包含多个函数对象,每个对象中包含:

  1. name:函数的名字,但函数被命中时接口会返回此处定义的名称;
  2. description:函数的描述,OpenAI 会根据此描述根据场景决定是否需要调用此函数,所以这里一定要描述清楚;
  3. parameters:函数参数,当前定义的函数需要哪些传参,这些参数的类型以及可空性都需要在这里声明;

首先我们先定义一个获取当前天气的函数声明,并且这个函数需要两个参数:

  1. location:位置信息,即想要查询那个地方的天气信息,类型是一个字符串,非空;
  2. unit:天气的单位,类型是一个字符串,是两个枚举摄氏度与华氏度,可空;

其声明大致如下:

json
复制代码
{ "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 代码大致如下:

py
复制代码
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 )

注意:

  1. 这里使用的模型是 gpt-3.5-turbo-0613 ,目前支持此功能的模型还有 gpt-4-0613 其他的模型可能并不支持函数调用。
  2. function_call 传参数表示 OpenAI 如何使用函数,如果 functions 参数不为空时默认值为 auto,如果为空时默认值为 none;

第 2 步:执行本地函数

若声明的函数被命中之后,GPT 会返回本地函数执行所需的必要参数(第一步中定义的),在执行本地函数之前需要先将其解析处理。GPT 的 Response 和之前相比会有以下不同之处:

  1. content:content 内容为空,不仔包含对应的文本信息;
  2. function_call:在 choices 列表中新增 function_call 字段,该字段中包含需要调用的函数信息;
  3. finish_reason:finish_reason 对应的值为 function_call ,表明是因为函数调用而停止的;

其返回的内容形式大致如下:

kotlin
复制代码
{ "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" }] }

其解析的代码逻辑大致如下:

python
复制代码
def parse_response(response): response_message = response["choices"][0]["message"] # 检测是否需要调用函数 if response_message.get("function_call"): # 调用函数 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"), )

 代码的大致逻辑如下:

  1. 首先判断 function_call 是否存在,若存在则表示需要调用函数;
  2. 使用 JSON 解析对应的函数名以及对应的参数信息;

注意,OpenAI 返回的 JSON 信息并不能始终保证其有效性,需要自己做容错处理。

其中 get_current_weather 这个函数的具体实现如下:

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 进行进一步的整合了。

第 3 步:将结果上报给 OpenAI

这一步我们会将函数的执行结果上传给 OpenAI,需要注意的有:

  1. 对话的 messages 中需要包含上一步 OpenAI 返回的 function_call 信息;
  2. 上报结果的 role 为 function,并且还需要通过 name 属性告知对应的函数名;

详细的代码如下:

python
复制代码
def chat_function_call(): # 第 3 步: 将函数执行的结果上报给 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 代码大致如下:

kotlin
复制代码
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"] } } ] }'

对应的返回结果如下:

kotlin
复制代码
{ "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 内容,就可以展示响应的信息给到用户了。

总结

Chat Completions 接口中新增的 Function Calling 功能可以很好解决 GPT 知识陈旧以及缺少手脚的问题,是对 GPT 能力的一次大升级。其主要流程为:

  1. 声明函数并请求 API:定义当前函数的名称,描述,以及对应的参数信息,并执行 OpenAI 的 Chat 接口;
  2. 执行本地函数:接受接口返回,并解析对应的函数参数信息,根据对应的参数信息调用本地函数;
  3. 上报执行结果:将本地函数执行的结果上报给 OpenAI Chat 接口进行汇总、总结;

个人推测 Function Calling 是 ChatGPT Plugin 功能的底层实现技术支撑,有了这个功能 LLM 应用开发变得更加稳定可靠,同时也会有人基于这个这个技术开发出一套第三方的 GPT Plugin 商店;

除此之外,想必在 LLM 开发层也会有对应的动作:

  1. LangChain 类似的工具会快速跟进支持(已经支持了);
  2. 像 Google Bart 之类自研 LLM 的厂家会及时跟进开发类似的功能;
  3. 开源的 LLM 应该也会很快的推出基于 Function Calling 类似的微调方案,使开源的 LLM 也能装上“手脚”;

后面会基于 Function Calling 功能讲解更多的实际案例,详情见:OpenAI 函数调用(2) – 打造自己 LLM 应用,欢迎关注。

参考资料

  1. OpenAI新功能 blog:Function calling and other API updates
  2. OpenAI 开发文档
  3. How_to_call_functions_with_chat_models
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
人工智能

基于阿里云免费算力自建LLM(类GPT)大模型

2024-4-24 7:31:31

人工智能

如何借助于 OpenAI 以命令的方式在 GPT 终端上画一只 “坤”?

2024-4-24 11:41:15

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索