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才能的运用供给了更丰富的可能性。运用开发者能够经过函数调用功用,让用户经过自然语言交互,获取实时数据、结构化数据,一起也能够与运用进行各类交互。本文中描绘的几个事例场景仅为抛砖引玉,欢迎我们多多讨论,尝试更多运用场景。

作者:京东零售 牛晓光

来历:京东云开发者社区