ハッカソンや動作検証に使える ChatGPT API を使ったテンプレート Web アプリを作ました

# はじめに

かなり適当ではありますが、ChatGPT API を使った Web アプリケーションのコードを公開しました。内容的には ChatGPT のポータルの劣化版ではありますが、自分で ChatGPT を組み込んだアプリを作りたいけどどうしたらいいの？という人への最初の一歩向けです。

GitHub のリポジトリ -> [shyamagu/chatgpt-web-template](https://github.com/shyamagu/chatgpt-web-template)

なお、こういうちょっと作りたいなという場合に、個人的には Vue (CDN) とNode.js が最強だと思っているので、下図のような構成となってます。（ちゃんと作るならまた違うものにするかもしれませんが）

ハッカソンをやってみたい、という場合にもそれなりに使えるんじゃないかなとは思いますが、お好みのテクノロジーでガリガリ書ける人たち向けではありませんので、あしからず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/4b475e92-9953-7c1a-fae4-fd2f6703947c.png)


なお、以下の手順では Git と Node.js はお使いのPCに入っている前提です。必要に応じて以下の公式手順なども見て下さい
- [Git のインストール](https://git-scm.com/book/ja/v2/%E4%BD%BF%E3%81%84%E5%A7%8B%E3%82%81%E3%82%8B-Git%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- [Node.js ダウンロード](https://nodejs.org/ja/download)

# テンプレート Web アプリの実行方法

## 1. まずはソースコードを clone します
```
git clone https://github.com/shyamagu/chatgpt-web-template.git
```
そうすると、以下のようなファイル構成になっていると思います。
```
  chatgpt-web-template
    │  .gitignore
    │  app.js
    │  index.html
    │  openai.js
    │  openai_azure.js
    │  package.json
    │  prompts.js
    │
    └─public
            style.css
```

## 2. 設定ファイルを作ります(or 環境変数を設定します)

OpenAI か、Azure OpenAI かで設定が変わりますので、お好みの方でどうぞ（同じ内容の環境変数でももちろん大丈夫です）

### OpenAI の ChatGPT を使う場合

clone したフォルダのルートに、.env ファイルを作成します。

<b>.env ファイル</b>
```properties
OPENAI_API_KEY={ここに OpenAI の KEY }
```
KEY は、OpenAI の[公式サイトなど](https://help.openai.com/en/articles/4936850-where-do-i-find-my-secret-api-key)を参考に取得してください。

### Azure OpenAI Service の ChatGPT を使う場合
作成する .env ファイルは、以下のようになります。
```properties
AOAI_ENDPOINT={エンドポイント名}/openai/deployments/{デプロイモデル名}/chat/completions?api-version=2023-03-15-preview
AOAI_API_KEY={ここに AzureOpenAI の KEY}
```
ENDPOINTという変数に、{エンドポイント名}+αを記載してしまっていますが、[公式のクイックスタート](https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/chatgpt-quickstart?tabs=command-line&pivots=rest-api)などを参考に、設定してください。なお、モデルのバージョンはとりあえず今日時点の最新にしてます。

加えて、app.js の 20-21 行目あたりにあるコードを以下のように callAzureChatGPT を使うように変えてください。
```js
    const answer = await callAzureChatGPT(messages);
    //const answer = await callChatGPT(messages);
```
なお、Azure OpenAI Service を使う場合は、事前申請が必要なこと＋ ChatGPT モデルの生成が必要となります。細かいところは、[公式のクイックスタート](https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/chatgpt-quickstart?tabs=command-line&pivots=rest-api)を参照してください。

## 3. 実行します
以下のスクリプトを実行します。
```bash
npm install
node app.js
```

## 4. http://localhost:3000 にアクセスします
以下の画面が出てきたら起動成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/d8eb234d-08b5-ee30-f9cc-697a39836b80.png)

あとはメッセージを送信すると、私が適当に作ったシステムプロンプトで ChatGPT と会話ができます。（どんな話題にも健康に関する話題を提供する AI になっています。コード的には以下を参考にしてください。）

```js
~~~~ openai.js ~~~~~
13  const system_prompt = {"role": "system", "content": prompts.health_master}

~~~~ prompts.js ~~~~
1   const health_master = `あなたは健康マスターです。
2   ユーザへの回答には、こじつけになっても最後に健康やカロリーに関する話題を入れてください。
3   例）

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/cd775c13-ba92-0d3a-3aa4-d0ef7ff62520.png)

# テンプレート Web アプリの解説
以下ではコードの中身について簡単に解説します

## フロントエンド
index.html および public/style.css が該当します。これだけです。何気に style.css にかなりの時間を使いましたが、見るべきは index.html オンリーです。
HTML部分は以下が全部です。メッセージのやり取りを表示するチャットフィールドと、メッセージを入力して送信するメッセージボックスの2つがあります。

