ChatGpt Apiを用いてクイズWEBアプリを作った際のプロンプトの知見

# 初めに

ChatGPT APIを使った何か作ってみようと思い、AIに質問をして正解を当てるクイズWEBアプリを作ってみました。

![20200622.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2713225/4abc99ce-d278-33c7-ef5d-6c55efc22a9f.gif)


↓WEBアプリPubliQはコチラ。
https://publiq.cloud/

こちらのページではphp(laravel)で解説しますが、どの言語を使っている方にも分かるように、「プロンプトの使い方」に焦点を当てて書こうと思います。

アドバイス、ご助言頂けますと幸いです。

# クイズの概要

例えば：お題が「野菜」で正解が「ニンジン」の場合は「色はオレンジ？」「根菜？」等の質問をAIしながら答えを探すクイズにしてみました。
ちなみに某ラーメンyoutuberのクイズショート動画から着想を得ています。
https://www.youtube.com/shorts/uizb90b2U8s

# ざっくりChatGPT APIの概要

- おなじみのChatGPTをブラウザだけでなく自分のwebサービスで用いることを可能にするAPIです。
- 有料です。gpt-3.5-turboの場合は1000トークンあたり
入力が0.001ドル（約0.15円）
出力が0.002ドル（約0.3円）
※2024年1月時点

- ひらがなカタカナは1文字1トークン　漢字は1文字2～3トークン
- 英語は1単語1トークン　（例：Helloで1トークン）
　
- ↓php（laravel）でChatGPT APIにpostするサンプル

```ruby:sample.php
        // ChatGPT APIのエンドポイントURL
        $url = "https://api.openai.com/v1/chat/completions";

        // APIキー
        $api_key = env('CHAT_GPT_KEY');

        // ヘッダー
        $headers = array(
            "Content-Type" => "application/json",
            "Authorization" => "Bearer $api_key"
        );
        
        //パラメーター
        $param = [
            "model" => "gpt-3.5-turbo",
            "messages" => [
                [
                    "role" => "system",
                    "content" => "あなたはユーザーの友達です。タメ口で答えて下さい。"
                ],
                [
                    "role" => "user",
                    "content" => "おはよう"
                ]
            ]
        ];
        $param['max_tokens'] = 返答する際のトークン数を指定;
        $response = Http::withHeaders($headers)->post($url, $param);
        
```

role `"system"`は返答する際の指示を設定できます。
role `"user"`は文字通りユーザーがリクエストした内容です。

roleについてはコチラの方の内容がわかりやすいです
https://qiita.com/yu8ikmnbgt6y/items/2ea717d266b98d9b7f9d

上記パラメーターの構造から返答の質を決めるのはrole `"system"`に設定するcontent（プロンプト）です。
それではここから本題の、chatGPT APIを使った際の知見に入ります。

# できるだけコストを抑えたい

