【初投稿】ChatGPT APIとGradioでチャットボットを簡単作成しよう！

# はじめに
皆さん、こんにちは！
Qiita初めての投稿となる今回は、今最もホットな話題のChatGPTをテーマにしてみました。
似たようなチャットボットプログラムは既にいっぱいあると思いますが、ChatGPT APIとPythonライブラリーのGradioを使ってシンプルにチャットボットを作成してみたので、その情報を共有したいと思います。なお、完成イメージは下記スクリーンショットの通りとなります。
![Image2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/ce76513b-81e6-652e-9b7f-6d738e67e582.jpeg)

# 準備
ChatGPT APIを使用するには下記が必要となります。

### ①OpenAIアカウント
[OpenAIのサイト](https://platform.openai.com/overview)でアカウントを作成（無料）

### ②APIキー
OpenAIアカウントでログイン後、右上の「Personal」部分をクリックしてメニューから「View API Keys」を選択します。
![Image01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/a876be6d-2753-c566-7a26-0659c0d2dd36.jpeg)

次に、API Keys画面で「Create new secret key」ボタンをクリックしてAPIキーを作成します。
![Image6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/f946d3f4-3dfd-c953-71d1-a21ada823b69.jpeg)

:::note warn
APIキーはこのタイミングでしか内容を確認できないので、必ず内容をファイルなどにコピーして保存して下さい。
:::

# APIコールしてみよう
ChatGPT APIをPythonで使用するには、openaiライブラリーが必要なので、pipコマンドでインストールします。
```
pip install openai==0.28.1
```
:::note warn
2023年11月、OpenAI APIがv1.0.0にアップデートされました。v1.0.0以降ではAPIインターフェースが大きく変更されているので、この記事のコードをそのままで実行するには、v1.0.0以前で最新のv0.28.1をインストールして下さい。
:::
そして、次のコードを入力したPythonファイルを作成します。
```python:call_api.py
import openai

# ファイルからAPIキーを取得してopenaiにセット
openai.api_key = open("api_key.txt", "r").read().strip('\n')

# APIコール
res = openai.ChatCompletion.create(
          model = "gpt-3.5-turbo",
          messages = [
              {
                  "role": "user",
                  "content": "チャットボットって何？"
              }
         ]
      )

# APIレスポンスを表示
print(res)
```
Pythonファイル「call_api.py」と同じディレクトリーに、APIキーの内容を保存したファイル「api_key.txt」を置いて、プログラムを実行します。
```
python call_api.py
```
正常に処理が完了すると、下記のようなJSON形式のデータが表示されます。
```
{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "content": "\n\n\u30c1\u30e3\u30c3\u30c8\u30dc\u30c3\u30c8\u306f\u3001\u4eba\u5de5\u77e5\u80fd\uff08AI\uff09\u3084\u81ea\u7136\u8a00\u8a9e\u51e6\u7406\uff08NLP\uff09\u306e\u6280\u8853\u3092\u5229\u7528\u3057\u3066\u3001\u4eba\u3068\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u884c\u3046\u30d7\u30ed\u30b0\u30e9\u30e0\u3067\u3059\u3002\u4e3b\u306b\u30c6\u30ad\u30b9\u30c8\u30c1\u30e3\u30c3\u30c8\u4e0a\u3067\u4f1a\u8a71\u3092\u884c\u3044\u3001\u8cea\u554f\u306b\u7b54\u3048\u305f\u308a\u3001\u60c5\u5831\u3092\u63d0\u4f9b\u3057\u305f\u308a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u307e\u305f\u3001\u8fd1\u5e74\u3067\u306f\u97f3\u58f0\u3084\u753b\u50cf\u306b\u3082\u5bfe\u5fdc\u3057\u3001\u3055\u307e\u3056\u307e\u306a\u5f62\u3067\u5229\u7528\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u4f01\u696d\u306e\u30ab\u30b9\u30bf\u30de\u30fc\u30b5\u30dd\u30fc\u30c8\u3084\u3001\u5546\u54c1\u3084\u30b5\u30fc\u30d3\u30b9\u306e\u6848\u5185\u3001\u8efd\u5fae\u306a\u30c8\u30e9\u30d6\u30eb\u306e\u89e3\u6c7a\u306a\u3069\u306b\u6d3b\u7528\u3055\u308c\u3001\u4eba\u306e\u624b\u306b\u3088\u308b\u5bfe\u5fdc\u306b\u6bd4\u3079\u3001\u8fc5\u901f\u304b\u3064\u4f4e\u30b3\u30b9\u30c8\u306b\u554f\u984c\u3092\u89e3\u6c7a\u3067\u304d\u308b\u3053\u3068\u304c\u9b45\u529b\u3067\u3059\u3002",
        "role": "assistant"
      }
    }
  ],
  "created": 1679320785,
  "id": "chatcmpl-6wAL3UwV9vYNpSwVDHKjkGI84qDQh",
  "model": "gpt-3.5-turbo-0301",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 220,
    "prompt_tokens": 16,
    "total_tokens": 236
  }
}
```

### contentプロパティ
contentプロパティの値、```\n\n\u30c1\u30e3\u30c3...```の部分がAIからの回答文です。
JSON出力するとUnicodeエスケープシーケンスで表示されるので分かりにくいですが、次のようにcontentプロパティの値を直接表示させると日本語で回答が返ってきている事が確認できます。
```
print(res.choices[0].message.content)

チャットボットは、人工知能（AI）や自然言語処理（NLP）の技術を利用して、人とコミュニケーションを行うプログラムです。主にテキストチャット上で会話を行い、質問に答えたり、情報を提供したりすることができます。また、近年では音声や画像にも対応し、さまざまな形で利用されています。企業のカスタマーサポートや、商品やサービスの案内、軽微なトラブルの解決などに活用され、人の手による対応に比べ、迅速かつ低コストに問題を解決できることが魅力です。
```

### usageプロパティ
usageプロパティには使用したトークン数がセットされています。

:::note warn
ChatGPT APIを使用すると、1,000トークンあたり、0.002ドルの料金が課金される点にご注意下さい。詳細は、[公式サイト](https://openai.com/pricing)をご参照下さい。
:::

- completion_tokens 
    - AIからの回答文のトークン数
- prompt_tokens
    - ユーザからの質問文のトークン数
- total_tokens
    - completion_tokensとprompt_tokensの合計トークン数（**このトークン数で課金される**）

トークン数とは文字数みたいなもので、公式サイトの[Tokenizer](https://platform.openai.com/tokenizer)を使用すると、文章のトークン数を計る事が可能です。
下記の例でも分かるように、文字数とは数え方が違うので微妙にズレがある点に注意して下さい。
![Image7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/f2759265-6668-bced-f63e-70e3565d08df.jpeg)

# APIコールごとに会話は完結
ChatGPT APIはステートレスなAPIです。
Web版のChatGPTを使っている際は気にする事はありませんが、ChatGPT APIを使用している場合、１回のAPIコールで会話は完結します。

例えば、先ほどのAPIコールの後で、「それについて具体例を挙げて」と会話を継続しようとしても、AIは前回の会話を覚えてないので「それ」が何の事か理解できません。
そのため、会話を継続して行う場合、それまでの（ユーザ＆AI両方の）全ての会話文をAPIにセットしてコールする必要があります。
つまり、会話の履歴管理が必要というわけです。

# 会話履歴を管理してみよう
ユーザ、AI、ユーザ、AIの順に２回連続で行われる会話の履歴をベタに管理するプログラムを作成してみます。
```python:conversation.py
import openai

# ファイルからAPIキーを取得してopenaiにセット
openai.api_key = open("api_key.txt", "r").read().strip('\n')

# 会話履歴用リスト型変数
message_history = []

# ユーザの最初のメッセージを履歴に追加
user_msg = "チャットボットって何？"
print(f"あなた：{user_msg}")
message_history.append({
    "role": "user",
    "content": user_msg
})

# APIコール（ユーザの最初のメッセージに対する回答を取得）
res = openai.ChatCompletion.create(
          model = "gpt-3.5-turbo",
          messages = message_history
      )

# ユーザの最初のメッセージに対する回答を表示
reply_msg = res.choices[0].message.content
print(f"AI：{reply_msg}")

# ユーザの最初のメッセージに対する回答を履歴に追加
message_history.append({
    "role": "assistant",
    "content": reply_msg
})

# ユーザの２つ目のメッセージを履歴に追加
user_msg = "それの具体例を挙げて。"
print(f"あなた：{user_msg}")
message_history.append({
    "role": "user",
    "content": user_msg
})

# APIコール（ユーザの２つ目のメッセージに対する回答を取得）
res = openai.ChatCompletion.create(
          model = "gpt-3.5-turbo",
          messages = message_history
)

# ユーザの２つ目のメッセージに対する回答を表示
reply_msg = res.choices[0].message.content
print(f"AI：{reply_msg}")
```

### message_history変数
今回はこの変数を使って、下記のような感じでメッセージ履歴を管理します。APIの仕様に合わせて、roleとcontentを持つ辞書型のリストとなっています。
| # | role | content |
| ---- | ---- | ---- |
| 0 | user | チャットボットって何？ |
| 1 | assistant | #0に対するAIの回答 |
| 2 | user | それの具体例を挙げて。 |
| 3 | assistant | #2に対するAIの回答 |

API仕様上、roleにはsystem, user, assistantの３つがありますが、今回はuserとassistantだけを使います。意味は文字通り、ユーザ（人間）のメッセージならuser、AIからの回答文ならassistantで、それぞれどちらのメッセージかを区別しています。

では、実行してみましょう。
```
python conversation.py

あなた：チャットボットって何？
AI：

チャットボットとは、人工知能を基にしたコンピュータープログラムで、ユーザーと対話することができます。顧客サポート、商品購入、情報検索、医療相談など、さまざまな目的に応じて使われます。主にメッセージングアプリやウェブサイトで利 
用され、近年はスマートスピーカーでも利用されるようになっています。
あなた：それの具体例を挙げて。
AI：以下は、いくつかのチャットボットの具体例です。

1. AmazonのAlexa：Alexaは、スマートスピーカーで使われる音声アシスタントです。Alexaを使って、音楽の再生、天気予報の確認、Amazonの商品の購入などができます。

2. LINEのClova：Clovaは、LINEのチャットボットです。Clovaは、翻訳、天気予報、ニュース、レストランの予約、タクシーの呼び出し、音楽の再生などの機能があります。

3. Watson Assistant：Watson Assistantは、IBMのチャットボットです。Watson Assistantは、顧客サポート、アポイントメントの予約、保険の請求処理、在庫管理など、さまざまな業務用途に使用されます。

4. Mitsuku：Mitsukuは、人間のように会話ができるチャットボットです。Mitsukuは、コミュニケーションの練習や遊び相手として使用されます。

これらの例は、それぞれ異なる目的に応じて、チャットボットがどのように使われるかを示しています。
```
会話の履歴を管理する事で、AIとの会話が継続できている事が確認できました。

# チャットボットをサクッと作成しよう
ここまででChatGPT APIの基本的な使い方は理解できたと思うので、最後にGradioライブラリーのChatbotを使って自分用のチャットボットを作成してみましょう。

まず、PythonのGradioライブラリーをpipコマンドでインストールします。
```
pip install gradio
```
そして、次のコードを入力したPythonファイルを作成します。
```python:chatgpt_bot.py
import openai
import gradio as gr

# ファイルからAPIキーを取得してopenaiにセット
openai.api_key = open("api_key.txt", "r").read().strip('\n')

# 会話履歴用リスト型変数
message_history = []

def chat(user_msg):
    """
    AIとの会話を実行後、全会話履歴を返す
    user_msg: 入力されたユーザのメッセージ
    """
    global message_history

    # ユーザの会話を履歴に追加
    print(user_msg)
    message_history.append({
        "role": "user",
        "content": user_msg
    })
    
    # ChatGPT APIコール
    res = openai.ChatCompletion.create(
        model = "gpt-3.5-turbo",
        messages = message_history
    )

    # AIの回答を履歴に追加
    assistant_msg = res.choices[0].message.content
    print(assistant_msg)
    message_history.append({
        "role": "assistant",
        "content": assistant_msg
    })

    # 全会話履歴をChatbot用タプル・リストに変換して返す
    return [(message_history[i]["content"], message_history[i+1]["content"]) for i in range(0, len(message_history)-1, 2)]


with gr.Blocks() as demo:
    # チャットボットUI処理
    chatbot = gr.Chatbot()
    input = gr.Textbox(show_label=False, placeholder="メッセージを入力してね").style(container=False)
    input.submit(fn=chat, inputs=input, outputs=chatbot) # メッセージ送信されたら、AIと会話してチャット欄に全会話内容を表示
    input.submit(fn=lambda: "", inputs=None, outputs=input) # （上記に加えて）入力欄をクリア

demo.launch()
```

### gradio.Chatbot()
gradio.Chatbotに全会話履歴を渡す際、各会話をタプルにしたリストで渡す必要があります（詳細は[公式ドキュメント](https://gradio.app/docs/#chatbot)を参照）。

| # | タプル |
| ---- | ---- |
| 0 | (ユーザの１回目の会話文, AIの１回目の回答文) |
| 1 | (ユーザの２回目の会話文, AIの２回目の回答文) |
| 2 | (ユーザの３回目の会話文, AIの３回目の回答文) |

### input.submit(fn=chat, inputs=input, outputs=chatbot)
input（Textbox）にメッセージが入力されて送信（submit）されたら、キーワード引数inputsで指定されたコンポーネント（input）を入力値として、キーワード引数fnで指定された関数```chat()```を実行します。関数の実行結果は、キーワード引数outputsで指定されたコンポーネント（chatbot）に渡されます。

### input.submit(fn=lambda: "", inputs=None, outputs=input)
ユーザがメッセージを送信したら、そのメッセージを消去します。キーワード引数outputsで指定しているinput（Textbox）に空文字を渡す事で実現していますので、関数は```return ""```する関数なら何でもいいですが、ここではラムダ式を使ってみました。この関数には引数は必要ないので```inputs=None```にしています。

それではこのチャットボットを実行してみましょう。
```
python chatgpt_bot.py
```
プログラムを実行すると、下記のようにURLが表示されるので、WebブラウザでそのURLにアクセスして下さい。
```
Running on local URL:  http://127.0.0.1:7860
To create a public link, set `share=True` in `launch()`.
```
こんな感じの画面がブラウザに表示されます。
![Image2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/f5cd09c2-fb66-3a14-40aa-bf8ba9f43bff.jpeg)
あとは自由にAIとの会話を楽しんで下さい♪
![Image3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/849813/79b1f7af-8d2b-563d-8677-a30cb93fa65b.jpeg)
終了させたい場合は、Pythonの方の画面（コマンドプロンプト等）に戻って、Ctrl + Cで終了します。

# 最後に
お疲れ様でした。今回はChatGPT APIの使い方を理解してもらえるように、できるだけシンプルに作成してみました。もう少し機能を追加したり、サーバにデプロイしたい場合は、LangChainやDjangoなどを活用するといいでしょう。

さて、Qiitaでの初投稿、いかがだったでしょうか？気に入ってもらえると嬉しいです。Qiitaの投稿は始めたばかり（下書きは一杯あります^^;）でQiitaの雰囲気に慣れてないので、どんな感じで書く／コミュニケーションするのか模索中ですが、今後もChatGPTに限らず、XR/AIを始めいろいろなテーマで記事を書いていきたいと思っています。どうぞよろしくです！