$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

OpenAI APIで英会話LINE Botを作る with Hono + Cloudflare Workers + Queues + D1

Cloudflare Worker + D1 + Hono + OpenAIでLINE Botを作るを見て、Cloudflare Workersに興味を持った。そこでLINEで英会話や添削ができるbotを作ってみた。

作ったもの

こんな形で英会話をしたり、英作文テーマを作ってくれたり、添削をしてくれたりする。

実際のコードは https://github.com/shibayu36/english-line-bot 。このコードをforkしてもらって、LINEチャネルの作成、Cloudflareへのデプロイ、src/prompt.tsのカスタマイズをすると、自分用のLINE botも作れると思う。

利用技術

開発Tips

基本的な作り方は Cloudflare Worker + D1 + Hono + OpenAIでLINE Botを作る と同じ。いくつか追加で工夫もしているので紹介する。

LINE側の1秒制限を突破する

【2023/05/26 22:00追記】 waitUntilというAPIを使うことでQueuesを使わずに解決できました。英会話LINE BotのCloudflare Queues利用部分をwaitUntil APIで書き直す - $shibayu36->blog;の記事を参照してください。またQueuesの実装を参考したい場合、https://github.com/shibayu36/english-line-bot/tree/d7a352baaf539e06f8047ff31a8790b8a89f984f のcommitで確認できるため、こちらをご覧ください。

新しくLINE Developers登録をしてWebhook APIの設定をしてみたのだが、Webhook APIに投げて1秒以内にレスポンスを返さない時にLINEがリクエストを切ってしまう動きをしていた(もしかしたら最近から?)。OpenAI APIを使うと普通に1秒は超えてしまうのと、リクエストを切るとCloudflare Workers側は処理を止めてしまうため、このままではうまく作れない。

どうしようかなと悩んでいると、ゆーすけべーさんがQueuesがあるということを教えてくれた。

ということで、1秒制限を突破するために、このように処理をすることにした。

  • LINEからリクエストが来たときは、Queuesに積むことだけを行い、即座にレスポンスを返す
  • Queuesの処理で実際にOpenAI APIにアクセスしたり、LINEへ投稿したりする

これを実現するため、まずQueuesのproducers/consumersの設定を行う

wrangler.toml

[[queues.producers]]
binding = "QUEUE"
queue = "english-line-bot-queue"

[[queues.consumers]]
max_batch_size = 1
max_batch_timeout = 30
queue = "english-line-bot-queue"

Honoを使った状態でQueuesのエントリポイントも生やす。 https://github.com/shibayu36/english-line-bot/blob/d7a352baaf539e06f8047ff31a8790b8a89f984f/src/index.ts#L71-L85

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return app.fetch(request, env, ctx);
  },
  async queue(batch: MessageBatch<QueueBody>, env: Bindings): Promise<void> {
    for (const message of batch.messages) {
      await handleQueue(message, env);
    }
  },
};

Queuesへの追加と即座のレスポンス返却はこんな感じ。 https://github.com/shibayu36/english-line-bot/blob/d7a352baaf539e06f8047ff31a8790b8a89f984f/src/index.ts#L41-L44

  await c.env.QUEUE.send({ text, replyToken });
  return c.json({ message: "ok" });

あとは実際に処理する関数を作る。 https://github.com/shibayu36/english-line-bot/blob/d7a352baaf539e06f8047ff31a8790b8a89f984f/src/index.ts#L71-L85

async function handleQueue(message: Message<QueueBody>, env: Bindings) {
  const { text, replyToken } = message.body;

  try {
    const generatedMessage = await generateMessageAndSaveHistory(text, env);

    // Reply to the user
    const lineClient = new Line(env.CHANNEL_ACCESS_TOKEN);
    await lineClient.replyMessage(generatedMessage, replyToken);
  } catch (err: unknown) {
    if (err instanceof Error) console.error(err);
    const lineClient = new Line(env.CHANNEL_ACCESS_TOKEN);
    await lineClient.replyMessage("I am not feeling well right now.", replyToken);
  }
}

ただしQueuesはWorkers Paid Plan ($5/month)に入る必要があるので注意。

npm run deployでsecretも含めて自動デプロイ

wrangler deployはWorkersなどのデプロイをいい感じにしてくれるが、secretについてはデプロイできない。一方で個人開発では大体手元開発と同じsecretを使うことが多く、単純に.dev.varsから自動でデプロイしても良いと考えた。

そういう作戦でdeploy.shというのを作って自動でデプロイしている。

deploy.sh

#!/bin/bash
set -euo pipefail

wrangler deploy

# Parse .dev.vars and set secrets
if [[ ! -f .dev.vars ]]; then
    echo "File .dev.vars not found!"
    exit 1
fi
while IFS='=' read -r name value
do
  echo "Setting $name"
  echo $value | wrangler secret put $name
done < .dev.vars

プロンプトのチューニングはスクリプトで試しながら行う

プロンプトのチューニングはかなり試行錯誤が必要だ。そこでここは適当にスクリプトを作り、色々試しながらチューニングをしていった。https://github.com/shibayu36/english-line-bot/blob/d7a352baaf539e06f8047ff31a8790b8a89f984f/src/tools/evaluate-prompt.ts のようなスクリプトを用意し、npx ts-node src/tools/evaluate-prompt.ts を実行しながらチューニングをしていった。

Cloudflareなどを使った開発の感想

  • 制約は厳しいが、簡単なWeb APIとかを作るには非常に楽に開発できる
    • 無料でもREST APIやCron Triggersによる定期実行が作れるWorkersや、SQLiteベースのRDBMSとして使えるD1が使える
    • 月$5払うと、Queuesによる非同期ジョブも使えて、かなりなんでもできる
    • Workersにアップロードできるサイズの制限が厳しい、axiosとかは動かないのでopenaiのnpmモジュールなどは使えないなどの制限はある
  • wranglerがとにかく楽で嬉しい。wrangler initしてwrangler deployするともうエンドポイントができているという気軽さ。QueuesやD1の作成も気軽にできる。
    • 仕事で本当に活用しようとして色々やろうとすると大変かもしれないが、個人開発としてはこのくらいで十分
  • Hono使っておくと、普通のWebアプリケーション開発の雰囲気でCloudflare Workersで動くWeb API作れて便利
    • Queuesとかもなんかいい感じになると嬉しいかもしれない。Honoのスコープ内ではなさそうではある

まとめ

今回はOpenAI API、Hono、Cloudflare Workers/Queues/D1を使って英会話LINE Botを作ってみた。Cloudflare Workersの気軽さ、Honoの使いやすさ、OpenAI APIを使って自分向けのbotを作るやり方など色々学べてよかった。