【OpenAI】ChatGPT函数调用(Function Calling)实践
6月13日OpenAI在Chat Completions API中添加了新的函数调用(Function Calling)能力,帮助开发者通过API方式实现类似于ChatGPT插件的数据交互能力。
本文在作者上一篇文章《私有框架代码生成实践》的基础上,依旧使用自然语言低代码搭建场景作为案例,将嵌入向量搜索(Embedding)获取私有知识库的方式,替换为函数调用方式,以我们更熟悉的结构化数据结构、关系型数据库的方式进行知识库管理。同时函数调用能力的灵活性和可扩展性,也可以帮助用户使用自然语言搭建更加复杂的页面内容、进行更丰富的交互操作。
一、 什么是函数调用
函数调用(Function Calling)是OpenAI在6月13日发布的新能力。根据官方博客描述,函数调用能力可以让模型输出一个请求调用函数的消息,其中包含所需调用的函数信息、以及调用函数时所携带的参数信息。这是一种将GPT能力与外部工具/API连接起来的新方式。
支持函数调用的新模型,可以根据用户的输入自行判断何时需要调用哪些函数,并且可以根据目标函数的描述生成符合要求的请求参数。
开发人员可以使用函数调用能力,通过GPT实现:
- 在进行自然语言交流时,通过调用外部工具回答问题(类似于ChatGPT插件);
- 将自然语言转换为调用API时使用的参数,或者查询数据库时使用的条件;
- 从文本中提取结构化数据。等
二、 如何使用函数调用
函数调用能力可以通过聊天API(Chat Completion)使用。为了实现函数调用能力,OpenAI对聊天API进行了修改,增加了新的请求参数、响应类型以及消息角色,应用开发者需要:
- 在请求参数中向聊天API传递信息,描述应用所提供的可调用函数的信息。
- 解析聊天API响应的消息类型,若模型决定需要调用函数,则根据模型返回的函数信息和函数传参调用函数,并获得返回结果。
- 将函数返回的结果添加到消息列表中,并再次调用聊天API。
1. 添加请求参数, 描述所支持的函数信息
聊天API中新增了两个请求体参数:
functions
当前应用可调用的函数的列表。函数信息中包含了函数的名称、自然语言描述、以及函数所支持传入的参数信息。
functions
参数的格式如下:
openai.createChatCompletion({
model: "gpt-3.5-turbo-0613",
messages: [
// ...
],
functions: [
{
name: 'function_name',
description: '该函数所具备能力的自然语言描述',
parameters: {
type: 'object',
properties: {
argument_name: {
type: 'string',
description: '该参数的自然语言描述'
},
// ...
},
required: ['argument_name']
}
},
// ...
]
})
functions
参数支持以数组形式录入多组函数信息,其中:
name
:函数名称。后续模型会在需要调用函数时返回此名称。description
:函数功能描述。模型通过该描述理解函数能力,并判断是否需要调用该函数。parameters.properties
:函数所需的参数。以对象的形式描述函数所需的参数,其中对象的key即为参数名。type
:参数类型。支持JSON Schema协议。description
:参数描述。
required
:必填参数的参数名列表。
function_call
控制模型应该如何响应函数调换。支持几种输入:
"none"
:模型不调用函数,直接返回内容。没有提供可调用函数时的默认值。"auto"
:模型根据用户输入自行决定是否调用函数以及调用哪个函数。提供可调用函数时的默认值。{"name": "function_name"}
:强制模型调用指定的函数。
2. 识别响应参数, 描述需要调用的函数信息
聊天API在响应内容的可选项(choices
)中提供了两个响应参数:
finish_reason
响应内容结束的原因。
可能的原因包括:
stop
:已返回完整消息。length
:已达到令牌限制或由max_tokens
参数设置的上限。function_call
:模型决定需要调用一个函数。content_filter
:内容触发了拦截策略,忽略返回内容。null
:API响应仍在执行。
其中,若返回function_call
则表示模型需要调用函数。此时message
参数会额外返回函数信息以及函数参数信息。
message.function_call
若响应内容结束的原因是模型需要调用函数,则message
参数中会增加一个用于描述函数信息的function_call
参数,其格式如下:
name
:函数名称。arguments
:函数参数信息。JSON字符串格式。
3. 添加对话角色, 向消息列表中添加函数返回值
在函数执行完成后,可以将函数的返回内容追加到消息列表中,并携带完整的消息列表再次请求聊天API,以获得GPT的后续响应。
在消息列表中,角色的可选值除了原有的系统(system
)、用户(user
)、助理(assistant
)外,新增了函数(function
)类型,用来标识该消息时函数调用的返回内容。
注意:向消息列表中追加函数调用响应消息前,还需要首先将上一步模型返回的消息追加到消息列表中,以保证消息列表中的上下文完整。
完整使用代码
const { Configuration, OpenAIApi } = require("openai");
const openai = new OpenAIApi(new Configuration({ /** OpenAI 配置 */ }));
/** 系统角色信息 **/
const systemPrompt: string = "系统角色prompt";
/** 支持函数信息 **/
const functionsPrompt: unknow[] = [
{
name: 'function_name',
description: '函数功能的自然语言描述',
parameters: {
type: 'object',
properties: {
argument_name: {
type: 'string',
description: '该参数的自然语言描述'
},
// ...
}
}
},
// ...
];
/** 支持函数逻辑 **/
const functionsCalls: { [name: string]: Function } = {
function_name: (args: { argument_name: string }) => {
const { argument_name } = args;
// ...
return '函数调用结果'
},
// ...
}
/** 开始聊天 **/
const chat = async (userPrompt: string) => {
const messages: unknow[] = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
];
let maxCall = 6;
while (maxCall--) {
const responseData = await openai.createChatCompletion({
model: "gpt-3.5-turbo-0613",
messages,
functions,
function_call: maxCall === 0 ? 'none' : 'auto'
}).then((response) => response.data.choices[0]);
const message = responseData.message
messages.push(message)
const finishReason = responseData.finish_reason
if (finishReason === 'function_call') {
const functionName = message.function_call.name
const functionCall = functionCalls[functionName]
const functionArguments = JSON.parse(message.function_call.arguments)
const functionResponse = await functionCall(functionArguments)
messages.push({
role: 'function',
name: functionName,
content: functionResponse
})
} else {
return message.content
}
}
}
三、 低代码自然语言搭建案例
在作者的上一篇文章中,使用嵌入向量搜索提供的“检索-提问解决方案”进行低代码私有协议的访问。在本文中,将使用函数调用方式进行替代。
同时,基于函数调用的能力,也探索了一些更加复杂的页面搭建能力和低代码平台功能。
1. 私有协议访问
基于我们的低代码平台私有协议,在进行CMS类型页面的搭建时,我们将协议的知识划分为几个层级,并分别提供函数供GPT按需调用,以实现私有协议的访问。
系统描述信息
const systemPropmpt = `使用CCMS协议编写页面的配置信息。
CCMS协议所支持的页面类型包括:
- *form*:表单页
- *table*:表格页
- *detail*:详情页`;
函数信息描述
const functionsPrompt = [
{
name: 'get_elements',
description: '获取CCMS协议在指定页面类型下,所支持的元素类型。',
parameters: {
type: 'object',
properties: {
page: {
type: 'array',
description: '页面类型',
items: { type: 'string' }
}
}
},
required: ['page']
},
{
name: 'get_features',
description: '获取CCMS协议在指定元素类型下,所支持的配置化特性。',
parameters: {
type: 'object',
properties: {
element: {
type: 'array',
description: '元素类型',
items: { type: 'string' }
}
}
},
required: ['element']
},
{
name: 'get_descriptions',
description: '获取CCMS协议下,指定页面类型、元素类型以及配置化特性的详细信息。',
parameters: {
type: 'object',
properties: {
page: {
type: 'array',
description: '页面类型',
items: { type: 'string' }
},
element: {
type: 'array',
description: '元素类型',
items: { type: 'string' }
},
feature: {
type: 'array',
description: '配置化特性',
items: { type: 'string' }
}
}
}
}
]
备注:尽管GPT模型支持函数的循环调用,但出于减少API调用频次和节省Token消耗的目的,我们建议在查询私有协议信息的函数中,使用关键词数组的形式进行批量查询。
函数内容
const functionsCalls = {
get_elements: (args: { page: string[] }) => {
const { page } = args;
// 请自行实现信息查询,下列返回内容仅为示例。
return page.map((pageType) => {
switch (pageType) {
case 'form':
return `# **form**表单页所支持的元素类型包括:
- *form_text*:文本输入框
- *form_number*: 数值输入框`;
default:
return `# **${pageType}**没有支持的元素。`
}
}).join("\n\n");
},
get_features: (args: { element: string[] }) => {
const { element } = args
// 请自行实现信息查询,下列返回内容仅为示例。
return element.map((elementKey) => {
const [ pageType, elementType ] = elementKey.split('_');
switch (pageType) {
case 'form':
switch (elementType) {
case 'text':
return `# **form_text**(文本输入框)所支持的配置化特性包括:
- *form_text_maxLength*: 文本最大长度限制
- *form_text_minLength*: 文本最小长度限制
- *form_text_regExp*: 文本正则表达式校验`
default:
return `# **${elementKey}**没有支持的配置化特性。`
}
default:
return `# **${elementKey}**没有支持的配置化特性。`
}
}).join("\n\n");
},
get_descriptions: (args: { page: string[], element: string[], feature: string[] }) => {
const {
page = [],
element = [],
feature = []
} = args
// 请自行实现信息查询,下列返回内容仅为示例。
return [
...page.map((pageType) => `# **${pageType}**的详细描述如下:...`),
...element.map((elementType) => `# **${elementType}**的详细描述如下:...`),
...feature.map((featureType) => `# **${featureType}**的详细描述如下:...`)
].join("\n\n")
}
}
调用示例
一次完整调用的消息列表:
为了便于阅读,已经调整了消息列表中消息内容的缩进排版,并且将表示函数调用参数的JSON字符串解析为对象形式。
[
// 第一次请求聊天API
{ role: 'system',
content: `使用CCMS协议编写页面的配置信息。
CCMS协议所支持的页面类型包括:
- *form*: 表单页
- *table*: 表格页
- *detail*: 详情页` },
{ role: 'user',
content: '创建一个课程报名页面' },
{ role: 'assistant',
content: null,
function_call: { name: 'get_elements',
arguments: { page: ["form"] } } },
// 第二次请求聊天API
{ role: 'function',
name: 'get_elements',
content: `# **form**表单页所支持的元素类型包括:
- *form_text*:文本输入框
- *form_number*: 数值输入框` },
{ role: 'assistant',
content: null,
function_call: { name: 'get_features',
arguments: { element: ["form_text", "form_number"] } } },
// 第三次请求聊天API
{ role: 'function',
name: 'get_features',
content: `# **form_text**(文本输入框)所支持的配置化特性包括:
- *form_text_maxLength*: 文本最大长度限制
- *form_text_minLength*: 文本最小长度限制
- *form_text_regExp*: 文本正则表达式校验
# **form_number**没有支持的配置化特性。` },
{ role: 'assistant',
content: null,
function_call: { name: 'get_descriptions',
arguments: { page: ["form"],
element: ["form_text", "form_number"],
feature: ["form_text_maxLength", "form_text_minLength", "form_text_regExp"] } } },
// 第四次请求聊天API
{ role: 'function',
name: 'get_descriptions',
content: `# **form**的详细描述如下:...
# **form_text**的详细描述如下:...
# **form_number**的详细描述如下:...
# **form_text_maxLength**的详细描述如下:...
# **form_text_minLength**的详细描述如下:...
# **form_text_regExp**的详细描述如下:...` },
{ role: 'assistant',
content: '课程报名页面的配置信息如下:\n\n...' }
]
2. 页面搭建能力扩展: 页面上下文跳转场景
在进行低代码页面搭建时,有时会需要在页面配置中加入一些上下文信息。
例如需要在页面中添加一个按钮,用户点击按钮时跳转至另一个页面。此时我们可以通过一个函数,允许模型获取相关的页面列表。
关于按钮、跳转操作等协议内容可以通过上一章节中的方法获取:
## button 按钮。 支持的配置项包括: - *label*:按钮标签
- *action*:操作类型,支持:
- *none*:无操作
- *redirect*:页面重定向
- *redirectTo*:页面标识
函数信息描述
const functionsPrompt = [
// ...
{
name: 'get_page_id',
description: '查询页面标识列表。其中包含页面标识(`id`)、页面名称(`name`)',
parameters: {
type: 'object',
properties: {
page: {
type: 'string',
description: '页面'
}
}
}
}
]
函数内容
const functionsCalls = {
// ...
get_page_id: (args: {}) => {
// 请自行实现信息查询,下列返回内容仅为示例。
return JSON.stringify([
{
id: 'page_list',
name: '列表页'
},
{
id: 'page_create',
name: '新增页',
description: '用于新增内容'
},
{
id: 'page_preview',
name: '预览页'
}
])
}
}
调用示例
一次完整调用的消息列表:
为了便于阅读,已经调整了消息列表中消息内容的缩进排版,并且将GPT返回的配置信息和表示函数调用参数的JSON字符串解析为对象形式。
[
// 已省略系统角色信息以及私有协议访问信息。
// ...
{ role: 'user',
content: '添加一个预览按钮,点击后跳转至预览页。'
},
// ...
{ role: 'assistant',
content: { type: "button",
label: "预览",
action: "redirect",
redirectTo: "preview" },
function_call: { name: 'get_page_id',
arguments: { page: "preview" } } },
{ role: 'function',
name: 'get_page_id',
content: [ { id: "page_list", name: "列表页" },
{ id: "page_create", name: "新增页" },
{ id: "page_preview", name: "预览页"} ] },
{ role: 'assistant',
content: { type: "button",
label: "预览",
action: "redirect",
redirectTo: "page_preview" }
]
3. 低代码平台能力扩展: 搭建窗口可视区域调整
在进行自然语言低代码搭建时,我们希望让搭建窗口的可视区域自动滚动到发生变化的区域,此时可以通过系统角色要求在进行页面配置变动时调用页面滚动方法,自动滚动至发生配置变化的元素位置。
系统描述信息
在系统描述信息中添加相关描述:
const systemPropmpt = `//...
每次对页面内容进行调整时,需要滚动页面至目标元素位置。
CCMS页面配置信息为一个数组,每个页面元素为数组中的一项,如:
```json
[
{
"id": "input",
"type": "text",
"label": "文本输入框"
}
]
```
// ...
`;
函数信息描述
const functionsPrompt = [
// ...
{
name: 'scroll_to',
description: '滚动页面至指定元素位置',
parameters: {
type: 'object',
properties: {
element_id: {
type: 'string',
description: '指定元素ID'
}
}
}
}
]
函数内容
const functionsCalls = {
// ...
scroll_id: (args: { element_id: string }) => {
const { element_id } = args
// 自行实现页面滚动逻辑
return '滚动完成'
}
}
四、 总结
OpenAI提供的函数调用功能为使用GPT能力的应用提供了更丰富的可能性。应用开发者可以通过函数调用功能,让用户通过自然语言交互,获取实时数据、结构化数据,同时也可以与应用进行各类交互。本文中描述的几个案例场景仅为抛砖引玉,欢迎大家多多讨论,尝试更多应用场景。
作者:京东零售 牛晓光
来源:京东云开发者社区
【OpenAI】ChatGPT函数调用(Function Calling)实践的更多相关文章
- function calling convention
这是2013年写的一篇旧文,放在gegahost.net上面 http://raison.gegahost.net/?p=31 February 19, 2013 function calling c ...
- PatentTips – Java native function calling
BACKGROUND OF INVENTION This invention relates to a system and method for providing a native functio ...
- [转]ARM64 Function Calling Conventions
from apple In general, iOS adheres to the generic ABI specified by ARM for the ARM64 architecture. H ...
- OpenAI ChatGPT 能取代多少程序员的工作?导致失业吗?
阅读原文:https://bysocket.com/openai-chatgpt-vs-developer/ ChatGPT 能取代多少程序员的工作?导致我们程序员失业吗?这是一个很好的话题,我这里分 ...
- Objective-C之消息机制
话说2014年4月编程语言排行榜中Objective-C的使用比又增加了,看来IOS和MAX OS的开发者是真给力呀. 不过个人感觉用不了多久,IOS和Android的开发者收入就不会有那么大的差异了 ...
- 解决:ChatGPT too many requests in 1 hour.Try again later 怎么办?OpenAI 提示
ChatGPT 提示: Too many requests in 1 hour. Try again later. 如下图,我多次访问也出现同样的问题.中文意思是太多的请求数量在当前 1 个小时内,请 ...
- 使用 Azure OpenAI 打造自己的 ChatGPT
一.前言 当今的人工智能技术正在不断发展,越来越多的企业和个人开始探索人工智能在各个领域中的应用.其中,在自然语言处理领域,OpenAI 的 GPT 系列模型成为了研究热点.OpenAI 公司的 Ch ...
- (转)函数调用方式与extern "C"
原文:http://patmusing.blog.163.com/blog/static/13583496020103233446784/ (VC编译器下) 1. CALLBACK,WINAPI和AF ...
- 什么是内联函数(inline function)
In C, we have used Macro function an optimized technique used by compiler to reduce the execution ti ...
- day05—JavaScript之函数调用
转行学开发,代码100天——2018-03-21 JavaScript中的函数调用有4种方式: 方式一:直接通过函数名调用 在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTM ...
随机推荐
- AI论文解读:基于Transformer的多目标跟踪方法TrackFormer
摘要:多目标跟踪这个具有挑战性的任务需要同时完成跟踪目标的初始化.定位并构建时空上的跟踪轨迹.本文将这个任务构建为一个帧到帧的集合预测问题,并提出了一个基于transformer的端到端的多目标跟踪方 ...
- ByteHouse+Apache Airflow:高效简化数据管理流程
Apache Airflow 与 ByteHouse 相结合,为管理和执行数据流程提供了强大而高效的解决方案.本文突出了使用 Apache Airflow 与 ByteHouse 的主要优势和特点,展 ...
- 正确理解c# default关键字
背景 最近QA测试一个我开发的一个Web API时,我意识到之前对C#的default的理解一直是想当然的.具体情况是这样,这个API在某些条件下要返回模型的默认值,写法类似于下面这样 [HttpGe ...
- 将nginx交给service管理
#!/bin/bash # chkconfig: 2345 99 99 prot=80 nginx=/usr/local/nginx/sbin/nginx check(){ ! $nginx -tq ...
- ME51N 采购申请屏幕增强仅显示字段
1.业务需求 通过委外工单生成的采购申请,需要将自定义"图号"字段显示在采购申请中,且只用于显示即可 2.增强实现 增强表EBAN的结构CI_EBANDB 增强点CMOD:MERE ...
- vue学习笔记 一、环境搭建
系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...
- NodeJS开发服务端实现文件上传下载和数据增删改查
本文主要讲解已NodeJS作为服务器完成文件的上传下载和数据增删改查,前端框架为Vue3,UI框架为element-plus,Node版本为V16.14.2. 项目场景模拟是开发一个项目管理的系统,支 ...
- 如何使用Markdown编写笔记
Markdown是什么? Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber). 它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的 XHTML(或者HTM ...
- Canal使用和安装总结
转载请注明出处: 1.定义 Canal 组件是一个基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费,支持将增量数据投递到下游消费者(如 Kafka.RocketMQ 等)或者存储(如 El ...
- Nginx Location 深入剖析及动静分离简易配置
本文为博主原创,未经允许不得转载: 1. location 使用分析 location 是 Nginx 配置 中的一个指令,用于访问的 URL 匹配,而在这个 location 中所配置的每个指令将会 ...