$shibayu36->blog;

株式会社アンドパッドでエンジニアをしています。プログラミングや読書のことなどについて書いています。

健康維持活動 - 寝る前に今日出来たこと3つを頭の中で考える

最近やっている健康維持活動(身体面・精神面両方)で、「寝る前に今日出来たこと3つを頭の中で考える」ということをしている。これが結構良いので共有してみる。

まず大前提として、自分は物事を悲観的に捉えることが多い性格である。悲観的に捉える性格は、良く働くと学習意欲や質の高い成果につながるが、悪く働くと精神面の健康を害する。

この性格が悪く働いている時期を観察すると、出来ていないことをを過大に捉え、出来ていることを過小に捉える傾向が強いことが分かった。ちなみにこのように「悪い点を過大評価し、よい点を過小評価する」という認知のクセは拡大視・縮小視と名付けられている(参考: よく見られる認知の誤り | 日野市の心療内科、こころクリニック)。

このような認知のクセ少しでもバランスが取れるように出来ないかなと思い、最近は「寝る前に今日出来たこと3つを頭の中で考える」ようにしている。やることは単純で、寝る前もしくは子どもの寝かしつけの時間などに、今日出来たことを3つ頭の中で考えるだけだ。出来たことというのも、非常に小さい出来事で良い。例えば

  • 仕事で1件タスクを終わらせられた
  • そろそろやらないとと思っていた子どもの制服のほつれを直せた
  • 今日疲れてたと思って無理かなと思っていたが、気合を入れて運動ができた
  • 10分、DeepLearningの勉強を進めることができた
  • 趣味のプロダクト作りをちょっとできた
  • 仕事でいつもと違う人とコミュニケーションできた

こんな感じの内容を3つほど頭に思い浮かべるだけで、不思議なことにほんの少しだけ「今日もなんだかんだ頑張ったな」という気持ちになれる。ほんの少しだけなのだけど、毎日続けていくと地味に気分に効いてくるし、拡大視・縮小視というクセも少しバランスが取れてくると感じている。

同じような特性がある人には「寝る前に今日出来たこと3つを頭の中で考える」が効果がある可能性もあるのでおすすめです。

英会話LINE BotのCloudflare Queues利用部分をwaitUntil APIで書き直す

OpenAI APIで英会話LINE Botを作る with Hono + Cloudflare Workers + Queues + D1 - $shibayu36->blog; を書いたら、id:mechairoi さんからwaitUntilが使えるんではと教えてもらった。確かにwaitUntilで書き直せればQueues依存がなくせるため、コンポーネントの複雑度もコストも抑えられる。

実際にやってみるとかなり簡単にできたのでメモしておく。

waitUntil APIとは

https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#waituntil

The waitUntil command extends the lifetime of the "fetch" event. It accepts a Promise-based task which the Workers runtime will execute before the handler terminates but without blocking the response. For example, this is ideal for caching responses or handling logging.

waitUntil APIを使えば、ひとまずレスポンスは返した上で、引き続きタスクを実行することができる。

waitUntilの具体的な使い方は Use waitUntil to perform work after Cloudflare Worker returns response | egghead.io の記事も参考になる。

Queuesを使った処理をwaitUntilで置き換える

waitUntilを使えばQueuesを使わずにLINEの1秒制約を回避できる。

  • LINEからリクエストが来たときは、context.waitUntilに重いasync functionを渡し、即座にレスポンスを返す
  • レスポンス返却後もCloudflare Workersは処理を続け、LINEにreplyする

これを実現したのが https://github.com/shibayu36/english-line-bot/pull/1/files のPRだ。

https://github.com/shibayu36/english-line-bot/pull/1/files#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80R48-R61 のようにasync functionを定義し

async function replyGeneratedMessage(env: Bindings, text: string, replyToken: string) {
  try {
    const generatedMessage = await generateMessageAndSaveHistory(text, env);
    console.log(generatedMessage);

    // 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);
  }
}

Honoを使っているとCloudflare Workersのcontextをc.executionCtxで取れるので、このように書く。 https://github.com/shibayu36/english-line-bot/pull/1/files#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80R37-R39

  c.executionCtx.waitUntil(replyGeneratedMessage(c.env, text, replyToken));

  return c.json({ message: "ok" });

こうするだけで、即座にレスポンスを返しつつCloudflare Workersで処理を続け、OpenAI APIの呼び出しなど重い処理が行える。便利。

まとめ

今回はwaitUntil APIを使うことで、Cloudflare Queuesへの依存を減らした。単純なAPIではQueuesを使わずとも十分に実現できるAPIが提供されていて、非常にありがたい。よりCloudflare Workersの使いやすさが際立った印象だった。

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を作るやり方など色々学べてよかった。