【未経験が作る】AIに質問をして正解をあてるWEBアプリをリリースしました Next.js + Laravel + Gemini
↓こちらから遊べます
はじめに
この度、転職とAI(Gemini pro)に関する知見の向上を目的にクイズWEBアプリPubliQをリリースしました。
プログラミングに関する学習は約2年ほど、こちらの開発は2024年1月より始めました。
まだまだ回答精度に問題はあるものの、ひとまず形にはなったのでサービスの使用技術や開発過程を共有したいと思います。
私自身、独学で学習してきたのでそういった方々にとって少しでも参考になれば幸いです。
自己紹介
職業は小売業のWEBマーケティング部門
楽天市場等の店舗ホームページを作っていたため、元々HTML/CSS/Jqueryは使えます。
PHP学習歴:約2年
React学習歴:約半年
AI学習歴:1ヶ月
転職を考えたきっかけ
最初は趣味でプログラミングを始めました。
どうせなら、現職でも役に立つような物を開発しようと思い、PHPで業務自動化のプログラムを組んでいたのですが、とうとう職にしたい思いが強くなり転職活動を始めました。
どういうクイズなのか
AIに質問を行い、正解を当てるクイズです。
なお、ゲーム性を持たせるために質問には、「はい」「いいえ」「回答できません」の3通りしか答えないプロンプトを組んでいます。
また、AIの返答精度を上げるため、正解を当てる場合は「正解は〇〇?」と聞くルールを設けています。
3回間違えたらゲームオーバーです。
例えばお題が「野菜」で正解が「🥕ニンジン」の場合は、
あなた:「色は緑ですか?」
🧐AI:「いいえ」
あなた:「根菜ですか?」
🧐AI:「はい」
あなた:「正解はレンコン」
🧐AI:「違います」
みたいな感じです。
問題を作ってSNSでシェアできます
PubliQの醍醐味として、ユーザーが問題を作ってXやLineでシェアできるところです。
また、作った問題は公開設定の場合はランダムで他のユーザーが遊べます。
よろしければ、こちらのページをご覧の方も問題を作って拡散してください🙇🙇🙇
なぜクイズアプリを作ろうと思ったのか
今後はAI社会に突入していくと思うので、AIを生かしたものを作ろうと思い至り作成しました。
・・・これは半分本当ですが、本音は別にあります。
実は本命のポートフォリオが別にあるのですが、あるYoutuberのクイズの動画を見て「これをクイズアプリにすればバズるのでは!?」と突拍子に思い、こちらのアプリ作りました(笑)
ポートフォリオが多いほうが面接の時に印象が良いですしね。
ちなみにこちらがきっかけの動画です。
主要な使用技術
前置きが長くなってしまいましたがここからは技術的な話になります。
フロントエンド
- Next.js14 (App Router)
- TypeScript
- React
- Tailwind CSS (CSSのフレームワーク)
- shadcn/ui(UIコンポーネント)
- Recoil(状態管理)
- Axios(非同期処理)
- swr(データフェッチ)
- reCAPTCHA v3(ボット対策)
- useSound(効果音)
- zod(バリデーション)
- eslint/prettier(コード整形)
バックエンド
- Laravel9
- PHP
- Laravel Sanctum(SPA認証)
- gemini-api-php(Geminiをphpで使うため)
Gemini ApiをLaravelで動かす理由
Genimi Apiは、Nextのサーバーアクションズで実行した方がパフォーマンスが向上しそうですが、
将来的に、ユーザー行動の統計計測やユーザー認証機能を実装を考えた時にLaravelで実装したほうが役割分担が明確にできて良いかと思ったためLaravelで実装しました。
あと、Gemini Apiの実装がLaravelのライブラリであればすんなり実装できたのもデカいです。
インフラ構成
月額料金安めなAmplifyとLightsailで構成しました。これで月額1,000円前後ぐらいでしょうか。
デプロイも比較的、簡単なほうかと。
フロントエンド
AWS Amplify
バックエンド
Amazon Lightsail(LAMP)
データベース設計
クイズに関わってくるのはなんと1テーブルのみです。 今後タグ
やユーザー
テーブルを追加予定です。
questionsテーブル
name | type | 説明 |
---|---|---|
id | bigint(20) | クイズのID |
genre | varchar(255) | お題 |
answer | varchar(255) | 正解ワード |
hint | varchar(255) | ヒント |
failWord1 | varchar(255) | 地雷ワード1 |
failWord2 | varchar(255) | 地雷ワード2 |
failWord3 | varchar(255) | 地雷ワード3 |
good | int(10) | 高評価 |
bad | int(10) | 低評価 |
play | int(10) | プレイ回数 |
isPublic | tinyint(1) | 公開or限定公開 |
created_at | timestamp | 作成日 |
updated_at | timestamp | 更新日 |
フロントエンド関連
フロントエンド関連では、どのようにUIを構築したかについて記載いたします。
App Routerのディレクトリ構成
ディレクトリ構成はベストプラクティスがわからないため、
Appディレクトリにquizディレクトリを作りクイズ関連のコンポーネントをまとめました。
ーApp
ーquiz
ーchat.tsx(チャットを表示するコンポーネント)
ーcreateForm.tsx(問題を作る画面のコンポーネント/バックエンドへ問題作成post処理)
ーfail.tsx(ゲームオーバー時に表示するコンポーネント)
ーsendForm.tsx(ユーザーの送信関連/バックエンドへ質問post処理)
ーlayout.tsx(recoilRootやGA4のproviderはここに設置している)
ーpage.tsx(quizディレクトリのレイアウトはここでしている)
ーcomponents
ーui(shad/cnのパーツ)
ーhooks
ーquestion.ts(バックエンドからのフェッチやクイズ関連の関数はここに)
ーvalidation
ーcreateQuestionForm.tsx(出題のバリデーション)
ーsendChatForm.tsx(質問する際のバリデーション)
componentsディレクトリはappディレクトリ外に配置しました。
理由としてはcomponentsディレクトリには純粋なパーツとshadcn/ui(UIコンポーネント)の物だけをまとめておきたかったからです。
通常時
「問題を作る」をクリックするとモーダル表示
ゲームオーバー時に表示
バックエンド関連
先程も書きましたが、Gemini API はLaravelで動かしています。
そのため、Gemini APIを使いどのように処理を行っているかについて記載致します。
LaravelへのGeminiの導入はこちらをご確認ください。
Gemini Apiを動かす処理
回答の精度を上げるためにユーザーのリクエストが「正解は◯◯?」と質問している時と普通に質問している場合で別のプロンプトを呼び出しています。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Question;
use Exception;
use GeminiAPI\Laravel\Facades\Gemini;
class QuestionController extends Controller
{
public function store(Request $request)
{
//お題
$genre = $request->genre;
//正解ワード
$answer = $request->answer;
//ユーザーの質問
$sentence = $request->sentence;
if (preg_match("/正解|せいかい|答え|こたえ/", $sentence)) {
$mode = "quiz";
} else {
$mode = 'question';
}
if ($mode == 'quiz') {
$prompt = <<<EOD
クイズの判定の役割をお願いします
#お題
{$genre}
#正解のワード
{$answer}
#お願い
回答が正解の場合は「正解」
違う場合は「違う」と答えて
#ユーザーの回答
$sentence;
EOD;
$chat_response = Gemini::generateText($prompt);
}
if ($mode == 'question') {
$prompt = <<<EOD
質問に「はい」か「いいえ」か「回答できません」と答えて
#質問
「{$answer}」は{$sentence}?
EOD;
$chat_response = Gemini::generateText($prompt);
}
if (strstr($chat_response, '正解') !== false) {
$result = "正解!";
} else if (strstr($chat_response, '違う') !== false) {
$result = "違う!";
} else if (strstr($chat_response, '回答できません') !== false) {
$result = "回答できません";
} else if (strstr($chat_response, 'はい') !== false) {
$result = "はい";
} else {
$result = "いいえ";
}
return response()->json(['result' => $result], 200);
}
}
「正解は◯◯」の場合
ユーザーのリクエストをそのままAIに渡します。
if ($mode == 'quiz') {
$prompt = <<<EOD
クイズの判定の役割をお願いします
#お題
{$genre}
#正解のワード
{$answer}
#お願い
回答が正解の場合は「正解」
違う場合は「違う」と答えて
#ユーザーの回答
$sentence;
EOD;
$chat_response = Gemini::generateText($prompt);
}
普通に質問している場合
ユーザーの質問に「正解のワード」を付け足してAIに質問をします。
例 「正解ワード:ニンジン」+「ユーザーの質問:根菜ですか?」 =「ニンジンは根菜ですか?」
if ($mode == 'question') {
$prompt = <<<EOD
質問に「はい」か「いいえ」か「回答できません」と答えて
#質問
「{$answer}」は{$sentence}?
EOD;
$chat_response = Gemini::generateText($prompt);
}
以前はGPT-3.5turboを使っていました。
以前は情報が結構でているGPT-3.5turboで動かしていたのですが、回答の精度に難があったため
出来たてホヤホヤのGemini proに切り替えました。
具体的な検証結果を出さなくて申し訳ないのですが、
GPT-3.5turboからGemini proに変えて明らかに回答精度が上がった気がします。
ただ、ちゃんと考えているからなのでしょうか、Gemini proは、たまーに回答が遅い時があります。
そしてなんと、Gemini proの場合は2024年1月現在はなんと無料です。
GPTは有料なので絶対にこっち(Gemini)のがいいですよ。。。
以前に、GPTのプロンプト検証についてもこちらの記事にまとめています。よかったら御覧ください。
感想
アプリの根幹部分の構築がもはやプログラミングコードではなく日本語だったことにすごい違和感を覚えました。
こうやってどんどんノーコード化が進むんだなとつくづく思わされます。
また、遊んでいただけると分かりますが回答の精度にまだまだ改善の余地があります。
より賢いAIを使えば回答の質は上がるかも知れませんが限界があります。
クイズアプリのクオリティアップのため
- ルール作り
- 条件分岐の工夫とそれに伴うプロンプトの設定
の2点に焦点をあててクオリティアップをしてみようかと思います。
あと、クイズのテーマを何かしらに特化して機械学習を取り入れるのも面白そうですね。
最後までご覧頂きありがとうございました。
ぜひともプレイしてみて下さい!
アドバイス、ご助言頂けますと幸いです!
よかったらXのフォロー・拡散もお願いします🙇♀️
全体のソースコードはこちらをご確認ください。
Discussion