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参数的格式如下:

  1. openai.createChatCompletion({
  2. model: "gpt-3.5-turbo-0613",
  3. messages: [
  4. // ...
  5. ],
  6. functions: [
  7. {
  8. name: 'function_name',
  9. description: '该函数所具备能力的自然语言描述',
  10. parameters: {
  11. type: 'object',
  12. properties: {
  13. argument_name: {
  14. type: 'string',
  15. description: '该参数的自然语言描述'
  16. },
  17. // ...
  18. },
  19. required: ['argument_name']
  20. }
  21. },
  22. // ...
  23. ]
  24. })

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)类型,用来标识该消息时函数调用的返回内容。

注意:向消息列表中追加函数调用响应消息前,还需要首先将上一步模型返回的消息追加到消息列表中,以保证消息列表中的上下文完整。

完整使用代码

  1. const { Configuration, OpenAIApi } = require("openai");
  2. const openai = new OpenAIApi(new Configuration({ /** OpenAI 配置 */ }));
  3. /** 系统角色信息 **/
  4. const systemPrompt: string = "系统角色prompt";
  5. /** 支持函数信息 **/
  6. const functionsPrompt: unknow[] = [
  7. {
  8. name: 'function_name',
  9. description: '函数功能的自然语言描述',
  10. parameters: {
  11. type: 'object',
  12. properties: {
  13. argument_name: {
  14. type: 'string',
  15. description: '该参数的自然语言描述'
  16. },
  17. // ...
  18. }
  19. }
  20. },
  21. // ...
  22. ];
  23. /** 支持函数逻辑 **/
  24. const functionsCalls: { [name: string]: Function } = {
  25. function_name: (args: { argument_name: string }) => {
  26. const { argument_name } = args;
  27. // ...
  28. return '函数调用结果'
  29. },
  30. // ...
  31. }
  32. /** 开始聊天 **/
  33. const chat = async (userPrompt: string) => {
  34. const messages: unknow[] = [
  35. { role: 'system', content: systemPrompt },
  36. { role: 'user', content: userPrompt }
  37. ];
  38. let maxCall = 6;
  39. while (maxCall--) {
  40. const responseData = await openai.createChatCompletion({
  41. model: "gpt-3.5-turbo-0613",
  42. messages,
  43. functions,
  44. function_call: maxCall === 0 ? 'none' : 'auto'
  45. }).then((response) => response.data.choices[0]);
  46. const message = responseData.message
  47. messages.push(message)
  48. const finishReason = responseData.finish_reason
  49. if (finishReason === 'function_call') {
  50. const functionName = message.function_call.name
  51. const functionCall = functionCalls[functionName]
  52. const functionArguments = JSON.parse(message.function_call.arguments)
  53. const functionResponse = await functionCall(functionArguments)
  54. messages.push({
  55. role: 'function',
  56. name: functionName,
  57. content: functionResponse
  58. })
  59. } else {
  60. return message.content
  61. }
  62. }
  63. }

三、 低代码自然语言搭建案例

在作者的上一篇文章中,使用嵌入向量搜索提供的“检索-提问解决方案”进行低代码私有协议的访问。在本文中,将使用函数调用方式进行替代。

同时,基于函数调用的能力,也探索了一些更加复杂的页面搭建能力和低代码平台功能。

1. 私有协议访问

基于我们的低代码平台私有协议,在进行CMS类型页面的搭建时,我们将协议的知识划分为几个层级,并分别提供函数供GPT按需调用,以实现私有协议的访问。

系统描述信息

  1. const systemPropmpt = `使用CCMS协议编写页面的配置信息。
  2. CCMS协议所支持的页面类型包括:
  3. - *form*:表单页
  4. - *table*:表格页
  5. - *detail*:详情页`;

