OpenAIのChat API(Chat completions)を用いての会話をPythonで実装してみた記録

[Supership](https://supership.jp/)の名畑です。GW中に[島本和彦先生 画業40周年突破記念『炎の原画展』](https://edition-88.com/pages/honoo-no-gengaten)を見に行ったので、心が熱い。

## はじめに

ネットだけではなくテレビだ雑誌だといたるところでChatGPTの話題だらけで今更感ありまくりですが、自環境で[OpenAI](https://openai.com/)の[Chat API(Chat completions)](https://platform.openai.com/docs/guides/chat)を試した際の諸々を書き残しておきます。
**ライブラリのインストール**、**アカウント作成**から始まって**Chat APIとの会話の実装**までの記録です。目新しいことはなにもないですが、少なくとも未来の自分の役には立ちそう。

大体の内容は[公式のAPI Reference](https://platform.openai.com/docs/api-reference/chat)と[Guide](https://platform.openai.com/docs/guides/chat/chat-completions-beta)のままです。

## 自環境

macOS(Monterey)です。

Pythonはすでにインストール済みでした。3.10.7です。

```shell-session
$ python --version
Python 3.10.7
```

OpenAIのライブラリ(Pythonバインディング)をインストールしておきます。

```shell-session
$ pip install openai
```

今のバージョンは0.27.6でした。

```shell-session
$ openai --version
openai 0.27.6
```

## OpenAIのアカウント作成

[OpenAI platform](https://platform.openai.com/)でアカウント作成をします。
メールアドレス(電話での認証あり)、Google、Microsoft Accountのいずれかが使えます。

無料クレジットの範囲であれば支払い設定は不要です。

## API key発行

サインイン後の[API keys](https://platform.openai.com/account/api-keys)のページで行えます。**Create new secret key**を押して、名前(optional)をつけるだけです。
API keyは作成時しか画面に表示されませんので気をつけてください。
他人とのシェアはしないこと、リークを検知した場合は自動でrotationされること等が注意書きされています。

取得したAPI keyはハードコーディングを避けるため環境変数にでも設定します。
公式に倣い**OPENAI\_API\_KEY**という環境変数名としました。

```bash
export OPENAI_API_KEY=ここに取得したAPI keyを書く
```

organization毎の使用量も取得できるのですが、今回は割愛します(Organization IDは[Organization settings](https://platform.openai.com/account/org-settings)のページでわかります)。

APIの使用は従量課金なので要注意です（現時点では無料クレジットあり）。

価格は[Pricing](https://openai.com/pricing)のページでわかります。使用量はログイン後に[Usage](https://platform.openai.com/account/usage)のページでわかります。
どれだけのトークンを処理したかで金額が決まります。英語であれば目安としては1000トークンは大体750単語に相当するそうです(言語によって異なります)。
~~今回用いる**gpt-3.5-turbo**だと1000トークンで**0.002米ドル**です。1ドル135円換算だと**0.27円**ですね。~~（**価格改定がありました**）

## PythonでのAPI keyのセット


```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
# openai.organization = "organizationを紐づけるなら記載"
```

## 提供されているAPI

- Models
- Completions
- Chat
- Edits
- Images
- Embeddings
- Audio
- Files
- Fine-tunes
- Moderations
- Engines ※deprecated

今回はChat APIを使ってみます。


## Chat APIとは

> Given a list of messages describing a conversation, the model will return a response.

会話を示すメッセージ配列をモデルに渡し、応答を受け取ります。

## Chat APIのリクエストパラメータ

APIにセットできるリクエストパラメータは下記です。

- **model**
- **messages**
	- **role**
	- **content**
	- name
- temperature
- top_p
- n
- stream
- stop
- max_tokens
- presence_penalty
- frequency_penalty
- logit_bias
- user

このうち**required**なのは、太字にした**model**と**messages**(の配下の**role**と**content**)のみです。

今回は**required**の項目のみを使って呼び出しを行います。

## model

使えるmodelは[Models](https://platform.openai.com/docs/models)に記載されています。

Chat APIで使えるmodelは下記です。

- gpt-4
- gpt-4-0314
- gpt-4-32k
- gpt-4-32k-0314
- gpt-3.5-turbo
- gpt-3.5-turbo-0301

modelによって扱える最大トークン数やいつまでの学習データを用いたかも異なります。

今回は**gpt-3.5-turbo**を用います。

> GPT-3.5 models can understand and generate natural language or code. Our most capable and cost effective model in the GPT-3.5 family is gpt-3.5-turbo which has been optimized for chat but works well for traditional completions tasks as well.

**MAX TOKENS**は**4,096 tokens**で、**TRAINING DATA**は**Up to Sep 2021**です。

余談ですが**GPT-4**を使うためには[GPT-4 API waitlist](https://openai.com/waitlist/gpt-4-api)への登録を行い、招待される必要があります。

## messages

**role**と**content**をセットにして**messages**という配列に順番に格納します。

公式例は下記です。

```Python
messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who won the world series in 2020?"},
        {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
        {"role": "user", "content": "Where was it played?"}
    ]
```

roleには**system、user、assistant**の三種類があります(詳しくは[Introduction](https://platform.openai.com/docs/guides/chat/introduction)をご覧ください)。

**user**は自分、**assistant**は相手(API)です。
**system**はassistantの行動を設定します。

> The system message helps set the behavior of the assistant. In the example above, the assistant was instructed with "You are a helpful assistant."

つまりは

```Python
{"role": "system", "content": "You are a helpful assistant."}
```

この記載であれば「**あなたは有能なアシスタントです**」という設定をしたことになります。

## レスポンスのフォーマット

公式例は下記です。

```Python
{
 'id': 'chatcmpl-6p9XYPYSTTRi0xEviKjjilqrWU2Ve',
 'object': 'chat.completion',
 'created': 1677649420,
 'model': 'gpt-3.5-turbo',
 'usage': {'prompt_tokens': 56, 'completion_tokens': 31, 'total_tokens': 87},
 'choices': [
   {
    'message': {
      'role': 'assistant',
      'content': 'The 2020 World Series was played in Arlington, Texas at the Globe Life Field, which was the new home stadium for the Texas Rangers.'},
    'finish_reason': 'stop',
    'index': 0
   }
  ]
}
```

choicesという配列配下にmessageがあり、そのcontentの中に応答の文章が格納されています。

## 質問してみる

質問を投げて応答を表示してみます。

ここからはわかりやすいように日本語のメッセージを投げてみます(品質的にもトークン数的にも本来は英語が良いのでしょうが)。

質問内容は「**私がこれから英語を身に付けたいのならどの国で学習するのが良いと思いますか？**」です。

まずはコードです。

```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")

completion = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": "非常に頭が良さそうな秘書としての回答をしてください"},
    {"role": "user", "content": "私がこれから英語を身に付けたいのならどの国で学習するのが良いと思いますか？"}
  ]
)
print(completion["choices"][0]["message"]["content"])
```

実行すると応答は下記でした。

> 英語を身に付けるためには、英語の母国語話者と接する機会が最も効果的です。そのため、英語を話す国々に滞在し、その国の文化に触れることで、英語のスキルを向上させることができます。英語を話す国は多数ありますが、特にアメリカ、イギリス、カナダ、オーストラリア、ニュージーランドなどが英語学習に最適です。それぞれの国には独自のアクセントや文化がありますので、自分に合ったものを選ぶことが大切です。また、英語教育に力を入れている大学や語学学校がある都市を選ぶこともお勧めします。

systemを変えてみます。

```Python
{"role": "system", "content": "箇条書きで回答してください"},
```

その際の応答は下記です。

> 以下はいくつかのオプションです：
> 
> 1. アメリカ合衆国 - アメリカ英語を学ぶには最適な場所であり、世界中の学生が選ぶ人気の留学先の一つです。
> 
> 2. カナダ - 英語が第一言語として話されるため、アメリカ英語とは異なるアクセントやスラングが学べます。
> 
> 3. イギリス - イギリス英語を最も正確に学ぶことができ、フィールドで使用されるビジネス英語の習得にも役立ちます。
> 
> 4. オーストラリア - オーストラリア英語を学び、ネイティブスピーカーと接触することで、より自然な英語力を身に付けることができます。
> 
> 以上の4つの国は、英語を身に付けるのに最適な場所であり、それぞれ異なるアクセントや文化を経験できます。あなたの学習目的や好みに応じて、最適な場所を選んで下さい。


## 会話をしてみる

最後に**会話**を実装してみます。つまりは「**ユーザーの言葉**」と「**APIからの応答**」を相互に行なっていくということです。

- **user**の入力
- **assistant**の応答

この二つを**messages**に随時追加して都度APIを呼び出せば会話になります。会話が続くに連れて必要なトークン数が増えていくことにご注意ください。

コードは下記です。

```Python
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

messages = [{"role": "system", "content": "くだけた言葉遣いで短めに話してください"}
            ]

while True:
    # 入力 / messagesへの追加
    your_message = input("あなたの番です: ")
    if your_message == "quit":  # quitと入力されたら終了
        exit()
    messages.append({"role": "user", "content": your_message})

    # API呼び出し
    try:
        completion = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=messages
        )
    except (openai.error.RateLimitError, openai.error.InvalidRequestError) as e:
        print(e.user_message)
        exit()

    # 応答の取得 / 表示 / messagesへの追加
    res_message = completion["choices"][0]["message"]["content"]
    print("OpenAIの応答です： " + res_message)
    messages.append({"role": "assistant", "content": res_message})

    # token数の取得と表示
    tokens = completion["usage"]["total_tokens"]
    print("今回のToken数： " + str(tokens))
```

**InvalidRequestError**(MAX TOKENSを超えた時など）と、支払い設定をしていないと発生しやすい**RateLimitError**(呼び出し上限、1分あたり3回)を拾っています。本当は前者はmessagesの内容を減らしてのリトライ、後者は時間を置いてのリトライをしてあげた方が親切ですね。

やりとりは下記のようになりました。
「あなたの番です: 」の後ろが私の入力した内容です。ドラゴンボールについて聞いて、次は応答をさらに短くしてもらっています。

> あなたの番です: ドラゴンボールって何？
> OpenAIの応答です： ドラゴンボールは、日本の漫画家・鳥山明さんによって創作された、人気のある漫画・アニメのシリーズだよ！主人公・孫悟空が、悪を倒して世界を守る冒険が描かれているんだ。各キャラには特徴的な技や個性があり、バトルシーンは熱い！アニメや映画もあるから、見たことある人も多いかもね！
> 今回のToken数： 197
> あなたの番です: もっと短い文章でお願い
> OpenAIの応答です： ドラゴンボールは、鳥山明さんが作った人気の漫画・アニメだよ！主人公の孫悟空が悪と戦って世界を守る話だよ。
> 今回のToken数： 282
> あなたの番です: quit

## 最後に

OpenAI関係の情報はインターネット上にいくらでも溢れてはいるのですが、今後も継続的に自分の手で試して残していこうとは思います。触ってみないと肌感が掴めないため。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。