【個人開発】ChatGPTを使ったTOEFL学習アプリを作りました

# アプリ作りました
3月にちょうどChatGPT APIが解禁したので、**ChatGPTを使ってTOEFL iBTスピーキング練習問題を無限に出力してくれるアプリ**を作りました。TOEFL iBTの勉強中、こんなアプリがあればなぁと思っていたのを具現化したものです。


![1024×500_01j-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607181/cee0a03a-bc27-f58a-6f5e-a9b8ceb0783f.png)


https://apps.apple.com/jp/app/1672838214

https://play.google.com/store/apps/details?id=com.tmasuko.toefl_speaking_exercise&fbclid=IwAR1sOu08MzfjYU_knloRz3Oq89wdzeAKWkuFdaahDL7-FviaxpUC2799MZk

## 特徴
* 全問題形式(Q.1〜Q.4)に対応
* 本番形式で、回答時間はタイマーで管理される
* ChatGPTの出力した問題はText-to-Speechで音声に変換
* 自分の回答は音声認識でテキスト化される
* 何回でもリトライできる

いつでもどこでも、オリジナルの練習問題に手軽にチャレンジできることを目指しました。


# 発端

TOEFLの勉強に疲れ切っていたある日、思い立ってChatGPTにこんな質問をしてみました。

```
Could you give me 10 exercises of TOEFL IBT speaking test Q.1? Each exercise must have the structure shown below:
List two opposite opinions. Each opinion has specific view but the reason is not described in the passage. The answerer will choose one opinion and explain the reason for it.
This is the example of exercise:
“Some people like going outside and having exercise but some people like staying home and watch TV. Which do you prefer?”
```

すると

```
・Technology has greatly improved our lives, but some people believe that it has had negative effects on our relationships. Which do you believe and why?
・Eating out is a great way to try new foods, but some people believe that cooking at home is healthier and saves money. Which do you believe and why?
...
[このノリで10個例題を出してくれた]
```

「これ使えんじゃね？」と思ったのが発端です。

# TOEFL iBTについて

英語の能力証明試験の一つとしてTOEFL iBTがあります。読む・書く・聞く・話すの能力を総合的に鍛えねばならず、特に自分にとってスピーキングは鬼門です。
問題集を買っても**収録問題数には限りがあり、もっとたくさんの問題が欲しい**と常々思っておりました。
一方で、タイマーを用意するなど周辺環境の整備が必要なのも相まって中々食指が動かず。やらなきゃいけないのはわかっているが遅々として進まず、なんとかして状況を打破したいと考えていました。
そんな折、今話題のChatGPTに聞いてみたところ素晴らしい例題を出力してくれたので、~~勉強そっちのけで~~開発を始めたという次第です。

# アプリの構成

## 各種機能と使った技術

Flutter×RevenueCatの黄金ペアです。例題出力部分は3月リリースのChatGPT APIを活用しました。

### RevenueCat
面倒なアプリ内課金のバックエンド処理を肩代わりしてくれるサービスです。これに慣れきってしまったので私はもう自力実装できるか怪しいです()
今回は初めて消耗型アイテムの購入管理を実装してみました。

https://www.revenuecat.com

### 例題出力 : ChatGPT API
みんなのお友達ChatGPTがAPIになって新登場。3月より使用可能となり、アプリへの組み込みが容易になりました。
使い方は至ってシンプルで、HTTP POSTでprompt(呪文)を投げると、自然な受け答えのテキストを返してくれます。

https://platform.openai.com/docs/api-reference/chat

#### 登録方法
上記リンク右上より「Signup」を選択し、まずはアカウント登録を行います。
ログイン後、右上のアカウントボタンをクリックすると下記のようなメニューが現れるので、「View API Keys」を選択します。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607181/5305ab52-cb1d-4132-a95f-c86bd7f3baf3.png">

「Create new secret key」を選択すると、新しくAPI Keyを取得できます。

![スクリーンショット 2023-04-15 15.50.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607181/1202fabb-5e12-2b66-95f5-25ed61453df9.png)

#### リクエストを投げる
Flutterでのリクエスト方法を記載します。まず、簡単にAPIを叩けるパッケージであるdioをインストール。

https://pub.dev/packages/dio

以下のように、pythonのrequestsばりに簡単にリクエストを投げることができます。
```dart
import 'package:dio/dio.dart';

final dio = Dio();
final response = await dio.get('https://www.google.co.jp');
print(response);
```
実際にChatGPTのエンドポイントにリクエストを投げるには以下のようにすれば良いです。細かいパラメータについてはAPI referenceをご参照ください。コピペOKです。
実際のアプリではmessagesを変化させてTOEFLのスピーキング問題を出力してくれるようにしています。


