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页面配置信息为一个数组,每个页面元素为数组中的一项,如:
                   [                     {                       "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能力的利用提供了更丰盛的可能性。利用开发者能够通过函数调用性能,让用户通过自然语言交互,获取实时数据、结构化数据,同时也能够与利用进行各类交互。本文中形容的几个案例场景仅为抛砖引玉,欢送大家多多探讨,尝试更多利用场景。> 作者:京东批发 牛晓光>