OpenAI Plugins & Function Calling


两种常见接口:

  1. 人机交互接口,User Interface,简称 UI
  2. 应用程序编程接口,Application Programming Interface,简称 API

接口能「通」的关键,是两边都要遵守约定。

  • 人要按照 UI 的设计来操作。UI 的设计要符合人的习惯
  • 程序要按照 API 的设计来调用。API 的设计要符合程序惯例

我们经历过很多调接口的痛苦经历,比如:

  • 文档坑
  • 大小写坑
  • 参数顺序坑
  • 参数类型坑 ……
  1. 命令行,Command Line Interface,简称 CLI(DOS、Unix/Linux shell, Windows Power Shell)。
  2. 图形界面,Graphical User Interface,简称 GUI(Windows、MacOS、iOS、Android)。
  3. 语言界面,Conversational User Interface,简称 CUI,或 Natural-Language User Interface,简称 LUI。
  4. 脑机接口,Brain–Computer Interface,简称 BCI。
    ui-evolution

UI 进化的趋势是:越来越适应人的习惯,越来越自然

  1. 从本地到远程,从同步到异步,媒介发生很多变化,但本质一直没变:程序员的约定。
  2. 自然语言接口,Natural-Language Interface,简称 NLI。

NLI 是 《以 ChatGPT 为代表的「大模型」会是多大的技术革命?》 一文中提出的概念。

用户操作习惯的迁移,会逼所有软件,都得提供「自然语言界面(Natural Language Interface,简称 NLI)」。这是一个新词,指的是以自然语言为输入的接口。

不仅用户界面要 NLI,API 也要 NLI 化。这是因为用户发出的宏观指令,往往不会是一个独立软件能解决的,它需要很多软件、设备的配合。

一种实现思路是,入口 AI(比如 Siri、小爱同学,机器人管家)非常强大,能充分了解所有软件和设备的能力,且能准确地把用户任务拆解和分发下去。这对入口 AI 的要求非常高。

另一种实现思路是,入口 AI 收到自然语言指令,把指令通过 NLI 广播出去(也可以基于某些规则做有选择的广播,保护用户隐私),由各个软件自主决策接不接这个指令,接了要怎么做,该和谁配合。

当 NLI 成为事实标准,那么互联网上软件、服务的互通性会大幅提升,不再受各种协议、接口的限制。 最自然的接口,就是自然语言接口。

以前因为计算机处理不对自然语言,所以有了那么多编程语言,那么多接口,那么多协议,那么多界面风格。而且,它们每一次进化,都是为了「更自然」。现在,终极的自然,到来了。

ChatGPT 让我们体验到 LUI 的美好。而 PluginsFunction Calling,让我们能直接进入 NLI 的过程。

学习 plugin 之前,先要了解 ChatGPT 及所有大模型都有两大缺陷:

  • 没有最新信息。大模型的训练周期很长,且更新一次耗资巨大,所以它的知识都是过去的。GPT-3.5 和 GPT-4 的知识截至 2021 年 9 月。
  • 没有「真逻辑」。它表现出的逻辑、推理,是训练文本的统计规律,而不是真正的逻辑。

比如把 100 以内所有加法算式都训练给大模型,它就能回答 100 以内的加法算式。但如果问它更大数字的加法算式,它就不一定答对了。 因为它并不懂「加法」,只是记住了 100 以内的加法算式的统计规律。它是用字面意义来做数学。

比如 Plugin 能一定程度解决这两个问题:

  • 用天气插件查询天气
  • 用 Wolfram Alpha 插件做数学题

如果你对 Wolfarm Alpha 做数学的能力感到惊讶,想了解它和 ChatGPT 原理的不同,推荐阅读这篇文章:《Wolfram|Alpha as the Way to Bring Computational Knowledge Superpowers to ChatGPT》

plugin

官方开发文档

可能是史上最容易开发的 plugin。只需要定义两个文件:

  1. yourdomain.com/.well-known/ai-plugin.json,描述插件的基本信息
  2. openai.yaml,描述插件的 API(Swagger 生成的文档)

