laitimes

OpenAI: ChatGPT Function Calling practice

author:JD Cloud developer

On June 13, OpenAI added a new Function Calling capability to the Chat Completions API to help developers achieve data interaction capabilities similar to ChatGPT plugins through APIs.

Based on the author's previous article "Private Framework Code Generation Practice", this article still uses natural language low-code construction scenarios as a case, replacing the embedded vector search (Embedding) with the function call method, and performing knowledge base management in the way of structured data structure and relational database that we are more familiar with. At the same time, the flexibility and extensibility of function invocation can also help users use natural language to build more complex page content and perform richer interactive operations.

1. What is a function call?

Function Calling is a new capability released by OpenAI on June 13. According to the official blog description, the function call capability allows the model to output a message requesting a function to be called, which contains the function information to be called and the parameter information carried when calling the function. This is a new way to connect GPT capabilities with external tools/APIs.

The new model that supports function calls can determine when and which functions need to be called based on the user's input, and can generate request parameters that meet the requirements based on the description of the objective function.

Developers can use function call capabilities to implement via GPT:

  • Answer questions by calling external tools when communicating in natural language (similar to ChatGPT plugins);
  • Translate natural language into parameters used when calling APIs, or conditions when querying databases;
  • Extract structured data from text. wait

Second, how to use function calls

Function invocation capabilities can be used through the Chat Completion. To implement function invocation capabilities, OpenAI has modified the chat API to add new request parameters, response types, and message roles, and application developers need to:

  1. In the request parameters, pass information to the chat API, describing the information of the callable functions provided by the application.
  2. Parse the message type of the chat API response, and if the model decides to call the function, call the function based on the function information returned by the model and pass the function parameters, and obtain the return result.
  3. Add the result returned by the function to the message list and call the chat API again.

1. Add request parameters to describe the supported functions

Two new request body parameters have been added to the chat API:

functions

A list of functions that the current app can call. The function information contains the name of the function, the natural language description, and the parameter information supported by the function.

The format of the functions parameter is as follows:

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']
      }
    },
    // ...
  ]
})
           

The functions parameter supports entering multiple sets of function information in the form of an array, where:

  • name: The name of the function. Subsequent models return this name when the function needs to be called.
  • description: A description of the function function. The model uses this description to understand the function capability and determine whether the function needs to be called.
  • parameters.properties: The parameters required by the function. Describe the parameters required by the function in the form of an object, where the key of the object is the parameter name.
    • type: The type of the parameter. Support JSON Schema protocol.
    • description: The parameter description.
  • required: A list of parameter names for required parameters.

function_call

Controls how the model should respond to function switching. Several inputs are supported:

  1. "none": The model does not call the function and returns the content directly. There is no default value when a callable function is provided.
  2. "auto": The model decides for itself whether to call a function and which function to call based on user input. Provide a default value when the function can be called.
  3. {"name": "function_name"}: Forces the model to call the specified function.

2. Identify the response parameters and describe the function information that needs to be called

The Chat API provides two response parameters in the choices of the response content:

finish_reason

The reason why the response content ended.

Possible causes include:

  • stop: The full message has been returned.
  • length: The token limit or the upper limit set by the max_tokens parameter has been reached.
  • function_call: The model decides that a function needs to be called.
  • content_filter: The content triggered the blocking policy and ignored the returned content.
  • null: The API response is still executing.

Among them, if the return function_call indicates that the model needs to call the function. At this time, the message parameter will additionally return function information and function parameter information.

message.function_call

If the end of the response content is because the model needs to call the function, a function_call parameter describing the function information is added to the message parameter, which has the following format:

  • name: The name of the function.
  • arguments: Function parameter information. JSON string format.

3. Add a conversation role and add a function return value to the message list

After the function execution is completed, you can append the return content of the function to the message list, and request the chat API again with the complete message list to obtain subsequent responses from GPT.

In the message list, in addition to the original system, user, and assistant, the optional values of the role add a function type to identify the return content of the function call when the message is made.

Note: Before appending the function call response message to the message list, you also need to append the message returned by the previous model to the message list to ensure that the context in the message list is complete.

Use the code in its entirety

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
    }
  }
}
           

Third, low-code natural language construction cases