今回のクイズアプリ（[PubliQ](https://publiq.cloud/)）は質問に対して、
あえて「正解」「違う」「はい」「いいえ」「回答できません」の5通りでしか返答しないように制限をかけています。
理由としてはユーザーに考えさせるクイズにしたかったからです。

・・・ごめんなさい嘘です。コスト削減です（笑）

ChatGPT APIは決して高くありませんが有料である事には変わりありません。
そして先ほどの概要でも触れましたが、返答は英語での生成の方が圧倒的に安いです。

そのため、APIの返答は英語でして貰い、日本語に変換してユーザーに返すようにしました。
とりあえず最初に作ったプロンプトを見てみましょう！

```ruby:prompt.php
$prompt = <<<EOD
            クイズの判定の役割をお願いします。
            #お題
            {$genre}
            #正解のワード
            {$answer}
            #お願いしたい事
            ユーザーの回答が正解の場合は「correct」とお答えください。
            ユーザーの回答が間違っていた場合はユーザーの質問に「yes」か「no」で答えて下さい。
            回答できない場合は「unable」と答えてください。
            EOD;

 $chat_response = chatGptService::store($prompt, "{$userQuestion}?");
        
```
こちらのプロンプトの場合、AIは「correct（正解）」「yes（はい）」「no（いいえ）」「unable（意味不）」の4パターンの返答しかしないように制限をかけているため
返答する際のコストは1トークンに抑えられます。

# 上記プロンプトの問題点

先ほどのプロンプトはユーザーが正解を当てていたとしても「correct（正解）」ではなく、「yes（はい）」と回答してしまうケースが多々ありました。

問題点としてはAIに条件分岐でタスクを求めていたためです。

⬇プロンプト抜粋
- ユーザーの回答が正解の場合は「correct」とお答えください。
- ユーザーの回答が間違っていた場合はユーザーの質問に「yes」か「no」で答えて下さい。

プロンプト内で条件分岐をするのは現時点ではあまり得策ではなさそうです。
次に、 ユーザーのリクエストに応じて条件分岐で別プロンプトを呼び出す行うやり方の解説です。

# 改善したやり方
正解を回答する場合は「正解は○○？」という風にユーザーにリクエストしてもらうようにルールを追加しました。

こうすることで、
- ユーザーがした質問に「正解」と言うワードが含まれていた時
- 含まれていなかった時
の２パターンで条件分岐で別のプロンプトを呼び出せるようにできます。

### 「正解」というワードが含まれていたときの処理
クイズにあったているかどうかのみ判別させる。
その際、「correct」「different」の2通りで回答させる

### 含まれていなかった時の処理
ユーザーの質問にクイズの正解ワードを付け足してAIに質問をするように変更。
例　「正解ワード：ニンジン」＋「ユーザーの質問：根菜ですか？」　 =「ニンジンは根菜ですか？」
その際に、「yes」「no」「unable」の3通りで回答させる

処理の全体がコチラです。
```ruby:prompt.php
 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}
            #お願いしたい事
            ユーザーの回答が正解の場合は「correct」とお答えください。
            違っていた場合は「different」と答えてください。
            EOD;
            $chat_response = chatGptService::store($prompt, "{$sentence}?");
        }

        if ($mode == 'question') {
            $prompt = <<<EOD
            「yes」か「no」で答えてください。
            答えられない場合は「unable」と答えてください。
            EOD;
            $chat_response = chatGptService::store($prompt, "「{$answer}」は{$sentence}?");
        }

        if (strstr($chat_response, 'correct') !== false) {
            $result = "正解！";
        } else if (strstr($chat_response, 'different') !== false) {
            $result = "違う！";
        } else if (strstr($chat_response, 'unable') !== false) {
            $result = "回答できません";
        } else if (strstr($chat_response, 'yes') !== false) {
            $result = "はい";
        } else {
            $result = "いいえ";
        }
        return response()->json(['result' => $result], 200);
    }
        
```

# 作った感想

アプリの根幹部分の構築がもはやプログラミングコードではなく日本語だったことにすごい違和感を覚えました。
こうやってどんどんノーコード化が進むんだなとつくづく思わされます。

また、遊んでいただけると分かりますが回答の精度にまだまだ改善の余地があります。

より賢いAIを使えば回答の質は上がるかも知れませんが限界があります。

クイズアプリのクオリティアップのため
- ルール作り
- 条件分岐の工夫とそれに伴うプロンプトの設定

の2点に焦点をあててクオリティアップをしてみようかと思います。

あと、クイズのテーマを何かしらに特化して機械学習を取り入れるのも面白そうですね。

最後までご覧頂きありがとうございました。
ぜひともプレイしてみて下さい！
アドバイス、ご助言頂けますと幸いです！

よかったらXのフォロー・拡散もお願いします🙇‍♀️
https://twitter.com/PubliQ_AI

↓問題を作ることもできます。よかったら作ってみてください。
https://publiq.cloud/