ChatGPT插件平台无望成为影响深远的"下一件小事",因而对于开发者来说,有必要对ChatGPT插件的开发有肯定的理解。原文: Writing a ChatGPT Plugin With Go

我工作的附带福利之一是偶然能够接触试用一些很酷的新技术,最近的一项技术是OpenAI的ChatGPT插件平台。

ChatGPT是一款突破性的AI聊天机器人,无论好坏,它都席卷了世界。在2023年3月,ChatGPT插件和插件平台的公布引发了继续关注(包含我),这可是件小事。

如果你把ChatGPT的公布等同于2007年公布iPhone,那么插件的公布就像是在2008年公布的挪动利用和App Store。这不仅是当今最弱小、最先进的AI模型开发服务平台,也是一个能够立刻触达1亿用户的机会。

对于像我这样的程序员来说,就像猫薄荷之于猫一样,当我拿到拜访权限时,立马跳了起来。

ChatGPT插件非常简单,实际上只不过是ChatGPT调用并获取信息的API,能够基于返回信息向用户提供答案。是的,你没看错,ChatGPT插件只是规范API。

那么,怎么实现ChatGPT插件呢?实现ChatGPT插件必须提供2个文件:

  1. 形容插件的manifest文件(实质上是JSON文件)
  2. 形容插件中API的OpenAPI标准(YAML文件)

接下来看看我是如何创立新加坡HDB停车场插件的。

停车场插件

我决定从一个非常简单的插件开始。在新加坡,住房发展局(HDB)")是负责公共住房的政府机构。公共住房在新加坡是件小事,约有80%的人住在公共住房中,因而HDB停车场占新加坡住宅停车场的大部分。

我想创立一个插件,能够通知我对于新加坡HDB停车场的应用状况。这些信息都能够在data.gov.sg上取得,这是一个收费数据门户网站,提供新加坡政府的公开数据集和API。

简而言之,插件有两个性能:

  1. 提供HDB停车场的材料,能够指定地点或指定停车场
  2. 提供HDB停车场的可用状况,能够指定地点或指定停车场

data.gov.sg门户网站有个针对这些指标很有用的API——HDB停车场应用状况API,这是一个实时API。至于HDB停车场信息,有一个定期更新的数据集,称为HDB停车场信息。侥幸的是,HDB停车场信息数据集是CKAN action API的前端。

无论如何,当初这两种性能都有API可用。这就完事了,对吧?没那么快。CKAN action API依据日期参数在数据集中返回所有内容,并不齐全有用。我不能把所有数据都转到ChatGPT去,数据集是324kb,超过了token限度。然而,解决这个问题绝对简略,只须要过滤一下。

此外,咱们不能间接公开API。记住,ChatGPT插件须要manifest文件和OpenAI标准。manifest文件还指向像logo和非法页面这样的货色,须要提供具备雷同域名和插件URL的电子邮件。所有这所有都意味着咱们真的须要开发一个独自的API程序,在将data.gov.sg API公布到ChatGPT之前用来进行解决。

API程序

我用Go语言开发了API程序,这是很天然的抉择,Go非常适合API。因为很多AI的货色都用Python,我最近始终在应用Python,所以也比拟渴望回到Go。

JSON数据结构

咱们从JSON数据结构定义开始。

package mainimport "time"// for car park informationtype CarParkData struct { CarParkInfo []struct {  TotalLots     string `json:"total_lots"`  LotType       string `json:"lot_type"`  LotsAvailable string `json:"lots_available"` } `json:"carpark_info"` CarParkNumber  string `json:"carpark_number"` UpdateDatetime string `json:"update_datetime"`}type CarParkAvailability struct { Items []struct {  Timestamp time.Time     `json:"timestamp"`  Data      []CarParkData `json:"carpark_data"` } `json:"items"`}// for car park availabilitytype CarParkRecord struct { FullCount           string  `json:"_full_count"` ShortTermParking    string  `json:"short_term_parking"` CarParkType         string  `json:"car_park_type"` YCoord              string  `json:"y_coord"` XCoord              string  `json:"x_coord"` Rank                float64 `json:"rank"` FreeParking         string  `json:"free_parking"` GantryHeight        string  `json:"gantry_height"` CarParkBasement     string  `json:"car_park_basement"` NightParking        string  `json:"night_parking"` Address             string  `json:"address"` CarParkDecks        string  `json:"car_park_decks"` ID                  int     `json:"_id"` CarParkNo           string  `json:"car_park_no"` TypeOfParkingSystem string  `json:"type_of_parking_system"`}type CarParks struct { Help    string `json:"help"` Success bool   `json:"success"` Result  struct {  ResourceID string `json:"resource_id"`  Fields     []struct {   Type string `json:"type"`   ID   string `json:"id"`  } `json:"fields"`  Q       string          `json:"q"`  Records []CarParkRecord `json:"records"`  Links   struct {   Start string `json:"start"`   Next  string `json:"next"`  } `json:"_links"`  Total int `json:"total"` } `json:"result"`}

