Blog

【ChatGPT】AIが返事してくれるツイートアプリの作り方

たびたび密かに更新しているアプリがあります。

Flowing-tweet
https://flowing-tweet.vercel.app

画面下のフォームにつぶやきを入れて送信すると、右から左にツイートが流れているだけのアプリですが、これに今回ChatGPTのAPIを使ってAIが返事をする機能を追加しました。
このアプリがどのようにできているかの紹介記事です。

機能としては単純なので、このようなものを作ろうとしたときに技術選定は色々だと思います。今回の方法は、一つの選択肢として参考にしていただければと思います。

技術スタック

  • フロントエンド Next.js
  • バックエンド(ツイート部分) Firebase (DB: Cloud Firestore)
  • バックエンド(ChatGPT部分) FastAPI

Next.js

フロントはNext.jsで作成しています。

  • フォームからツイートの内容を取得
  • Cloud Firestoreのクエリを実行(保存・削除)
  • ツイートのHTMLエレメントを作成
  • 秒数やカラー、AI有効化の設定を管理
  • テーマカラー切り替えの実装

ペラっと1ページのアプリなのですが、機能を洗い出すと意外とあるので、ライブラリを使う方が楽です。
開発体験が良いことと、vercelとの連携がしやすくデプロイが容易であること、また今後の拡張性を考えてNext.jsを選択しました。

Cloud Firestore

ツイート部分のバックエンドはFirebaseのCloud Firestoreを使っています。

  • ツイートをDBに保存
  • ブラウザにツイートを送信
  • DBからツイートを削除

フロントからCloud Firestoreのクエリを実行している形です。
Firestoreは無料枠があるので、今回のような小規模なアプリで使いやすいです。
一時期は、この課金システムがどうなっているのかどこを読んでもよくわからなかったのですが、最近は公式のドキュメントや、使用料・請求額のコンソールが充実してきているので、制限や課金されるまでのステップがわかりやすくなっています。
https://firebase.google.com/pricing?hl=ja

2024/6現在、超過時は下記のようになります。
https://firebase.google.com/support/faq?hl=ja#pricing-right-plan

  • Sparkプランでは超過時はアクセスが制限される
  • Blazeプランでは超過時に課金される

無料枠があることと、バックエンドの開発らしい開発をしなくてよいという理由でFirebaseを選択しました(のちにAI用を作ることになりますが)。
Firebase Javascript SDKのおかげでフロントにDBの操作をかくことができるのが魅力です。

FastAPI

ChatGPTに返事をしてもらうために、まずはPythonでOpenAIのAPIを叩いてボットのメッセージを取得する処理を作成し、それをAPIとして公開するためにFastAPIを使いました。

OpenAIへのリクエストは、わざわざPythonでかかなくてもNode.jsでできるライブラリもあります。
そちらを使えば今回のようにバックエンドのプロジェクトを別に作らずとも、Next.jsのApp Routerを使えば1プロジェクトで済むというメリットがあります。
なので自分が慣れているJavaScriptで完結させてもよかったのですが、そうしなかった理由は、「FastAPIの学習」を兼ねたかったから。あと、AIという分野にはPythonが強いという勝手なイメージがあったので、Pythonで作成しました。

PythonのWebアプリケーションフレームワークはいろいろありますが、そのあたりはあまり詳しいわけではないのでいろいろ調べてみました。
Webアプリケーション用APIを作成するということが目的だったので、

  • 自動ドキュメント生成(OpenAPIとJSON Schemaに基づいたAPIドキュメント)
  • 型検証、タイプヒントあり
  • コードが読みやすそう
  • 比較的新しくて人気がある

このあたりが決め手となり、FastAPIを選択しました。

Cloud FirestoreのonSnapshot

今回は、ツイートが保存されると同時に、そのツイートが「全ての人の」ブラウザに流れるというのがメインの機能です。
これを実現させるためには、DBに変更があったらクライアント側に通知する必要があります。
Firestoreにはリアルタイムクエリシステムがあります。onSnapshotを使えばデータベースの変更をリッスンし、データが変更されると低レイテンシの通知を受け取ることができます。チャットアプリなどのリアルタイムのデータ同期が必要なアプリケーションに使える機能です。
https://firebase.google.com/docs/firestore/real-time_queries_at_scale?hl=ja#understand_the_real-time_query_system