```html
        <div class="main">
            <h2 class="title">{ChatGPT Chat Template}</h2>
            <!-- チャットフィールド -->
            <div id="chatfield" class="chatfield">
                <div v-for="message in messages" :class="['chat_'+message.role]">
                    <pre class="chat_message">{{message.content}}</pre>
                </div>
                <div v-if="loading" class="loader"></div>
            </div>
            <!-- メッセージボックス -->
            <textarea class="messagebox" title="chat" name="chat" id="chat" placeholder="メッセージを入力してください" v-model="message"></textarea>
            <div class="messagebox_bottom">
                <div v-if="error" class="error">{{error}}</div>
                <button class="button_clear" type="button" @click="clear">履歴をクリア</button>
                <button class="button_send" type="button" @click="send">メッセージを送信</button>
            </div>
        </div>
```

```@click="send"``` となっている部分がメッセージ送信関数を呼んでいます。index.html の70行目からの部分です。（以下）
やっていることは、メッセージボックスの内容(this.message)を、これまでの履歴の配列(this.messages）にpush して、 POST で/message を呼び出しているくらいです。
また ChatGPT からの返り値はこれまでの履歴の配列(this.messages) にpush しています。

```js
                // メッセージを送信する
                async send() {
                    this.loading = true
                    this.error = ""
                    
                    this.messages.push({
                        "role": "user",
                        "content":this.message,
                    })
                    this.message = ''

                    try {
                        // メッセージを送信する
                        const res = await fetch("/message", {
                            method: "POST",
                            headers: {
                                "Content-Type": "application/json",
                            },
                            body: JSON.stringify({
                                messages: this.messages,
                            }),
                        });
                        const json = await res.json();

                        this.messages.push({
                            "role": "assistant",
                            "content":json.answer,
                        })


                    } catch (e) {
                        this.error = "サーバ呼び出しでエラーが発生しました";
                    }
                    this.loading = false
                }
```


## サーバサイド
app.js および openai.js, openai_azure.js と prompts.js が該当します。
openai.js が OpenAI の ChatGPT を、 openai_azure.js が Azure OpenAI の ChatGPT を呼び出すようになっておりますが、動的な切り替えは実装しておらず、上述した通り app.js 内での呼び出す関数を変えて、サーバを起動しなおす運用です。
基本以下だけです
```js
app.post("/message", async (req, res) => {
  const messages = req.body.messages;

  try {
    //const answer = await callAzureChatGPT(messages);
    const answer = await callChatGPT(messages);
    res.json({
      answer: answer,
    });
  } catch (error) {
    console.error("ChatGPT call failed");
    console.log(error);
    res.json({
      answer: "ChatGPT 呼び出しでエラーが発生しました",
    });
  }
});
```

この中では openai.js の callChatGPT を呼んでいます。(下記)
パラメータなどについては[こちらの記事](https://qiita.com/shyamagu/items/4f61edee71aacdc77a1c)も参考にしてください。
``` js
async function callChatGPT(messages){

    const system_prompt = {"role": "system", "content": prompts.health_master}

    messages.unshift(system_prompt);

    const completion = await openai.createChatCompletion({
        model: "gpt-3.5-turbo",
        messages: messages,
        temperature: 1.0,
        presence_penalty: 0,
        frequency_penalty: 0,
        top_p: 1.0,
    });
    return completion.data.choices[0].message.content
}
```
プロンプトが長くなるのがいやだったので、今回は別のファイルに定義しています。prompts.js が該当です。ここにお好きな定数としてプロンプトを定義して、上記の```prompts.health_master``` を変えるとそのように動くようになります。

**注意**
prompts.js の最後に module.export があります。ここに追加した定数名を入れてください
```js
module.exports = {
    datascientist,
    shiretoko_master,
    book_query_search, //例えば、book_query_searchというモノを追加した場合
}
```

## 制限事項や考慮していない点
以下、今回のテンプレートでは考慮していない点、制限事項などになります。

#### 1. 入力チェックはしていない
メッセージの入力制限は一切ありません。無限に素の文字を送り込めるようになっています。なので、ちゃんとやるなら実装が必要です。

#### 2. プロンプトインジェクション対策はしていない
[こちらの記事](https://qiita.com/shyamagu/items/19c6e3c7dda54320aa36)にあるような、何らかのプロンプトインジェクション対策は本来は必要です。

#### 3. メッセージの入力履歴は**すべて**送信する形になっている
サーバサイドで配列の長さを制限しないと、トークンの限界まで入力を送り続けてしまいます。このまま使う場合は定期的に履歴をクリアする必要があります。

# まとめ

今回は ChatGPT API をつかったテンプレート Web アプリの使い方と中身の説明でした。ちょっとプロンプトの動きを試してみたいのであれば、Web ポータルから試せばいいのですが、画面を含めて何らかのアプリを作りたい、というときにさっくり触れるものにはなったのかなと思います。

繰り返しになりますが、Vue3.2(CDN) と Node.js のため、起動も早いとは思うモノの、きちんとやる場合はビルドどうするかとか考えた上で今回のテンプレートは使えないかもしれません。が、ひとまず動くものをそれっぽい画面でという機会（時間の短いハッカソンや、ちょっとした検証）などがあれば是非使ってみて下さい。