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进行了修改,增加了新的请求参数、响应类型以及消息角色,应用开发者需要:

  1. 在请求参数中向聊天API传递信息,描述应用所提供的可调用函数的信息。
  2. 解析聊天API响应的消息类型,若模型决定需要调用函数,则根据模型返回的函数信息和函数传参调用函数,并获得返回结果。
  3. 将函数返回的结果添加到消息列表中,并再次调用聊天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

控制模型应该如何响应函数调换。支持几种输入:

  1. "none":模型不调用函数,直接返回内容。没有提供可调用函数时的默认值。
  2. "auto":模型根据用户输入自行决定是否调用函数以及调用哪个函数。提供可调用函数时的默认值。
  3. {"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)实践的更多相关文章

  1. function calling convention

    这是2013年写的一篇旧文,放在gegahost.net上面 http://raison.gegahost.net/?p=31 February 19, 2013 function calling c ...

  2. PatentTips – Java native function calling

    BACKGROUND OF INVENTION This invention relates to a system and method for providing a native functio ...

  3. [转]ARM64 Function Calling Conventions

    from apple In general, iOS adheres to the generic ABI specified by ARM for the ARM64 architecture. H ...

  4. OpenAI ChatGPT 能取代多少程序员的工作?导致失业吗?

    阅读原文:https://bysocket.com/openai-chatgpt-vs-developer/ ChatGPT 能取代多少程序员的工作?导致我们程序员失业吗?这是一个很好的话题,我这里分 ...

  5. Objective-C之消息机制

    话说2014年4月编程语言排行榜中Objective-C的使用比又增加了,看来IOS和MAX OS的开发者是真给力呀. 不过个人感觉用不了多久,IOS和Android的开发者收入就不会有那么大的差异了 ...

  6. 解决:ChatGPT too many requests in 1 hour.Try again later 怎么办?OpenAI 提示

    ChatGPT 提示: Too many requests in 1 hour. Try again later. 如下图,我多次访问也出现同样的问题.中文意思是太多的请求数量在当前 1 个小时内,请 ...

  7. 使用 Azure OpenAI 打造自己的 ChatGPT

    一.前言 当今的人工智能技术正在不断发展,越来越多的企业和个人开始探索人工智能在各个领域中的应用.其中,在自然语言处理领域,OpenAI 的 GPT 系列模型成为了研究热点.OpenAI 公司的 Ch ...

  8. (转)函数调用方式与extern "C"

    原文:http://patmusing.blog.163.com/blog/static/13583496020103233446784/ (VC编译器下) 1. CALLBACK,WINAPI和AF ...

  9. 什么是内联函数(inline function)

    In C, we have used Macro function an optimized technique used by compiler to reduce the execution ti ...

  10. day05—JavaScript之函数调用

    转行学开发,代码100天——2018-03-21 JavaScript中的函数调用有4种方式: 方式一:直接通过函数名调用 在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTM ...

随机推荐

  1. AI专家一席谈:复用算法、模型、案例,AI Gallery带你快速上手应用开发

    摘要: 华为云社区邀请到了AI Gallery的负责人严博,听他谈一谈AI Gallery的设计初衷.经典案例以及未来规划. 本文分享自华为云社区<AI专家一席谈:复用算法.模型.案例,AI G ...

  2. [BitSail] Connector开发详解系列四:Sink、Writer

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 Sink Connector BitSail Sink Connector交互流程介绍 Sink:数据写入组件的生 ...

  3. 1个案例读懂——游戏产品如何用 A/B 测试做增长

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 随着国内游戏用户数量趋于饱和,中国游戏产业也从高速成长期逐渐转型,市场成熟度提升,竞争趋于精细化. 随着游戏出海以 ...

  4. Mac下Homebrew替换镜像

    替换git和homebrew镜像源 一.git加速 查找域名对应的地址 nslookup github.com nslookup github.global.ssl.fastly.Net 修改host ...

  5. 【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类

    目录 认证类 前期准备 自定义认证类 配置认证类 全局配置 局部禁用 认证组件使用步骤 基于类中方法的认证(了解) 权限类 前期准备 重写has_permission() 添加权限不足信息 权限组件使 ...

  6. STM32CubeMX教程16 DAC - 输出3.3V内任意电压

    1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK ...

  7. js import的几种用法

    最近昨天公司小朋友离职,临时接收其负责的vue前端项目.vue好久没做了,很多东西都忘记或以前也没接触,几天开始慢慢写点vue的小知识,算是历程或备忘吧. import在js.ts中用了不知多少次,但 ...

  8. L2-029 特立独行的幸福 (25分) (简单循环 + 素数筛)

    对一个十进制数的各位数字做一次平方和,称作一次迭代.如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数.1 是一个幸福数.此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68, ...

  9. 使用cdn剥离js文件,让他们独立加载

  10. flexible+rem移动端适配