而 OpenAI 那边,更简单,没有任何人和你对接。是 AI 和你对接!AI 阅读上面两个文件,就知道该怎么调用你了。

{
  "schema_version": "v1", //配置文件版本
  "name_for_human": "Sport Stats", //插件名字,给用户看的名字
  "name_for_model": "sportStats", //插件名字,给ChatGPT模型看的名字,需要唯一
  "description_for_human": "Get current and historical stats for sport players and games.", //描述插件的功能,这个字段是在插件市场展示给用户看的
  "description_for_model": "Get current and historical stats for sport players and games. Always display results using markdown tables.", //描述插件的功能,ChatGPT会分析这个字段,确定什么时候调用你的插件
  "auth": {
    "type": "none" //这个是API认证方式,none 代表不需要认证
  },
  "api": {
    "type": "openapi",
    "url": "PLUGIN_HOSTNAME/openapi.yaml" //这个是Swagger API文档地址,ChatGPT通过这个地址访问我们的api文档
  },
  "logo_url": "PLUGIN_HOSTNAME/logo.png", //插件logo地址
  "contact_email": "[email protected]", //插件官方联系邮件
  "legal_info_url": "https://example.com/legal" //与该插件相关的legal information
}
openapi: 3.0.1
info:
  title: Sport Stats
  description: Get current and historical stats for sport players and games.
  version: "v1"
servers:
  - url: PLUGIN_HOSTNAME
paths:
  /players:
    get:
      operationId: getPlayers
      summary: Retrieves all players from all seasons whose names match the query string.
      parameters:
        - in: query
          name: query
          schema:
            type: string
          description: Used to filter players based on their name. For example, ?query=davis will return players that have 'davis' in their first or last name.
      responses:
        "200":
          description: OK
  /teams:
    get:
      operationId: getTeams
      summary: Retrieves all teams for the current season.
      responses:
        "200":
          description: OK
  /games:
    get:
      operationId: getGames
      summary: Retrieves all games that match the filters specified by the args. Display results using markdown tables.
      parameters:
        - in: query
          name: limit
          schema:
            type: string
          description: The max number of results to return.
        - in: query
          name: seasons
          schema:
            type: array
            items:
              type: string
          description: Filter by seasons. Seasons are represented by the year they began. For example, 2018 represents season 2018-2019.
        - in: query
          name: team_ids
          schema:
            type: array
            items:
              type: string
          description: Filter by team ids. Team ids can be determined using the getTeams function.
        - in: query
          name: start_date
          schema:
            type: string
          description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date.
        - in: query
          name: end_date
          schema:
            type: string
          description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date.
      responses:
        "200":
          description: OK
  /stats:
    get:
      operationId: getStats
      summary: Retrieves stats that match the filters specified by the args. Display results using markdown tables.
      parameters:
        - in: query
          name: limit
          schema:
            type: string
          description: The max number of results to return.
        - in: query
          name: player_ids
          schema:
            type: array
            items:
              type: string
          description: Filter by player ids. Player ids can be determined using the getPlayers function.
        - in: query
          name: game_ids
          schema:
            type: array
            items:
              type: string
          description: Filter by game ids. Game ids can be determined using the getGames function.
        - in: query
          name: start_date
          schema:
            type: string
          description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date.
        - in: query
          name: end_date
          schema:
            type: string
          description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date.
      responses:
        "200":
          description: OK
  /season_averages:
    get:
      operationId: getSeasonAverages
      summary: Retrieves regular season averages for the given players. Display results using markdown tables.
      parameters:
        - in: query
          name: season
          schema:
            type: string
          description: Defaults to the current season. A season is represented by the year it began. For example, 2018 represents season 2018-2019.
        - in: query
          name: player_ids
          schema:
            type: array
            items:
              type: string
          description: Filter by player ids. Player ids can be determined using the getPlayers function.
      responses:
        "200":
          description: OK

收起

注意description 的内容非常重要,决定了 ChatGPT 会不会调用你的插件,调用得是否正确。