看起来很简单,但其实我舞弊了,我只是从data.gov.sg API中获取JSON样本和输入,并应用Matt Holt提供的JSON-to-Struct服务,该服务会立刻创立相应的构造体,而后只需依据须要对其进行调整。

API

接下来是API程序自身。

package mainimport ( "encoding/json" "io" "log" "net/http" "net/url" "os" "time")const infoUrl = "https://data.gov.sg/api/action/datastore_search?\  resource_id=139a3035-e624-4f56-b63f-89ae28d4ae4c&q="const availUrl = "https://api.data.gov.sg/v1/transport/\  carpark-availability?date_time="func main() { port := os.Getenv("PORT") mux := http.NewServeMux() mux.Handle("/static/", http.StripPrefix("/static",   http.FileServer(http.Dir("./static")))) mux.Handle("/.well-known/ai-plugin.json", cors(manifest)) mux.Handle("/openapi.yaml", cors(specs)) mux.Handle("/info", cors(info)) mux.Handle("/availability", cors(availability)) server := &http.Server{  Addr:    ":" + port,  Handler: mux, } server.ListenAndServe()}// middleware to set CORS for handlersfunc cors(h http.HandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  w.Header().Set("Access-Control-Allow-Origin", "*")  w.Header().Set("Access-Control-Allow-Methods", "GET")  w.Header().Set("Access-Control-Allow-Headers",     "Origin,Content-Type,OpenAI-Conversation-ID,OpenAI-Ephemeral-User-ID")  h.ServeHTTP(w, r) })}// returns openai.yaml filefunc specs(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "openapi.yaml")}// returns .well-known/ai-plugin.json filefunc manifest(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, ".well-known/ai-plugin.json")}// handler function// get information on car parkfunc info(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") records := getCarParkRecords(query) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(records)}// handler function// get availability on car parkfunc availability(w http.ResponseWriter, r *http.Request) { // get the car parks requested query := r.URL.Query().Get("q") records := getCarParkRecords(query) // get the availability of car parks in Singapore now now := time.Now() dt := now.Format(time.RFC3339) availability := new(CarParkAvailability) carparkdata := []CarParkData{} resp, err := http.Get(availUrl + url.QueryEscape(dt)) if err != nil {  log.Println("Cannot call url", availUrl+dt, err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil {  log.Println("Cannot read JSON file", err) } err = json.Unmarshal(data, availability) if err != nil {  log.Println("Error unmarshalling json data:", err) } // filter off those that we want to look for for _, item := range availability.Items {  for _, data := range item.Data {   for _, record := range records {    if data.CarParkNumber == record.CarParkNo {     carparkdata = append(carparkdata, data)    }   }  } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(carparkdata)}// get card park records based on a queryfunc getCarParkRecords(query string) []CarParkRecord { carparks := new(CarParks) resp, err := http.Get(infoUrl + query) if err != nil {  log.Println("Cannot call url", infoUrl+query, err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil {  log.Println("Cannot read JSON file", err) } err = json.Unmarshal(data, carparks) if err != nil {  log.Println("Error unmarshalling json data:", err) } return carparks.Result.Records}

这就是整个API程序,大概120行左右代码。咱们来剖析一下。

Manifest文件

之前提到有两个文件定义ChatGPT插件,一个是manifest文件,须要放在运行API程序的服务器上的.well-known/ai-plugin.json门路下。

为此,我提供了一个JSON文件。如果不须要身份验证,则格局绝对简略。

{    "schema_version": "v1",    "name_for_human": "HDB Car Park",    "name_for_model": "hdbcarpark",    "description_for_human": "For checking availability of car park \      lots at various HDB car parks around Singapore.",    "description_for_model": "For checking availability of car park \      lots at various HDB car parks around Singapore.",    "auth": {      "type": "none"    },    "api": {      "type": "openapi",      "url": "https://carpark.sausheong.com/openapi.yaml",      "is_user_authenticated": false    },    "logo_url": "https://carpark.sausheong.com/static/logo.png",    "contact_email": "sausheong@sausheong.com",    "legal_info_url": "https://carpark.sausheong.com/static/legal.html"}
OpenAPI标准

