昨日、ワークショップの講師をしました。 華やかなものを作ってもらうはずが色々ありまして、 超簡易なブログのWeb APIが最終形になってしまいそうでした。めっちゃ地味です。見た目JSONです。 このまま終わると地味な印象で終わってしまうのがヤベーってなってその場で思いついたのが「ChatGPTにそのAPIを使わせるChatGPTプラグインを作る」です。 それをライブコーディングしたら湧いたのでその話をします。
ワークショップ
ServerlessDay Tokyo 2023というイベントの一環で「Cloudflare WorkersとHonoのワークショップ」をやりました。 驚くべきことは「13時から17時」4時間という長丁場なことです。 未知です。 特にネタが尽きるの怖かったので、小粒な例題をいくつもつくっておきました。
想定外
いざ開始。 すると、別のワークショップとの会場が近く、声が通りにくいことが判明しました。 想定外のことですが、僕が移動したり、参加者の方々に前に来てもらって解決できました。
問題は次です。 CloudflareのR2というストレージ、D1というデータベースと、ChatGPTのプラグインを組み合わせたものを作ってもらおうと思ったのですが、 解決しようのない問題がおきました。 R2は無課金で使えるのですが、事前にクレジットカードの登録が必要。それをやってもらってなかった。 ChatGPTに至っては、有料プランでないとプラグインの機能が使えません。 どちらも当たり前のように使っていたので、気づかなかったのです。
切り替えて、事前に用意しておいた「ブログを作る」をやることにしました。
ブログを作ってもらう
R2は使わないものの、Cloudflare D1というデータベースを扱うことになり、体験としてはいい題材です。 データ構造はこちら。
- posts
- id
- title
- content
- created_at
これだけです。分かりやすくていいのですが、問題はできるものがWeb APIなので、地味です。JSONです。 辛うじて進みが早い人には、HTMLのガワをつけてもらったのですが、それでも地味です。
その時点で時間はもう16時になってました。うーん、このまま帰ってもらうのは悪いです。 なにか「沸く」ものをやらないと。
ChatGPTにそれを使わせる
そこで思いついたのが「ChatGPTにブログWeb APIを使わせる」でした。 ChatGPTのプラグインは事前にいくつか作ってあったので、コードを流用できます。 プラグインと今出来てるWeb APIを繋げたらなんとかなるのではないか。 参加者にやってもらうには時間と準備が足りないので、僕がその場でコードを書くことにしました。
ChatGPTのプラグイン
ChatGPTのプラグインは面白くて、ロジックさえあれば、以下の2つを用意するだけでよしなに繋がります。
- マニフェスト
- OpenAPIで書かれた仕様
ロジックはもうWeb APIで出来ています。なのでこの2つを作ればよい。
まずマニフェスト。これにはChatGPTに「このプラグインは何であるか?」を自然言語で書きます。 「Plug-ins for blogging」としました。全体は以下になりました。
export const aiPluginJson = {
schema_version: 'v1',
name_for_human: 'Blog',
name_for_model: 'Blog',
description_for_human: 'Plug-ins for blogging',
description_for_model: 'Plug-ins for blogging',
auth: {
type: 'none',
},
api: {
type: 'openapi',
url: 'http://localhost:8787/openapi.json',
},
logo_url:
'https://ss.yusukebe.com/a167950f7cab4b1a1b4a5afa199654363406aa91b8092b6b734b6df04e50ba68_800x603.png',
contact_email: '[email protected]',
legal_info_url: 'https://example.com/legal',
}
logo_url
がミソです。時間がなかったのですが、ここはこだわりたかったのでブログっぽい画像にしました。
これです。
次にOpenAPIに取り掛かります。 今回使っているHonoという(僕が作っているフレーワーク)には「Zod OpenAPI」という拡張があって、 ルート定義をプログラムで書くとドキュメントを生成しつつ、各エンドポイントに適切な型を提供してくれます。
記事を追加するルートを書いてみます。 まず、Zodというバリデータで、入力値を定義します。
const schema = z.object({
title: z.string().min(1),
content: z.string().min(1),
})
Zod OpenAPIの素晴らしいところは、定義が、定義のみならず、実際のロジック内で検証するためにも使われるという点です。 逆に言うと、これまで作っていたロジックで使っていたZodのスキーマをそのまま流用できました。
そのschema
で定義したフォーマットで、POST /posts
にJSONのリクエストが来ることを定義すると以下の通りです。
export const routeAddPost = createRoute({
method: 'post', // <== メソッド名
path: '/posts', // <== パス
operationId: 'addPost',
summary: 'Add a post',
request: {
body: {
content: {
'application/json': { // <= JSONのContent-Type
schema: schema, // <== Zodのスキーマ
},
},
},
},
responses: {
'200': {
description: 'OK',
},
},
})
あとはロジックです。これまでのコードにはPOST /posts
のロジックが書かれているので…
app.post('/posts', zValidator('form', schema), async (c) => {
const { title, content } = c.req.valid('form')
const id = crypto.randomUUID()
await c.env.DB.prepare('INSERT INTO posts(id, title, content) values (?, ?, ?)')
.bind(id, title, content)
.run()
return c.jsonT({
ok: true,
})
})
それをOpenAPIのエンドポイントにして、値を受け取る元をform
からjson
に変更するだけでOK。なはず!
app.openapi(routeAddPost, async (c) => { // <== app.post('/posts') を app.openapi(routeAddPosts) に変更
const { title, content } = c.req.valid('json') // <=== 'form'だったのを'json'に変更
// あとはおんなじ
})
これまたZod OpenAPIの素晴らしい点なのですが、上記のルート定義でContent-Type
がapplication/json
のリクエストが渡ってくると書きましたが、
そうすると「これまでform
から値をとってたけど、json
でとらないとだめだよ」とエディタが教えてくれます。
これはだいぶ助かった。
これでChatGPTプラグインの実装はできた、はず!
動かす
さあ、いよいよChatGPTのプラグインとして登録してみます。 僕を含め、その場にいる誰も試したことのないことで、どのような結果になるかドキドキです。 「ワークショップをやって、楽しかったっていうブログを書いて」と入力します。
で、で、できたー。 ChatGPTがブログの記事を考えて、できたものをWeb APIに投稿してくれてます。
試しに記事を見たいと言うとちゃんとWeb APIを叩いて、リストを取ってきてます。 その証拠に当初サンプルで入れてた「foo」、「Hello」という記事も一覧の中にいます。
沸く
沸きました! JSONを眺めるだけ終わるところが、ChatGPTにブログを書かせる、しかもそれがWeb APIと連携してDBに保存されている。 デプロイすればエッジで動かすこともできる。という夢のある話になったのではないでしょうか。
まとめ
以上、ワークショップをやって、ChatGPTにブログを書かせたらよかった話をしました。 参加者のみなさん、いたらぬところありまして、すませんでした。 Cloudflare WorkersとHono、ぜひ使ってみてください!
以下、ほとんどやりきれなかった資料です。