时间线:

  • 3 月 24 日发布, 提供 11 个插件,可以申请加入 waitlist 获得使用权。
  • 5 月 15 日开始向 Plus 用户全量开放插件和 Browsing, 插件数 70 多个。
  • 7 月 5 日因安全原因,关闭 Browsing(用户可通过此功能访问付费页面)。
  • 7 月 11 日开始全量开放 Code Interpreter。插件数已超 400。

媒体将其类比为 App Store,获得鼓吹。

6 月 7 日(全面放开后三星期)一篇应 OpenAI 要求而被删除的帖子中透露,在一个闭门会中说:

插件的实际使用情况表明,除了 Browsing 以外,还没有达到理想的产品市场契合点。他表示,很多人认为 OpenAI 希望开发者的应用程序位于 ChatGPT 之内,但 OpenAI 真正想要的是 ChatGPT 存在于开发者的应用之中。
—— Sam Altman

更确切的说,他认为未来的应用趋势是将大模型的功能嵌入更多 APP 应用,而不是在 ChatGPT 上生长出更多插件。 因为现实中大多数插件并没有呈现出产品与市场的匹配度。前者是 API 租赁业务,后者是插件业务。

被删内容这里见这里

plugins 失败的主要原因:

  • 缺少「强 Agent」调度,只能手工选三个 plugin,使用成本太高。 (解决此问题,相当于 App Store + Siri,可挑战手机操作系统地位)
  • 不在「场景」中,不能提供端到端一揽子服务。(解决此问题,就是全能私人助理了,人类唯一需要的软件)
  • 开销大(至少两次 GPT-4 生成,和一次 Web API 调用)

做「智能应用」也会面对同样的问题。 好在 OpenAI 很快推出了大杀器 Function Calling 功能,来帮助我们开发更好的智能应用。

func

Function Calling 完整的官方接口文档

收起

收起

划重点:

  1. Function Calling 中的函数与参数的描述也是一种Prompt
  2. 这种 Prompt 也需要调优,否则会影响函数的召回、参数的准确性,甚至让 GPT 产生幻觉

收起

收起

收起

因为 Function Calling 能力是特别 fine-tune 在模型内的,所以输出更稳定,用来获取 JSON 更可靠。搞个假函数声明,就能拿到 JSON 了。

收起

收起

收起

注意
  1. 只有 gpt-3.5-turbo-0613gpt-4-0613 可用。它俩针对 Function Calling 做了 fine-tuning,以尽可能保证正确率。
  2. 但不保证不出错,包括不保证 json 格式正确。所以官方强烈建议(原文:strongly recommend)如果有写操作,一定插入人工流程做确认。但比纯靠 prompt 控制,可靠性是大了很多的
  3. 函数声明是消耗 token 的。要在功能覆盖、省钱、节约上下文窗口之间找到最佳平衡
  4. 实战经验:把自己的函数调用结果用自然语言给到 OpenAI,效果有时更好。

想象你是下面产品的研发,怎样用 Function Calling 实现下面的功能?

  1. 用户对着微信说:「给我每个女性好友发一条情真意切的拜年消息,还要带点儿小幽默」
  2. 用户对着富途牛牛说:「人工智能相关股票,市盈率最低的是哪几个?最近交易量如何?都有哪些机构持有?」
  3. 用户对着京东说:「我想买一台 65 寸的电视,不要日货,价格在 5000 元左右」

基本上:

  • 我们的任何功能都可以和大模型结合,提供更好的用户体验
  • 通过大模型,完成内部功能的组合调用,完全 agent 化设计系统架构

遗憾的是,暂时国内还没有任何大模型支持 Function Calling。但应该很快都会陆续支持起来,因为太好用了。 OpenAI 再次定义了行业标准。

当然,「幻觉」仍然是存在的。如何尽量减少幻觉的影响,参考以下资料:

NLP算法工程师视角:
  1. 模型砍大面,规则修细节
  2. 一个模型搞不定的问题,拆成多个解决
  3. 评估算法的准确率
  4. 评估badcase的影响面
  5. 算法的结果永远不是100%正确的,建立在这个假设基础上推敲产品的可行性

可以关注 ToolBench