ruby-openaiを使ってChatGPT APIを叩いてみよう

ChatGPTのWeb APIが3月2日（日本時間）から利用できるようになったということで、Rubyで試してみました。

https://openai.com/blog/introducing-chatgpt-and-whisper-apis

https://www.itmedia.co.jp/news/articles/2303/02/news076.html

今までも「ChatGPTのAPI」を使ったという記事を見かけたことがあったので何が違うのか初めは困惑しましたが、以下のような違いがあるようです。[^1]

- 今回OpenAIのAPIで使えるようになったのは `gpt-3.5-turbo` というモデルで、これはChatGPTで使われている
- 今まで「ChatGPTのAPI」と言われることがあったのは `text-davinci-003` というモデルで、ChatGPTで使われているモデルとは違うものだった

# ざっくり手順

1. OpenAI APIのAPI Keyを発行する
1. `ruby-openai` を入れる
1. `ruby-openai` のサンプルコードを実行してみる
1. 前提条件や会話履歴も含めて渡してみる

# 1. OpenAI APIのAPI Keyを発行する

まずは [Account API Keys - OpenAI API](https://platform.openai.com/account/api-keys) でAPI Keyを発行します。
新規ユーザーには$18のクレジットが付与されて、その範囲内で利用できます。料金や各種制限については[記事の終盤](https://qiita.com/yocajii/items/cbce3cb92c601984314c#料金各種制限)をご確認ください。

# 2. `ruby-openai` を入れる

RubyでOpenAI APIを簡単に利用するために、`ruby-openai` を使います。
READMEのInstallationに沿って、Gemfileに追加して `bundle install` しておきます。

https://github.com/alexrudall/ruby-openai


# 3. `ruby-openai` のサンプルコードを実行してみる

## API Keyをセットする

READMEのQuickstartに沿って、`access_token` として 1で作成したAPI Keyをセットします。

```ruby
require "openai"

client = OpenAI::Client.new(access_token: "access_token_goes_here")
```

## ChatGPTを利用するサンプルコードをコピペする

READMEのUsege > ChatGPTのサンプルコードをそのまま貼り付けます。
https://github.com/alexrudall/ruby-openai#chatgpt

## 実行する

全体を組み立てると以下のようになっているかと思います。

```ruby:hello.rb
require "openai"

client = OpenAI::Client.new(access_token: "【あなたのAPI Key】")

response = client.chat(
    parameters: {
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Hello!" }],
    })
puts response.dig("choices", 0, "message", "content")
```

このファイルを実行して、返事が返ってきたら成功です！
```
❯ ruby hello.rb

Hello there! How may I assist you?
```

# 4. 前提条件や会話履歴も含めて渡してみる

## 日本語を使ってみる

サンプルコードのうち、次の部分がユーザーからの質問にあたります。
```ruby
{ role: "user", content: "Hello!" }
```

ChatGPTのサービスと同じく、日本語で質問したら日本語で返してくれます。
```ruby
{ role: "user", content: "こんにちは！" }
```

```
❯ ruby hello.rb
こんにちは！私はAIアシスタントのオープンAIです。何かお探しですか？
```

## 前提条件を与える

`role` には `user` のほか `system` と `assistant` があり、`system` を指定して前提条件を与えることができます。

```ruby
    messages: [
      { role: "system", content: "私はプログラミングの初学者です。あなたにはプログラミング講師の役をお願いします。私はプログラミングで意図した挙動が得られず困っています。プログラミング初学者に分かりやすく、困りごとを解消するために何をしたら良いか教えてください。" },
      { role: "user", content: "助けてください" },
    ],
```

試しにプログラミング講師の役をお願いした上で雑に「助けてください」とだけ質問すると、文脈に沿った返事をしてくれました。
```
❯ ruby mentor.rb
はじめまして。プログラミングの初学者の方に役立つ情報をお届けすることができます。どのような問題に直面しているのでしょうか？詳しく教えていただけますか？

例えば、どのようなプログラムを作ろうとしていて、どこで問題が発生しているのか、そういった情報があれば、具体的なアドバイスをすることができます。また、プログラミングで困っていることを、具体的に列挙していただければ、それに合わせてアドバスすることもできます。
```
`role: "system"` の行をコメントアウトすると、前提条件がないときの返事を確認できます。

```ruby
    messages: [
      # { role: "system", content: "私はプログラミングの初学者です。あなたにはプログラミング講師の役をお願いします。私はプログラミングで意図した挙動が得られず困っています。プログラミング初学者に分かりやすく、困りごとを解消するために何をしたら良いか教えてください。" },
      { role: "user", content: "助けてください" },
    ],
```

```
❯ ruby mentor.rb 

何がお困りですか？どのように私が助けることができますか？詳しく教えてください。
```

## 会話履歴を渡す

今回利用できるようになった `gpt-3.5-turbo` の特色として、会話の履歴を踏まえた上での返事を得られるという点があるとのことです。

渡し方はいたってシンプルで、`messages: []` に過去ターンの会話＋今ターンの質問を詰め込んであげます。
この時、ユーザーの過去の質問は `role: "user"` で、ChatGPTからの返事は `role: "assistant"` として区別します。


```ruby
    messages: [
      { role: "system", content: "私はプログラミングの初学者です。あなたにはプログラミング講師の役をお願いします。私はプログラミングで意図した挙動が得られず困っています。プログラミング初学者に分かりやすく、困りごとを解消するために何をしたら良いか教えてください。" },
      { role: "user", content: "助けてください" },
      { role: "assistant", content: "はじめまして。プログラミングの初学者の方に役立つ情報をお届けすることができます。どのような問題に直面しているのでしょうか？詳しく教えていただけますか？例えば、どのようなプログラムを作ろうとしていて、どこで問題が発生しているのか、そういった情報があれば、具体的なアドバイスをすることができます。また、プログラミングで困っていることを、具体的に列挙していただければ、それに合わせてアドバスすることもできます。" },
      { role: "user", content: "SSHの接続をしようとしていますが、つながりません" }
    ],
```

```
❯ ruby mentor.rb
SSHの接続に問題が生じた場合は、いくつか問題の原因が考えられます。以下の項目を確認してください。

1. 接続先のIPアドレス、ホスト名、ポート番号の指定が正しいか確認してください。不正確な情報がある場合は、接続ができません。
2. ファイアウォールによって接続がブロックされている可能性があります。SSHのポート（通常は22番）がブロックされていないことを確認してください。必要に応じて、ファイアウォールの設定を調整してください。
3. SSHサーバーソフトウェアが正しく動作していることを確認してください。SSHサーバのログファイルに問題がないかを調べることができます。
4. SSHクライアント（ラップトップ等）が正常であることを確認してください。SSHクライアントツールの設定を確認し、問題がないことを確認してください。

これらのアイデアのいずれかが原因である可能性があります。基本的な設定が正しくできていることを確認し、詳細なエラーメッセージがあれば、追加情報を提供してください。
```

# ●料金、各種制限

## `gpt-3.5-turbo` の利用料金

1,000トークンあたり$0.002だそうです。
トークンという単位に馴染みがないのと少数点以下の桁が多いために、私は当初あまりピンと来ませんでした。この記事で紹介した内容を試してみる分には問題にならないくらい低額と考えて良いかと思います。

https://openai.com/pricing#chat

### トークンって何？

テキスト処理を行う際の単位で、文字数でもワード数でもない概念とのことです。OpenAI用語というわけではなく、自然言語処理においては一般的な用語だそうですが、ここではOpenAIの料金を予想するのに必要なトークンについて考えます。

質問と回答の両方が英語の場合は、OpenAIのページにある記載によると1ワードあたりだいたい0.75トークンになるそうなので、ワード数よりもトークン数の方が少なくなるようです。

> A helpful rule of thumb is that one token generally corresponds to ~4 characters of text for common English text. This translates to roughly ¾ of a word (so 100 tokens ~= 75 words).

日本語の場合は、公式のツールとして用意されている [Tokenizer](https://platform.openai.com/tokenizer) [^2] を使って確認してみるのが分かりやすいかと思います。
1文字が3トークンになったり、2文字で1トークンになったりします。

入力のトークン数と出力のトークン数を合わせたものが料金計算の対象になります。
実際のやり取りで発生したトークン数はレスポンスで確認することができ、下記の例では `"total_tokens"=>19` となっています。

```shell
# { role: 'user', content: 'こんにちは！' } でリクエストした時のレスポンス
{"id"=>"chatcmpl-6qJ047vLZRjkjPxDS26GScxIyrQha",
 "object"=>"chat.completion",
 "created"=>1677924112,
 "model"=>"gpt-3.5-turbo-0301",
 "usage"=>{"prompt_tokens"=>9, "completion_tokens"=>10, "total_tokens"=>19},
 "choices"=>[{"message"=>{"role"=>"assistant", "content"=>"こんにちは！どうしましたか？"}, "finish_reason"=>"stop", "index"=>0}]}
```


## 新規ユーザーに付与される無料枠

新規のユーザーには無料枠として18ドルのクレジットが付与され、3か月は使えるらしい...のですが、helpの記事では5ドル・3か月となっている一方で管理画面では18ドル・1か月となっているのでこの辺りよく分かっていません。信頼できる資料をご存じの方がおいででしたら教えていただけると嬉しいです :pray: 

https://help.openai.com/en/articles/4936830-what-happens-after-i-use-my-free-tokens-or-the-3-months-is-up-in-the-free-trial

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160235/853fab62-d405-a3ea-9c3c-278ae43fa279.png)

## Free trial usersの制限

Rate Limitとして下記の制限が適用されるようです。[^3]
- 20リクエスト/分
- 40,000トークン/分

# おわりに

Web APIを叩くだけなら自然言語処理や機械学習をよく知らない自分みたいな人でも簡単にできました！
この記事が、ChatGPTのAPIが気になっている方の背中を押す助けになれば嬉しいです。

[^1]: https://platform.openai.com/docs/models
[^2]: よく見ると選択できるモデルが「GPT-3」と「Codex」だけなので、GPT3.5ベースであるgpt-3.5-turboの場合はまた変わってきそうです
[^3]: https://platform.openai.com/docs/guides/rate-limits/overview