第二个是API的OpenAPI标准。和后面一样,我提供了openapi.yaml文件,文件地位在下面的manifest文件中指定。

openapi: 3.0.1info:  title: HDB Car Park  description: For checking avaiability of car park lots at various HDB \    car parks around Singapore  version: 'v1'servers:  - url: https://carpark.sausheong.compaths:  /info?q={location}:    get:      operationId: getCarParkInfo      summary: Get information about HDB car parks at a location      parameters:      - in: path        name: location        schema:            type: string        required: true        description: location to search for HDB car parks      responses:        "200":          description: OK          content:            application/json:              schema:                $ref: '#/components/schemas/getCarParkInfo'  /availability?q={location}:    get:      operationId: getCarParkAvailability      summary: Get availability of HDB car parks in Singapore at this moment      parameters:      - in: path        name: location        schema:            type: string        required: true        description: location to search for HDB car parks            responses:        "200":          description: OK          content:            application/json:              schema:                $ref: '#/components/schemas/getCarParkAvailability' components:  schemas:    getCarParkInfo:      type: array      items:         type: object        properties:           _full_count:             type: string          short_term_parking:             type: string          car_park_type:             type: string          y_coord:             type: string          x_coord:             type: string          rank:             type: number          free_parking:             type: string          gantry_height:             type: string          car_park_basement:             type: string          night_parking:             type: string          address:             type: string          car_park_decks:             type: string          _id:             type: integer            format: int          car_park_no:             type: string          type_of_parking_system:             type: string    getCarParkAvailability:      type: object      properties:         timestamp:           type: string          format: date-time        carpark_data:           type: array          items:             type: object            properties:               carpark_info:                 type: array                items:                   type: object                  properties:                     total_lots:                       type: string                    lot_type:                       type: string                    lots_available:                       type: string              carpark_number:                 type: string              update_datetime:                 type: string

该文件定义了两个门路,/info/availability,用于提供对应的两个性能。这两个门路还能够承受查问参数location,用于过滤地位。

接下来看看具体性能。

停车场信息

getCarParkRecords函数间接向data.gov.sg API发送GET申请,并返回CarParkRecord构造的切片。

// get card park records based on a queryfunc getCarParkRecords(query string) []CarParkRecord { carparks := new(CarParks) resp, err := http.Get(infoUrl + query) if err != nil {  log.Println("Cannot call url", infoUrl+query, err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil {  log.Println("Cannot read JSON file", err) } err = json.Unmarshal(data, carparks) if err != nil {  log.Println("Error unmarshalling json data:", err) } return carparks.Result.Records}

info处理程序应用getCarParkRecords来获取记录,并作为JSON响应返回。

// get information on car parkfunc info(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") records := getCarParkRecords(query) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(records)}
停车场应用状况

查看停车场的应用状况略微有点辣手。availability处理程序首先调用getCarParkRecords来获取感兴趣的停车场。

而后调用data.gov.sg CKAN API来获取新加坡所有HDB停车场的以后应用状况,并放入CarParkAvailability构造体中。

// get availability on car parkfunc availability(w http.ResponseWriter, r *http.Request) { // get the car parks requested query := r.URL.Query().Get("q") records := getCarParkRecords(query[:len(query)-1]) // get the availability of car parks in Singapore now now := time.Now() dt := now.Format(time.RFC3339) availability := new(CarParkAvailability) carparkdata := []CarParkData{} resp, err := http.Get(availUrl + url.QueryEscape(dt)) if err != nil {  log.Println("Cannot call url", availUrl+dt, err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil {  log.Println("Cannot read JSON file", err) } err = json.Unmarshal(data, availability) if err != nil {  log.Println("Error unmarshalling json data:", err) } // filter off those that we want to look for for _, item := range availability.Items {  for _, data := range item.Data {   for _, record := range records {    if data.CarParkNumber == record.CarParkNo {     carparkdata = append(carparkdata, data)    }   }  } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(carparkdata)}

而后遍历CarParkAvailabilityCarParkRecords中的停车场数据,并匹配停车场编号以过滤CarParkAvailability。最初,以JSON格局返回停车场应用数据。

