ChatGPT APIを使ってチャットボットを作る

:::note info
Udemyでの[ChatGPT APIを使ってチャットボットを作ろう！【GPT-3.5/GPT-4】 -LLMを使うアプリの構築と公開-](https://www.udemy.com/course/chatgpt-api-bot/)というコースを勉強したメモです。分かりやすかったので是非チェックしてください。
:::

最近、ChatGPTを組み合わせてお客様に提案する機会が増えてきています。しかし、自分がChatGPTのことがまだ理解していないことが多くて、まともな提案ができないため、ChatGPTのAPIを利用してチャットボットを作ることで、ChatGPT APIの基本概要を理解したいと思います。
最後にパブリッシュしたものは、[My AI Assistant](https://utanesuke0612-openai-api-bot-udemy-app-kh50kv.streamlit.app/)へアクセスして、チェックしてください。

# 事前準備

Google、Streamlit、及びOpenAIのアカウントを作成する必要があります。
- **開発環境：** Python、Google Colaboratory、[ngrok](https://ngrok.com/)、[GitHub](https://github.co.jp/)
- **Webアプリのデプロイ環境：** [Streamlit](https://streamlit.io/) 
- **ChatGPT API：** [ChatGPT Billing overview](https://platform.openai.com/account/billing/overview)よりクレジットカードを登録し、ChatGPT APIを利用します。

# ChatGPTのAPIを触ってみる

必要なアカウントが整えたら、Google Colaboratory(PythonのNotebook)を利用してChatGPTのAPIを触ってみます。

```python
# ChatGPT APIを使用するために必要なライブラリ、openaiをインストールします。
!pip install openai

# インストールの完了後、openaiをインポートしておきます。
import openai

# ChatGPT APIを使用するために必要な「API key」を設定します。
openai.api_key = "自分のOpenAIのAPIキー"
```

次は、ChatGPTへリクエストを投げて、レスポンスを取得します。

```python
# ChatGPT APIからのレスポンスを取得する
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "あなたは優秀なアシスタントAIです。"},
        {"role": "user", "content":"あなたはどんなことができますか？"}
        ]
)

# レスポンスの表示
print(response['choices'][0]['message']['content'])
```
上記printしてきた結果は以下の通りです。

```
私は自然言語処理（NLP）技術を利用して、以下のようなことができます。

1. 質問や指示に応じて情報を提供する
2. 予定やリマインダーの作成、管理
3. 音楽やニュースの再生
4. 会話を通じて情報の収集や分析
5. 翻訳や文章生成
6. メールやチャットの自動返信や応答
7. タスクの自動化

など、様々なことを行うことができます。
```

## Create chat completionのリクエスト文

詳細に関しては、以下のオフィシャルサイトから参照できます。

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

Pythonでは、以下のようにリクエストを投げました。
```python
# ChatGPT APIからのレスポンスを取得する
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "あなたは優秀なアシスタントAIです。"},
        {"role": "user", "content":"あなたはどんなことができますか？"}
        ]
)
```

## リクエストBodyの中身（一部抜粋）

| パラメータ名 | 内容 |
|:-:|:-|
| model  | 利用するモデル、リクエスト先(endpoint)が違うと、利用できるモデルも違います。例えば、`/v1/chat/completions`であれば、`gpt-4, gpt-4-0314, gpt-4-32k`などが選択可能、詳細は[Model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility)を参照  |
|messages　|これがメッセージのリストです、一つのメッセージでは、role/content/nameの設定項目があります。　**こちらの設定が超重要です**<sup>補足情報</sup>、[Example Python code](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb)|
|temperature　|`top_p`と、どちか一つを利用。デフォルトが1、temperatureを上げるほど、出力する文章の自由度が増しますが、その分、文章表現がおかしくなって、最終的には文章が崩壊してしまうということが分かりました。|
|top_p　|`temperature`と、どちか一つを利用。top_pを0.1にすると、予測する次の単語は確率の高い上位10%の候補から選択されます。小さくすればするほど候補が確率の高いものに絞られるため、確定的になっていきます。|
|n　|返されるリスポンスの`choices`でのmessage数、即ち、生成された回答数|
|stream　|結果が一気に返されるか、それともサーバサイドが作成しつつ結果も少しづつ返されるか。デフォルトが一気に。[Example Python code](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb)|
|stop　|どんな単語が出現したら文章生成を打ち切るかを指定します。例えば改行`stop=["\n"]`と指定すると改行された時点で文章生成が終了します。リストで渡すことで複数の単語を指定することができます。|
|presence_penalty　|-2.0から2.0の値を取り、既に出てきた単語をもう一度使うかどうかを指定します。**-2.0に近いと同じ単語を繰り返し使うようになり、2.0に近いと同じ単語は繰り返し使いづらくなります**|
|frequency_penalty　|こちらもpresence_penaltyと同じようなパラメータで-2.0から2.0の値を取り、出てきた回数が多いほどペナルティを大きくするものです。**presence_penaltyと同様に-2.0に近いと同じ単語を繰り返し使うようになり、2.0に近いと同じ単語は繰り返し使わなくなります。**　|
|user　|お客様のエンドユーザーを表す固有の識別子であり、OpenAIが監視および悪用の検出を支援する役割を果たします。例：`user="user123456"`|

:::note info
**補足情報：** roleを設定することで、得られる結果が違ってきます。
**ケース1:**
```python
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "user", "content":"日本の英語教育の欠点は何ですか"}
        ],
)
```

```
私はAIの言語モデルであり、日本に住んだことがないため、この質問に対する正確な答えを提供することはできません。しかし、一般的に言えることは、日本の英語教育に関しては、次のような問題が指摘されています。
1.教育の目的が読み書きに重点が置かれすぎているため、リスニングスキルやスピーキングスキルが弱い場合がある。
2.日本人の英語教師の中には、英語の発音やアクセントが標準的でない場合がある。
3.普段のカリキュラムの中で、実際の英会話に必要な文法や表現を教えていない場合がある。
4.英語の国際試験に重点が置かれすぎているため、実際の英語コミュニケーション力が評価されていない場合がある。
以上が、日本の英語教育に関する一般的な欠点だと考えられますが、実際の状況は学校や講師によって異なるため、一律に評価することはできません。
```

**ケース2:**
```python
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "あなたは英語の先生です。"},
        {"role": "user", "content":"日本の英語教育の欠点は何ですか"}
        ],
)
```

```
日本の英語教育には、いくつかの欠点があります。その中でも主なものとしては、以下のようなものが挙げられます。
1. 発音・リスニングの重視不足：日本の英語教育では、読み書きに重点がおかれがちで、発音やリスニング力の向上に取り組む機会が少ない傾向があります。そのため、実際に英語を話したり、聞いたりする場面でコミュニケーションがスムーズにできないことがあります。
2. 文法・単語の暗記中心の授業：日本の英語教育では、文法や単語を暗記することが中心で、コミュニケーション力を向上させるための授業が少ない傾向があります。そのため、英語が話せても自然に流暢な表現ができない、あるいは相手の文脈や状況に合わせた適切な表現ができないことがあります。
3. 英語が教科化されていること：日本の学校では、英語が一つの教科として扱われがちで、他の科目と同じような教育を受けます。しかし、英語はコミュニケーションツールであるため、実際に使ってこそ本来の意味を持ちます。そのため、英語を単なる科目として捉えることで、コミュニケーション力を向上させることができなくなっていることがあります。
```

:::

## リスポンスBodyの中身：

実際のリスポンスの中身をprintすると：

```python
print(response)
```

```json
{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "content": "私は、情報を検索したり、質問に回答した...",
        "role": "assistant"
      }
    }
  ],
  "created": 1685870582,
  "id": "chatcmpl-7NeEwyYbodXUrW3pzBKJaseXeDwVB",
  "model": "gpt-3.5-turbo-0301",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 150,
    "prompt_tokens": 46,
    "total_tokens": 196
  }
}
```

## UiPathでのアクティビティ

UiPathでの既に用意していたアクティビティの入力パラメータ及び出力パラメーターも上記のと同じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/e0628a1f-b208-836e-00c9-94cbe8f64488.png)

アクティビティを通じて、実際に設定して、どんなリスポンスかをチェックしてみてください。

上記のコードの実行後、OpenAIのサイトでAPIの使用量を確認してみましょう。

https://platform.openai.com/account/usage

# チャットボットの構築

こちらで、ChatGPTのAPIとStreamlitのフレームワークを利用して、ウェブアプリを構築します。

## ngrokの設定

**ngrokとは：**

簡単にいうと、ローカルPC上で稼働しているネットワーク（TCP）サービスを外部公開できるサービスです。例えば、ローカルPCのWebサーバを外部公開することができます。
本コースでは、Google Colaboratory上で構築したチャットボットの動作確認に使用します。

**ngrokにユーザ登録：**
[ngrok公式サイト](https://dashboard.ngrok.com/user/signup)からユーザ登録します。

**Your Authtokenの取得：**
ツールをダウンロードして使用できるが、こちらはYour Authtokenを利用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/37267426-fa1a-d2b2-bc98-9a96f40932b6.png)

## Streamlitの概要

[Streamlit](https://www.streamlit.io/)とは、PythonでWebアプリケーションを作成するためのフレームワークです。 データサイエンティストやAIエンジニア向けに開発されており、バックエンド開発の知識がなくてもPythonのコードを数行書くだけで、気軽にデモ用のアプリを作成することができるのが特徴です。
Streamlit Cloudにより、アプリを公開することが可能です。([他人の作品gallery](https://streamlit.io/gallery)、例：[KnowledgeGPT](https://knowledgegpt.streamlit.app/)、[AI Talks](https://ai-talks.streamlit.app/))

**ライブラリのインストール:**
Streamlit、およびアプリの動作の確認に使用する「ngrok」をインストールします。
```python  
!pip install streamlit==1.20.0 --quiet
!pip install pyngrok==4.1.1 --quiet

# Streamlit、およびngrokをインポートします。
import streamlit as st
from pyngrok import ngrok
```

**ページのUI：**
%%writefileのマジックコマンドを使って、様々なUIを配置するコードを「app.py」に書き込みます。

```python
%%writefile app.py
# 以下を「app.py」に書き込み
import streamlit as st
import numpy as np
import pandas as pd

# ---------- スライダー ----------
st.title("st.slider()")
x = st.slider("xの値")
st.write(str(x) + "の2乗は" + str(x**2))

# ---------- ボタン ----------
st.title("st.button()")
if st.button("Morning?"):
    st.write("Good morinig!")
else:
    st.write("Helllo!")

# ---------- テキスト入力 ----------
st.title("st.text_input()")
st.text_input("お住まいの国", key="country")
st.session_state.country  # keyでアクセス

# ---------- チェックボックス ----------
st.title("st.checkbox()")
is_agree = st.checkbox("同意しますか？")
if is_agree:
    st.write("了解です！")
else:
    st.write("残念です！")

# ---------- セレクトボックス ----------
st.title("st.selectbox()")
df_select = pd.DataFrame({
    "col1": [11, 12, 13, 14],
    "col2": [111, 112, 113, 114]
    })
selected = st.selectbox(
    "どの番号を選びますか？",
     df_select["col2"])
st.write("あなたは" + str(selected) + "番を選びました！")

# ---------- サイドバー ----------
st.sidebar.title("st.sidebar")

y = st.sidebar.slider("yの値")
st.sidebar.write(str(y) + "の2倍は" + str(y*2))

df_side = pd.DataFrame({
    "animal": ["犬", "猫", "兎", "象", "蛙"],
    "color": ["赤", "青", "黄", "白", "黒"]
    })
selected_side = st.sidebar.selectbox(
    "どの動物を選びますか？",
    df_side["animal"]
    )
st.sidebar.write("あなたは" + str(selected_side) + "を選びました！")
```

**Authtokenの設定:**
YourAuthtokenの箇所を、自分のAuthtokenに置き換えます。(ダブルクォーテーションなし)
Authtokenは、ngrokのサイトに登録すれば取得することができます。
https://ngrok.com/

```python
!ngrok authtoken YourAuthtoken
```

**アプリの起動:**

```python
# streamlitのrunコマンドでアプリを起動します。
!streamlit run app.py &>/dev/null&  # 「&>/dev/null&」により、出力を非表示にしてバックグランドジョブとして実行
```

ngrokのプロセスを終了した上で、新たにポートを指定して接続します。
接続の結果、urlを取得できます。
ngrokの無料プランでは同時に1つのプロセスしか動かせないので、エラーが発生した場合は「ランタイム」→「セッションの管理」で不要なGoogle Colabのセッションを修了しましょう。


```python
ngrok.kill()  # プロセスの修了
url = ngrok.connect(port="8501")  # 接続
```

**動作確認：**

```Python
# URLのhttpの部分をhttpsに変換する関数を設定します。
def convert_http_to_https(url):
    if url.startswith("http://"):
        url = url.replace("http://", "https://", 1)
    return url

# 変換したurlを表示し、リンク先でアプリが動作することを確認します。
print(convert_http_to_https(url))
```

ウェブアプリのURL（`https://8f77-35-245-19-38.ngrok-free.app`）が表示されます。
アクセスして、画面を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/7834231d-fcea-b420-2ca3-2f44d192e7cd.png)

## チャットボットの構築

StreamlitとChatGPT APIを使い、チャットボットを構築します。

```python
# ライブラリのインストール
!pip install streamlit==1.20.0 --quiet
!pip install pyngrok==4.1.1 --quiet
!pip install openai

# streamlit、ngrok、およびopenaiをインポートしておきます。
import streamlit as st
from pyngrok import ngrok
import openai
```

**チャットボットのコード:**
%%writefileのマジックコマンドを使って、チャットボットのコードを「app.py」に書き込みます。
Streamlitでは、値を保持するために「st.session_state」を使います。
https://docs.streamlit.io/library/api-reference/session-state

```python
%%writefile app.py
# 以下を「app.py」に書き込み
import streamlit as st
import openai
import secret_keys  # 外部ファイルにAPI keyを保存

openai.api_key = secret_keys.openai_api_key

# st.session_stateを使いメッセージのやりとりを保存
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "あなたは優秀なアシスタントAIです。"}
        ]

# チャットボットとやりとりする関数
def communicate():
    messages = st.session_state["messages"]

    user_message = {"role": "user", "content": st.session_state["user_input"]}
    messages.append(user_message)

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages
    )  

    bot_message = response["choices"][0]["message"]
    messages.append(bot_message)

    st.session_state["user_input"] = ""  # 入力欄を消去


# ユーザーインターフェイスの構築
st.title("My AI Assistant")
st.write("ChatGPT APIを使ったチャットボットです。")

user_input = st.text_input("メッセージを入力してください。", key="user_input", on_change=communicate)

if st.session_state["messages"]:
    messages = st.session_state["messages"]

    for message in reversed(messages[0:]):  # 直近のメッセージを上に
        speaker = "🙂"
        if message["role"]=="assistant":
            speaker="🤖"

        st.write(speaker + ": " + message["content"])
```

**OpenAIのAPI keyを設定:**

ChatGPT APIを使用するために必要な「API key」を設定します。
%%writefileのマジックコマンドを使って、API keyを設定するコードを「secret_keys.py」に書き込みます。
以下のコードの、
openai_api_key = "Your API key"
における
Your API keyの箇所を、自分のAPI keyに置き換えます。
ChatGPTのAPI keyは、OpenAIのサイトで取得できます。
https://platform.openai.com/account/api-keys
```python
%%writefile secret_keys.py

openai_api_key = "自分のOpenAPIキー"
```

**ngrokのAuthtokenを設定：** 

ngrokで接続するために必要な「Authtoken」を設定します。（ダブルクォーテーションなし）
以下のコードの、
!ngrok authtoken YourAuthtoken
における
YourAuthtokenの箇所を、自分のAuthtokenに置き換えます。
Authtokenは、ngrokのサイトに登録すれば取得することができます。
https://ngrok.com/

```python
!ngrok authtoken YourAuthtoken
```

**アプリの起動:**

```python
!streamlit run app.py &>/dev/null&  # 「&>/dev/null&」により、出力を非表示にしてバックグランドジョブとして実行

ngrok.kill()  # プロセスの修了
url = ngrok.connect(port="8501")  # 接続
```

**動作の確認:**

```python
def convert_http_to_https(url):
    if url.startswith("http://"):
        url = url.replace("http://", "https://", 1)
    return url
print(convert_http_to_https(url))
```

出力されたURL（`https://62e5-34-121-169-48.ngrok-free.app`）をアクセスして、確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/4e58f973-eafe-0230-d86e-8312d9e7990e.png)

# チャットボットの公開

Streamlit Community Cloudを利用して、チャットボットのウェブアプリを公開できます。

## チャットボットの構築

**ライブラリのインストール:**

```python
!pip install streamlit==1.20.0 --quiet
!pip install openai

# インストールの完了後、streamlitおよびopenaiをインポートしておきます。
import streamlit as st
import openai

```

**チャットボットのコード:**

%%writefileのマジックコマンドを使って、チャットボットのコードを「app.py」に書き込みます。
今回、OpneAIのAPI keyは、Streamlit Community Cloudの「Secrets」に保存します。

```python
%%writefile app.py

import streamlit as st
import openai

# Streamlit Community Cloudの「Secrets」からOpenAI API keyを取得
openai.api_key = st.secrets.OpenAIAPI.openai_api_key

# st.session_stateを使いメッセージのやりとりを保存
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "あなたは優秀なアシスタントAIです。"}
        ]

# チャットボットとやりとりする関数
def communicate():
    messages = st.session_state["messages"]

    user_message = {"role": "user", "content": st.session_state["user_input"]}
    messages.append(user_message)

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages
    )  

    bot_message = response["choices"][0]["message"]
    messages.append(bot_message)

    st.session_state["user_input"] = ""  # 入力欄を消去


# ユーザーインターフェイスの構築
st.title("My AI Assistant")
st.write("ChatGPT APIを使ったチャットボットです。")

user_input = st.text_input("メッセージを入力してください。", key="user_input", on_change=communicate)

if st.session_state["messages"]:
    messages = st.session_state["messages"]

    for message in reversed(messages[1:]):  # 直近のメッセージを上に
        speaker = "🙂"
        if message["role"]=="assistant":
            speaker="🤖"

        st.write(speaker + ": " + message["content"])
```

**requirements.txtの作成:**

Streamlit Community Cloudのサーバー上でアプリを動かすために、「requirements.txt」を作成する必要があります。
このファイルでは、必要なライブラリを指定します。
以下のセルでは、%%writefileのマジックコマンドを使って、必要なライブラリを「requirements.txt」に書き込みます。

```python
%%writefile requirements.txt
streamlit==1.20.0
openai
```

以下の作成されたファイルをダウンロードして、GitHubのレポジトリにアップしましょう。
- app.py
- requirements.txt
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/10362bfd-f729-57f7-c184-8d7cbf995425.png" width=240>

## アプリのデプロイ

**Githubへコミット：**
まず、GitHubで新しいリポジトリを作成します。次は先程ダウンロードしたファイルをアップロードして、コミットします。

**streamlitで公開：**

- [https://share.streamlit.io/](https://share.streamlit.io/)で[New App]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/2442bc0d-74e0-0d9d-d184-13d94e54d77f.png)

- ついはDeployします。Open AIのキーがまだ設定していないので、エラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/cc39b741-c42b-eedd-70a8-c229d8e427b7.png)
- 次は、OpenAIのキーをstreamlitに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/f969294b-5696-bbbb-8d00-97b009dd76d0.png)


- キーの設定をしたら、再起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/c71377bc-8171-6950-decf-7efbf8286ca0.png)

- 暫くしたら、[URL](https://utanesuke0612-openai-api-bot-udemy-app-kh50kv.streamlit.app/)をアクセスして、パブリッシュができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/bf5887cd-68d8-26a3-f07a-c5e0ff928256.png)

## streamlitでの情報保存

こちらは`openai.api_key = st.secrets.OpenAIAPI.openai_api_key`を使って、以下のような画面で設定したのを取得しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/f969294b-5696-bbbb-8d00-97b009dd76d0.png)

同じ方法で、以下のような変更をすれば、見せたくない内容の保存ができます。

**変更前：**

```python
# st.session_stateを使いメッセージのやりとりを保存
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "あなたは優秀なアシスタントAIです。"}
        ]
```

**変更後：**

```python
# st.session_stateを使いメッセージのやりとりを保存
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": st.secrets.AppSettings.chatbot_setting}
        ]
```

及び、以下のような記述を「Secrets」に追記します。

```
[AppSettings]
chatbot_setting = "あなたは優秀な英語教師です。何を聞かれても英語で答えてください。"
```

# 様々なチャットボットの構築

ChatGPTへ与える情報を工夫して、専門分野のチャットボットの構築ができます。

例えば、料理専門家とかですと：

```Python

system_prompt = """
あなたは優秀な料理研究家です。
限られた食材や時間で、様々な料理のレシピを提案することができます。
あなたの役割はレシピを考えることなので、例えば以下のような料理以外ことを聞かれても、絶対に答えないでください。

* 旅行
* 芸能人
* 映画
* 科学
* 歴史
"""

# st.session_stateを使いメッセージのやりとりを保存
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": system_prompt}
        ]

```


# 参考情報

https://note.com/it_navi/n/n0376c2f4fb21

https://data-analytics.fun/2021/12/01/gpt-3-api/