函数信息描述

  1. const functionsPrompt = [
  2. {
  3. name: 'get_elements',
  4. description: '获取CCMS协议在指定页面类型下,所支持的元素类型。',
  5. parameters: {
  6. type: 'object',
  7. properties: {
  8. page: {
  9. type: 'array',
  10. description: '页面类型',
  11. items: { type: 'string' }
  12. }
  13. }
  14. },
  15. required: ['page']
  16. },
  17. {
  18. name: 'get_features',
  19. description: '获取CCMS协议在指定元素类型下,所支持的配置化特性。',
  20. parameters: {
  21. type: 'object',
  22. properties: {
  23. element: {
  24. type: 'array',
  25. description: '元素类型',
  26. items: { type: 'string' }
  27. }
  28. }
  29. },
  30. required: ['element']
  31. },
  32. {
  33. name: 'get_descriptions',
  34. description: '获取CCMS协议下,指定页面类型、元素类型以及配置化特性的详细信息。',
  35. parameters: {
  36. type: 'object',
  37. properties: {
  38. page: {
  39. type: 'array',
  40. description: '页面类型',
  41. items: { type: 'string' }
  42. },
  43. element: {
  44. type: 'array',
  45. description: '元素类型',
  46. items: { type: 'string' }
  47. },
  48. feature: {
  49. type: 'array',
  50. description: '配置化特性',
  51. items: { type: 'string' }
  52. }
  53. }
  54. }
  55. }
  56. ]

备注:尽管GPT模型支持函数的循环调用,但出于减少API调用频次和节省Token消耗的目的,我们建议在查询私有协议信息的函数中,使用关键词数组的形式进行批量查询。

函数内容

  1. const functionsCalls = {
  2. get_elements: (args: { page: string[] }) => {
  3. const { page } = args;
  4. // 请自行实现信息查询,下列返回内容仅为示例。
  5. return page.map((pageType) => {
  6. switch (pageType) {
  7. case 'form':
  8. return `# **form**表单页所支持的元素类型包括:
  9. - *form_text*:文本输入框
  10. - *form_number*: 数值输入框`;
  11. default:
  12. return `# **${pageType}**没有支持的元素。`
  13. }
  14. }).join("\n\n");
  15. },
  16. get_features: (args: { element: string[] }) => {
  17. const { element } = args
  18. // 请自行实现信息查询,下列返回内容仅为示例。
  19. return element.map((elementKey) => {
  20. const [ pageType, elementType ] = elementKey.split('_');
  21. switch (pageType) {
  22. case 'form':
  23. switch (elementType) {
  24. case 'text':
  25. return `# **form_text**(文本输入框)所支持的配置化特性包括:
  26. - *form_text_maxLength*: 文本最大长度限制
  27. - *form_text_minLength*: 文本最小长度限制
  28. - *form_text_regExp*: 文本正则表达式校验`
  29. default:
  30. return `# **${elementKey}**没有支持的配置化特性。`
  31. }
  32. default:
  33. return `# **${elementKey}**没有支持的配置化特性。`
  34. }
  35. }).join("\n\n");
  36. },
  37. get_descriptions: (args: { page: string[], element: string[], feature: string[] }) => {
  38. const {
  39. page = [],
  40. element = [],
  41. feature = []
  42. } = args
  43. // 请自行实现信息查询,下列返回内容仅为示例。
  44. return [
  45. ...page.map((pageType) => `# **${pageType}**的详细描述如下:...`),
  46. ...element.map((elementType) => `# **${elementType}**的详细描述如下:...`),
  47. ...feature.map((featureType) => `# **${featureType}**的详细描述如下:...`)
  48. ].join("\n\n")
  49. }
  50. }

调用示例

一次完整调用的消息列表:

为了便于阅读,已经调整了消息列表中消息内容的缩进排版,并且将表示函数调用参数的JSON字符串解析为对象形式。

  1. [
  2. // 第一次请求聊天API
  3. { role: 'system',
  4. content: `使用CCMS协议编写页面的配置信息。
  5. CCMS协议所支持的页面类型包括:
  6. - *form*: 表单页
  7. - *table*: 表格页
  8. - *detail*: 详情页` },
  9. { role: 'user',
  10. content: '创建一个课程报名页面' },
  11. { role: 'assistant',
  12. content: null,
  13. function_call: { name: 'get_elements',
  14. arguments: { page: ["form"] } } },
  15. // 第二次请求聊天API
  16. { role: 'function',
  17. name: 'get_elements',
  18. content: `# **form**表单页所支持的元素类型包括:
  19. - *form_text*:文本输入框
  20. - *form_number*: 数值输入框` },
  21. { role: 'assistant',
  22. content: null,
  23. function_call: { name: 'get_features',
  24. arguments: { element: ["form_text", "form_number"] } } },
  25. // 第三次请求聊天API
  26. { role: 'function',
  27. name: 'get_features',
  28. content: `# **form_text**(文本输入框)所支持的配置化特性包括:
  29. - *form_text_maxLength*: 文本最大长度限制
  30. - *form_text_minLength*: 文本最小长度限制
  31. - *form_text_regExp*: 文本正则表达式校验
  32. # **form_number**没有支持的配置化特性。` },
  33. { role: 'assistant',
  34. content: null,
  35. function_call: { name: 'get_descriptions',
  36. arguments: { page: ["form"],
  37. element: ["form_text", "form_number"],
  38. feature: ["form_text_maxLength", "form_text_minLength", "form_text_regExp"] } } },
  39. // 第四次请求聊天API
  40. { role: 'function',
  41. name: 'get_descriptions',
  42. content: `# **form**的详细描述如下:...
  43. # **form_text**的详细描述如下:...
  44. # **form_number**的详细描述如下:...
  45. # **form_text_maxLength**的详细描述如下:...
  46. # **form_text_minLength**的详细描述如下:...
  47. # **form_text_regExp**的详细描述如下:...` },
  48. { role: 'assistant',
  49. content: '课程报名页面的配置信息如下:\n\n...' }
  50. ]

2. 页面搭建能力扩展: 页面上下文跳转场景

在进行低代码页面搭建时,有时会需要在页面配置中加入一些上下文信息。

例如需要在页面中添加一个按钮,用户点击按钮时跳转至另一个页面。此时我们可以通过一个函数,允许模型获取相关的页面列表。

关于按钮、跳转操作等协议内容可以通过上一章节中的方法获取:

  1. ## button
  2. 按钮。
  3. 支持的配置项包括:
  4. - *label*:按钮标签
  5. - *action*:操作类型,支持:
  6. - *none*:无操作
  7. - *redirect*:页面重定向
  8. - *redirectTo*:页面标识

函数信息描述

  1. const functionsPrompt = [
  2. // ...
  3. {
  4. name: 'get_page_id',
  5. description: '查询页面标识列表。其中包含页面标识(`id`)、页面名称(`name`)',
  6. parameters: {
  7. type: 'object',
  8. properties: {
  9. page: {
  10. type: 'string',
  11. description: '页面'
  12. }
  13. }
  14. }
  15. }
  16. ]

函数内容

  1. const functionsCalls = {
  2. // ...
  3. get_page_id: (args: {}) => {
  4. // 请自行实现信息查询,下列返回内容仅为示例。
  5. return JSON.stringify([
  6. {
  7. id: 'page_list',
  8. name: '列表页'
  9. },
  10. {
  11. id: 'page_create',
  12. name: '新增页',
  13. description: '用于新增内容'
  14. },
  15. {
  16. id: 'page_preview',
  17. name: '预览页'
  18. }
  19. ])
  20. }
  21. }

调用示例

一次完整调用的消息列表:

为了便于阅读,已经调整了消息列表中消息内容的缩进排版,并且将GPT返回的配置信息和表示函数调用参数的JSON字符串解析为对象形式。

  1. [
  2. // 已省略系统角色信息以及私有协议访问信息。
  3. // ...
  4. { role: 'user',
  5. content: '添加一个预览按钮,点击后跳转至预览页。'
  6. },
  7. // ...
  8. { role: 'assistant',
  9. content: { type: "button",
  10. label: "预览",
  11. action: "redirect",
  12. redirectTo: "preview" },
  13. function_call: { name: 'get_page_id',
  14. arguments: { page: "preview" } } },
  15. { role: 'function',
  16. name: 'get_page_id',
  17. content: [ { id: "page_list", name: "列表页" },
  18. { id: "page_create", name: "新增页" },
  19. { id: "page_preview", name: "预览页"} ] },
  20. { role: 'assistant',
  21. content: { type: "button",
  22. label: "预览",
  23. action: "redirect",
  24. redirectTo: "page_preview" }
  25. ]

3. 低代码平台能力扩展: 搭建窗口可视区域调整

在进行自然语言低代码搭建时,我们希望让搭建窗口的可视区域自动滚动到发生变化的区域,此时可以通过系统角色要求在进行页面配置变动时调用页面滚动方法,自动滚动至发生配置变化的元素位置。

系统描述信息

在系统描述信息中添加相关描述:

  1. const systemPropmpt = `//...
  2. 每次对页面内容进行调整时,需要滚动页面至目标元素位置。
  3. CCMS页面配置信息为一个数组,每个页面元素为数组中的一项,如:
  4. ```json
  5. [
  6. {
  7. "id": "input",
  8. "type": "text",
  9. "label": "文本输入框"
  10. }
  11. ]
  12. ```
  13. // ...
  14. `;

函数信息描述

  1. const functionsPrompt = [
  2. // ...
  3. {
  4. name: 'scroll_to',
  5. description: '滚动页面至指定元素位置',
  6. parameters: {
  7. type: 'object',
  8. properties: {
  9. element_id: {
  10. type: 'string',
  11. description: '指定元素ID'
  12. }
  13. }
  14. }
  15. }
  16. ]

函数内容

  1. const functionsCalls = {
  2. // ...
  3. scroll_id: (args: { element_id: string }) => {
  4. const { element_id } = args
  5. // 自行实现页面滚动逻辑
  6. return '滚动完成'
  7. }
  8. }

四、 总结

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. 面试官问:mysql中时间日期类型和字符串类型的选择

    摘要:MySQL中有多种表示时间日期的数据类型,主要有YEAR.TIME.DATE.DATETIME.TIMESTAMP等 本文分享自华为云社区<一针见血,mysql中时间日期类型和字符串类型的 ...

  2. Go语言逆向技术:常量字符串

    摘要:Go语言源代码编译成二进制文件后,源代码中的字符串存放在哪里?是如何组织的? 本文分享自华为云社区<go语言逆向技术之---常量字符串解密>,作者:安全技术猿. Go语言源代码编译成 ...

  3. 如何注册appuploader账号​

    如何注册appuploader账号​ 我们上一篇讲到appuploader的下载安装,要想使用此软件呢,需要注册账号才能使用,今​ 天我们来讲下如何注册appuploader账号来使用软件.​ 1.A ...

  4. appuploader 入门使用

    回想一下我们发布 iOS 应用,不仅步骤繁琐,非常耗时.一旦其中一步失误了,又得重新来.作为一名优秀的工程师不应该让这些重复的工作在浪费我们的人生.在软件工程里面,我们一直都推崇把重复.流程化的工作交 ...

  5. 火山引擎 DataTester:一个 A/B 测试,将一款游戏的核心收益提升了 8%

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 随着社会节奏及生活方式的改变,休闲游戏在移动游戏市场的占比逐渐升高,据伽马数据发布的<2022 年休闲游戏发 ...

  6. SpringBoot Docker 发布

    本文是手动模式,可以移步 Intellij IDEA 集成 Docker 发布 使用 Intellij 集成Docker 发布,比较方便 pom 文件 <groupId>com.vipso ...

  7. JupyterLab 桌面版 !!!

    JupyterLab 是广受欢迎的 Jupyter Notebook「新」界面.它是一个交互式的开发环境,可用于 notebook.代码或数据,因此它的扩展性非常强. 用户可以使用它编写 notebo ...

  8. Python数据预处理:彻底理解标准化和归一化

    数据预处理 数据中不同特征的量纲可能不一致,数值间的差别可能很大,不进行处理可能会影响到数据分析的结果,因此,需要对数据按照一定比例进行缩放,使之落在一个特定的区域,便于进行综合分析. 常用的方法有两 ...

  9. POJ1426: Find The Multiple

    题目: 给定一个正整数n,请编写一个程序来寻找n的一个非零的倍数m,这个m应当在十进制表示时每一位上只包含0或者1.你可以假定n不大于200且m不多于100位. 提示:本题采用Special Judg ...

  10. Codeforce:723A. The New Year: Meeting Friends (水题)

    题意:有三个好朋友的家都住在x轴的不同坐标,问新年的时候三个朋友之间问候走的最短距离 max{(a,b,c)} - min{(a,b,c)} 即可 编译器由 VS2017 切换到VScode使用,纪念 ...