启用CORS

须要留神,要为API程序启用CORS(跨域资源共享,Cross-Origin Resource Sharing),从而容许ChatGPT调用API。我将其实现为每个处理程序的HTTP中间件。

// middleware to set CORS for handlersfunc cors(h http.HandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  w.Header().Set("Access-Control-Allow-Origin", "https://chat.openai.com")  w.Header().Set("Access-Control-Allow-Methods", "GET")  w.Header().Set("Access-Control-Allow-Headers",     "Origin,Content-Type,OpenAI-Conversation-ID,OpenAI-Ephemeral-User-ID")  h.ServeHTTP(w, r) })}

就这么多!

部署插件

ChatGPT容许咱们装置运行在计算机上的插件,也就是说,能够在本地主机上运行,这对于测试和调试十分有用。同时也意味着须要将manifest文件和OpenAPI标准指向localhost。

一旦对测试后果感到称心,就能够将其部署到互联网上,让最多15个用户进行测试。部署插件相似于部署任何API,在此不再详述,除了须要为服务提供适当的域名,并且API域名须要与manifest文件中的分割电子邮件地址相匹配,还须要通过HTTPS运行。

一旦部署了API,就能够将其作为插件装置在ChatGPT上。

为什么这么繁琐?

如果你认为对于API来说太过于繁琐,只有看看当我在ChatGPT上问更简单的问题时,这个插件是如何回应的。请记住,插件不只是向用户提供数据,也为当今最先进的AI模型之一提供数据,以用于答复问题。

我试着比拟多层停车场和高空停车场,这很无效,感觉我是在问一个领有所有数据并可能为我剖析数据的人。

问: 在淡滨尼,多层停车场是否比高空停车场有更多可用车位?

ChatGPT: 依据数据,这里是淡滨尼多层停车场和高空停车场的比拟:

多层停车场:

总停车位: 26,486

空停车位: 14,070

空停车位占比: 53.1%

高空停车场:

总停车位: 26,486

空停车位: 198

空停车位占比: 18.6%

因而,与高空停车场相比,淡滨尼的多层停车场的可用车位比例更高。

接下来,我试着问哪个停车场车位最多,离这里有多远。

*问: 淡滨尼哪个停车场的车位最多,地址是什么? 离淡滨尼购物中心有多远?

ChatGPT: 淡滨尼可用车位最多的停车场位于NT5,共有1489个车位,其中有812个空车位。

此停车场地址为"淡滨尼中环7号510C座/淡滨尼广场"。

至于到淡滨尼购物中心的间隔,我须要计算一下,请稍等。

"NT5"停车场位于"淡滨尼中环7号510C座/淡滨尼广场",间隔淡滨尼购物中心约500米,步行大概6分钟。请留神,理论工夫可能会依据路线和步行速度而有所不同。

这就很有意思了,当ChatGPT没有数据时,就会胡编乱造。它不晓得停车场离购物中心有多远,所以就编了一个数字,猜想人们从停车场走到购物中心须要多长时间。理论间隔为1公里,步行工夫约为12分钟(谷歌地图)。

我还试着让它比拟新加坡两个不同地区的停车场应用状况,也很无效。

问: 淡滨尼的空停车位与宏茂桥相比,占停车场容量的百分比如何?

ChatGPT: 以下是淡滨尼和宏茂桥的停车场可用状况:

淡滨尼:

总停车位: 13,739

空停车位: 4,751

空停车位占比: 34.6%

宏茂桥:

总停车位: 30,544

空停车位: 9,380

空停车位占比: 30.7%

请留神,这些数字是实时更新的,随时可能会发生变化。

总结

编写ChatGPT插件非常简单,并且是一个弱小的工具。编写插件的技能与目前技能没什么不同,不像刚开始开发挪动利用时须要更多专业技能。很容易看出,当它正式公布时,很可能会有一个插件热潮。更令人兴奋的是,插件能够相互协作以提供协同性能。

ChatGPT依然存在问题,当数据不可用时,会产生幻觉景象。然而,从久远来看,这是能够解决的。我认为一个后劲微小的AI市场正在开始。

代码

能够从上面Git仓库取得所有代码。

ChatGPT plugin for Singapore HDB car park availability


你好,我是俞凡,在Motorola做过研发,当初在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。为了不便大家当前能第一工夫看到文章,请敌人们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的反对和能源,激励我继续写下去,和大家独特成长提高!

本文由mdnice多平台公布