In the author's previous article, low-code private protocol access was made using the "retrieve-question solution" provided by embedded vector search. In this article, you'll use function calls instead.

At the same time, based on the ability of function call, some more complex page building capabilities and low-code platform functions are also explored.

1. Private Protocol Access

Based on the private protocol of our low-code platform, when building the CMS type page, we divide the knowledge of the protocol into several levels, and provide functions for GPT to call on demand to achieve access to the private protocol.

System description information

const systemPropmpt = `使用CCMS协议编写页面的配置信息。

                       CCMS协议所支持的页面类型包括:
                       - *form*:表单页
                       - *table*:表格页
                       - *detail*:详情页`;
           

Function information description

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' }
        }
      }
    }
  }
]
           
Note: Although the GPT model supports circular calls of functions, in order to reduce the frequency of API calls and save token consumption, we recommend that you use a keyword array to query in batches in functions that query private protocol information.

Functional content

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")
  }
}
           

Invoke the sample

A full list of messages for one call:

For readability, the indentation of message content in the message list has been adjusted, and the JSON string representing the function call parameters has been parsed into object form.
[
  // 第一次请求聊天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. Page building capability expansion: page context jumps to the scene

When building a low-code page, it is sometimes necessary to add some context to the page configuration.

For example, you need to add a button to a page that jumps to another page when the user clicks the button. At this point, we can use a function that allows the model to get a list of relevant pages.

The protocol content such as buttons and jump operations can be obtained through the methods in the previous section:

## button button. Supported configuration items include: - *label*: button label - *action*: action type, supported: - *none*: no action - *redirect*: page redirect - *redirectTo*: page identity

Function information description

const functionsPrompt = [
  // ...
  {
    name: 'get_page_id',
    description: '查询页面标识列表。其中包含页面标识(`id`)、页面名称(`name`)',
    parameters: {
      type: 'object',
      properties: {
        page: {
          type: 'string',
          description: '页面'
        }
      }
    }
  }
]
           

Functional content

const functionsCalls = {
  // ...
  get_page_id: (args: {}) => {
    // 请自行实现信息查询,下列返回内容仅为示例。
    return JSON.stringify([
      {
        id: 'page_list',
        name: '列表页'
      },
      {
        id: 'page_create',
        name: '新增页',
        description: '用于新增内容'
      },
      {
        id: 'page_preview',
        name: '预览页'
      }
    ])
  }
}
           

Invoke the sample

A full list of messages for one call:

For readability, the indentation of message content in the message list has been adjusted, and the configuration information returned by GPT and JSON strings representing function call parameters have been parsed into object form.
[
  // 已省略系统角色信息以及私有协议访问信息。
  // ...
  { 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. Low-code platform capability extension: build window visual area adjustment

When building natural language low-code, we want to automatically scroll the visual area of the building window to the changed area, and we can call the page scroll method when the page configuration change is made by the system role to automatically scroll to the position of the element where the configuration change occurred.

System description information

Add a description to the system description:

const systemPropmpt = `//...

                       每次对页面内容进行调整时,需要滚动页面至目标元素位置。
                       
                       CCMS页面配置信息为一个数组,每个页面元素为数组中的一项,如:

                       \`\`\`json
                       [
                         {
                           "id": "input",
                           "type": "text",
                           "label": "文本输入框"
                         }
                       ]
                       \`\`\`
                       
                       // ...
                       `;
           

Function information description

const functionsPrompt = [
  // ...
  {
    name: 'scroll_to',
    description: '滚动页面至指定元素位置',
    parameters: {
      type: 'object',
      properties: {
        element_id: {
          type: 'string',
          description: '指定元素ID'
        }
      }
    }
  }
]
           

Functional content

const functionsCalls = {
  // ...
  scroll_id: (args: { element_id: string }) => {
    const { element_id } = args
    // 自行实现页面滚动逻辑
    return '滚动完成'
  }
}
           

IV. Summary

The function invocation function provided by OpenAI provides richer possibilities for applications using GPT capabilities. Application developers can use the function call function to allow users to obtain real-time data, structured data, and interact with applications through natural language interaction. The case scenarios described in this article are only a starting point, and you are welcome to discuss and try more application scenarios.

Author: JD Retail Niu Xiaoguang

Source: JD Cloud Developer Community

Read on