```dart:generator.dart
class HttpRequest {

  List<Map<String, String>> getMessages() {
    List<Map<String, String>> messages = [
    {
      "role": "system",
      "content":
          "You are helpful assistant."
    },
    {
      "role": "user",
      "content":
          "Plaese tell me about youeself."
    }
  ];

    return messages;
  }

  Map<String, dynamic> getPayload() {
    List<Map<String, String>> messages = getMessages();
    Map<String, dynamic> payload = {
      "model": "gpt-3.5-turbo",
      "messages": messages,
      "temperature": 0.5,
      "max_tokens": 100,
      "n": 1
    };
    return payload;
  }

  //POST
  Future<String> post() async {
    var dio = Dio();
    String authToken = yourOpenaiAPIKey; //さっき取得したAPI Keyを差し込む
    try {
      var result = await dio.post("https://api.openai.com/v1/chat/completions",
          options: Options(
            headers: {
              "Authorization": "Bearer $authToken",
            },
            contentType: Headers.jsonContentType,
          ),
          data: getPayload());

      if (result.statusCode != 200) {
        return "";
      }
      

      //正常にレスポンスがくれば、ChatGPTが返してきた複数個の回答のうち先頭のものを抽出する
      String res = result.data['choices'][0]['message']['content'];

      return res;
    } catch (e) {
      return "";
    }
  }
}

```



### 例題を音声に変換して再生する
Flutterのパッケージであるflutter_ttsを使用しました。端末の持つテキスト読み上げ機能をそのまま使うものです。
登場人物が2名になる、会話文の読み上げをさせる場合は、**一文一文読み上げるごとに音声を変更する**ことで対応しています。

https://pub.dev/packages/flutter_tts

### 準備時間が与えられ、タイマーでカウントが始まる
前作「[おかたづけタイマー](https://qiita.com/rokko2massy/items/8ac84684c597e906aad3)」の知見を活用しました。
画面上では円形の領域が残り時間を表していて、こちらが1秒ごとに減っていくような構成にしています。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607181/9d60f74b-db87-67a9-65c4-29f9bd473e0f.png">


### 制限時間内に回答を喋る。音声はテキストに変換される
Flutterの音声認識パッケージ「speech_to_text」を使用しました。こちらもflutter_ttsと同じく端末の音声認識機能を活用します。やはり無料で使えるのが一番大きいですね。

https://pub.dev/packages/speech_to_text

音声認識と音声読み上げは外部APIを使った方が精度は高いのですが、インターネット接続を常時必要とする(一回生成した問題にリトライする時とか不便)点とAPI利用料がネックとなり、採用を見送りました。

## マネタイズ

例題の出力にはアプリ内通貨「コイン」が必要な設計にしています。コインは購入もできますが、1日1枚無料で取得可能です。
また、google admobのリワード広告を実装しており、1日5回まで無料でコインが取得できます。

https://developers.google.com/admob/flutter/rewarded?hl=ja

# リジェクト履歴
## ChatGPTという文言は使わない
「ChatGPT」というワードを説明文やサブタイトルに加えると、OpenAIの公式アプリみたいな誤解を生むとしてリジェクトされました。「ChatAI」に修正することで事なきを得ました。
調べたら他のアプリだと「ChatGPT」って使ってるものもありますね・・・担当者によって違うかもしれませんが、使わないに越したことはないと思います。


## 音声を何に使うかちゃんと記載
テキスト化するために音声を使用します！というのをinfo.plistに明記しました。

## サポートページの不備
サポートページのURLを記載せねばならないのですが、そのページに問い合わせ先メールアドレスを記載していませんでした。メールアドレスを記載したところ審査を通過しました。


# その他知見
## androidの音声認識のタイムアウト
androidは、音声認識開始後一定時間音声入力がないと自動で認識を終了してしまいます。これは端末側の音声認識機能を使用する以上避けられないようです。以下にspeech_to_textの注意事項を引用します。

> Android speech recognition has a very short timeout when the speaker pauses.  The duration seems to vary by device and version of the Android OS. In the devices I've used none have had a pause longer than 5 seconds. Unfortunately there appears to be no way to change that behaviour.

[原文 : speech_to_text - Troubleshooting](https://pub.dev/packages/speech_to_text#speech-recognition-stops-after-a-brief-pause-on-android)

## ChatGPT組み込み時の注意
ChatGPTは「知ったかぶり」をするので、ユーザーが自由にプロンプトを入力できるような仕組みだと不都合が生じると考えました。
今回のように例題を出力してくれるとかならいいんですが、Appleの審査に出すにあたって「プロンプトはハードコーディングしてあります！ユーザーの自由入力じゃないです！！」をアピールした方がいいかなと思って、一応前プロンプトをpdfにして審査時にくっつけました。
もしかしたら不要だったかもしれませんが、ご参考まで。。。

# 最後に
ChatGPTに代表される自然言語処理AIの発達は目覚ましく、特に語学学習においてはパラダイムシフトの感があります。今後もいろんなサービスが出てくると思いますので、注視していきたいです。


実は今日TOEFLを受けてきたのですがやはりスピーキングが一番足を引っ張っていたので、開発した時間が無駄じゃなかったことを証明するため、自分でこのアプリをたくさん使って鍛えます！←