onSnapshotで、データベースが更新された時にコールバックを実行することができます。

import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
  const cities = [];
  querySnapshot.forEach((doc) => {
      cities.push(doc.data().name);
  });
  console.log("Current cities in CA: ", cities.join(", "));
});

https://firebase.google.com/docs/firestore/query-data/listen?hl=ja#listen_to_multiple_documents_in_a_collection

この機能を使って、Firestoreにツイートが保存されると同時に、アプリを開いているブラウザに変更のあったデータを通知することができます。

あとはフロント側でHTML作成してCSSアニメーションで流すという仕組みです。

ChatGPTのボットメッセージを作成

ChatGPTのAPIは、OpenAIのアカウントを作成し、APIキーを取得することで利用できます。

従量課金星の有料プランに加入する必要があるので、支払い登録が必要です。とりあえず使う前に何ドルか入れておく必要があります。
2024/6現在では、クレジット残高が閾値を自動的にリチャージするかどうかの設定をすることができます。アプリを止めたくない場合は自動リチャージ、止めてでも支払いたくない場合はリチャージしないことを選択できます。
また、月々の予算を設定することもできます。予算を超えた場合、APIリクエストは拒否されます。
使いすぎを防ぐことができるので、安心です。

ChatGPT APIへのリクエストは、以下のようにして行っています。

import os
from openai import OpenAI

api_key = os.environ["OPENAI_API_KEY"]
client = OpenAI(
    api_key=os.environ.get(api_key),
)

def botMsg(myinput: str):
    system = "あなたは〜〜〜です。などの設定を書く\n\n"
    profile = "あなたのプロフィールは以下です。\n名前:太郎\n趣味:散歩と音楽鑑賞\n好きな食べ物:ラーメン、チョコレート"
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        temperature = 1.5,
        messages=[
            {"role": "system", "content": system + profile},
            {"role": "user", "content": myinput},
        ],
    )
    return response.choices[0].message.content

OPENAI_API_KEYは環境変数に設定しておきます。

このリクエスト方法もアップデートによって変わっていく可能性があるので、公式ドキュメントを参照してください。

ちなみにchatgptのモデルは3.5を使用しています(安いので)。ゆえに、彼に知識を求めないでください。会話(といっても一問一答)ができればOKという趣旨です。たまによくわからない返答もします。

FastAPIでAPIを作成

FastAPIのAPIは、以下のようにして作成しています。

from fastapi import FastAPI
from bot import botMsg

class ModelName(str):
    msg: str

@app.get("/bot/")
async def bot(msg: str):
    res = botMsg(msg)
    return {"message": res}

その他、CORSの設定や、匿名ユーザー認証なども行なっています(これはそこそこの労力がかかった…割愛)。

作成したプロジェクトをデプロイし、Next.jsのフロントエンドでAPIを使うことができます。

FastAPIのデプロイ

vercelでもいけるのか検討してみたところpythonもデプロイできそうだったので、vercelを使用しました。
Pythonランタイムを使用してvercelでPythonを動かすことができるようです。
小さな規模のバックエンドなので、デプロイ先についてはそこまで検討をしていませんが、今回は充分でした。

requirements.txt(package.jsonのようなもの)というファイルを作成し、必要なPythonパッケージをリストアップします。

fastapi==0.111.0
uvicorn==0.29.0
openai==1.28.1

vercel.jsonを作成します。

{
  "builds": [
    {
      "src": "api/main.py",
      "use": "@vercel/python"
    }
  ],
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "api/main.py"
    }
  ]
}

フロントと同じようにGithubと連携しプッシュ⇨デプロイできます。
Python + vercelはあまりネット上に情報がないので、パターンとしては少ないのだと思いますが、この程度のものだと非常に簡単でした。

ちなみにdevelop環境(preview)での確認でCORSエラーが出た場合は、vercel認証をかもしれません(これで2.5日が溶けた)。
Develop環境でCORSエラー(Vercel認証起因)

おわりに

まだ変な返答をしますが、アプリの使用感が良くなった気がします(自己満足)。
引き続き、面白そうな機能があれば追加していきたいと思っています。

おすすめの記事 recommend blog

新着 new blog

github