Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

OpenAI APIをRubyアプリケーションに統合する(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

OpenAI APIの情報は移り変わりが早いのでご注意ください。最新の情報については以下などの公式情報をご覧ください。

参考: Guides | OpenAI Help Center
参考: GPT best practices - OpenAI API

なお、サンプルコードにあるOpenAI APIの出力結果の日本語訳には、OpenAI APIによる機械翻訳の出力結果を使っています。

OpenAI APIをRubyアプリケーションに統合する(翻訳)

🔗 ChatGPTについて

ChatGPTは、OpenAIが開発した人工知能(AI)チャットボットであり、人間らしい会話を行ったり、テキストの指示に基づいて画像を生成したりできます。これは自然言語処理における大きな飛躍と言えます。

🔗 OpenAI APIをRubyアプリケーションに統合する

OpenAIが提供するAPIをRubyアプリケーションに統合することで、ChatGPTのあらゆる機能を実装してユーザーを惹きつけられるようになります。本記事で用いるruby-openai gemは、ユースケースに応じてさまざまなOpenAIモデルを利用できます。

alexrudall/ruby-openai - GitHub

🔗 gemをインストールする

Gemfileにruby-openai gemを追加します。

# Gemfile
gem "ruby-openai"

続いてbundle installを実行してgemをインストールします。

🔗 アクセスキーを取得する

APIからレスポンスを取得するには、アクセスキーを生成する必要があります。API keysページを開いて、新しい秘密キーを作成します1

秘密キーをコピーして、シェルのOPENAI_ACCESS_TOKEN環境変数に設定します。

export OPENAI_ACCESS_TOKEN="xxxxxxxxxxxxxxx"

🔗 Ruby OpenAIを設定する

OpenAIアカウントが組織に紐付けられている場合は、OPENAI_ORGANIZATION_ID環境変数にも値を設定します。organization IDの値は、Settingsページにあります。

OpenAI.configure do |config|
  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
  config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # オプショナル
end

次に、クライアントを作成します。

client = OpenAI::Client.new

🔗 適切なモデルを選定する

モデルを扱う前に、「トークン(token)」という概念を理解しておく必要があります。

以下はOpenAIヘルプページからの抜粋です。

トークンは、語(word)の一部とみなせます。APIはプロンプトを処理する前に、入力をトークンに分割します。分割されたトークンは、必ずしも語の開始と終了に一致しているとは限りません。語の末尾にあるスペースや、語の一部がトークンとして扱われる可能性もあるのです。

  • 1トークン ~= 英文字で4文字
  • 1トークン ~= ¾語
  • 100トークン ~= 75語

What are tokens and how to count them? | OpenAI Help Centerより

つまり、1個のトークンはおよそ4文字(character)程度とみなせます。

OpenAI APIにはさまざまなモデルがあります。モデルにはバージョンがあり、ユースケースに応じて使い分けられます。

🔗 1) GPT-4

GPT-4モデルは複雑な問題の解決に優れていて、高い精度と従来モデルよりもずっと多くの能力を備えています。ほとんどの基本タスクでは、GPT-4とGPT-3.5に大きな違いは見られません。

gpt-4
このモデルは複雑なタスクや最適化されたチャットを実行可能で、最大8,192トークンをサポートし、2021年9月までのデータでトレーニングされています。
gpt-4-32k
このモデルはgpt-4モデルと同程度の能力を備えていますが、サポートするトークン数は最大8,192で、2021年9月までのデータでトレーニングされています。

🔗 2) GPT-3.5

GPT-3.5モデルは、自然言語やコードの解釈や生成を行なえます。gpt-3.5-turboはチャットに特化していますが、従来のタスクもこなせます。

gpt-3.5-turbo
このモデルはGPT-3.5のほとんどの能力を備えています。チャットに特化し、サポートするトークン数は最大4,096で、2021年9月までのデータでトレーニングされています。
text-davinci-003
(訳注: 2023/07/10時点ではレガシーとされています)このモデルは任意の言語タスクを、高品質かつより長い出力で、かつ一貫した指示で実行できます。サポートするトークン数は最大4,096で、2021年9月までのデータでトレーニングされています。
text-davinci-002
(訳注: 2023/07/10時点ではレガシーとされています)このモデルはtext-davinci-003に近い能力を備えていますが、ファインチューニングされた教師あり学習でトレーニングされています。サポートするトークン数は最大4,096で、2021年9月までのデータでトレーニングされています。
code-davinci-002
(訳注: 2023/07/10時点ではレガシーとされています)このモデルはコード補完に最適化されています。サポートするトークン数は最大8,001で、2021年1月までのデータでトレーニングされています。

🔗 3) GPT-3

GPT-3モデルは自然言語を生成・理解できます。以下のGPT-3モデルは、その後より強力なGPT-3.5生成モデルに取って代わられました。どのモデルも最大2,049個のトークンをサポートしており、トレーニングデータは2019年10月までのものが使われています。

davinci
このモデルは最も能力が高く、他のモデルよりも高品質なタスクを実行できます。
curie
このモデルは非常に能力が高く、davinciモデルよりも高速かつ低コストです。
babbage
このモデルは素朴なタスクでの能力が高く、極めて高速かつ低コストです。
ada
このモデルはきわめてシンプルなタスクを実行でき、GPT-3モデル中最速かつ最も低コストです。

🔗 4) DALL-E

DALL-Eモデルは、自然言語による記述から画像を生成・編集できます。

🔗 5) Whisper

Whisperモデルは、音声をテキストに変換できます。さまざまな言語の音声を認識したり、音声の翻訳や言語名の識別が可能です。

🔗 6) 埋め込み

埋め込み(Embedding)は、テキストの数値表現であり、2つのテキスト間の関連性を測定するために使われます。これらのモデルは、検索、クラスタリング、推薦、異常検知、および分類のタスクで有用です。

🔗 7) モデレーション

モデレーション(moderation)は、テキストが機密に触れる、または安全でない可能性があるかどうかを検出できる高度に調整されたモデルです。このモデルは、渡されたコンテンツがOpenAIの利用ポリシーに準拠しているかどうかをチェックします。

🔗 チャットの場合

本記事執筆時点では、gpt-4のアクセスに制限がかけられているため、gpt-3.5-turboモデルを使っています。

リクエストには、modelmessagesという2つの必須パラメータも渡さなければなりません。messagesパラメータの内部では、roleパラメータとcontentパラメータに値を渡す必要があります。

temperature(温度)パラメータは省略可能ですが、渡す値は0〜2の範囲でなければなりません。temperatureの値を高くすると結果が予測しにくくなってレスポンスがまちまちになり、逆に値を低くすると予測可能かつ保守的なレスポンスになります。

OpenAIのAPIでは3つのロールがサポートされています。

system
systemは、アシスタント(OpenAIの回答)の振る舞いを設定するときに有用です。会話に対する大枠の指示を与えられます。
user
エンドユーザーが指示を渡します。
assistant
assistantメッセージは以前のレスポンスを保存するのに役立ちます。

RubyのOpenAI APIは、レスポンスとしてオブジェクトを返します。このオブジェクトには以下が含まれます。

id
チャットのID。
object
レスポンスを返すAPIの名前。
created
レスポンスの作成日時。
model
レスポンスの生成に使われたモデル。
usage
渡されたトークンや生成されたトークンの個数を返します。
choices
モデルで生成されたメッセージと、結果のステータスです。

ここでは、roleパラメータにuserを設定し、contentパラメータにメッセージまたは質問を設定します。

🔗 例1: 問題を解かせる

以下のコード例では、宇宙船が地球から太陽まで到達するのに要する時間を算出するようOpenAI APIに指示し、ステップバイステップで計算結果を返しています。

client = OpenAI::Client.new

response = client.chat(
    parameters: {
        model: "gpt-3.5-turbo", # 必須
        messages: [{ role: "user", content: "If a spaceship is travelling at a speed of 70 KM/s, how long would it take to reach the sun from the earth?"}], # 必須
        temperature: 0.7,
    })

puts response.dig("choices", 0, "message", "content")
# The distance between the Earth and the Sun is approximately 149.6 million kilometers.

# To calculate the time it would take for the spaceship to reach the Sun from the Earth:

# time = distance ÷ speed

# time = 149,600,000 km ÷ 70 km/s

# time = 2,137,143 seconds

# Convert seconds to days:

# 2,137,143 seconds ÷ 86,400 seconds/day = 24.7 days

# Therefore, it would take approximately 24.7 days for the spaceship to reach the Sun from the Earth at a speed of 70 km/s.

地球と太陽の距離は約149.6百万キロメートルです。
宇宙船が地球から太陽に到達するまでの時間を計算するためには:
時間 = 距離 ÷ 速度
時間 = 149,600,000 km ÷ 70 km/s
時間 = 2,137,143 秒
秒を日数に変換します:
2,137,143 秒 ÷ 86,400 秒/日 = 約24.7 日
したがって、速度が70 km/sの場合、宇宙船が地球から太陽に到達するまで約24.7日かかります。
(OpenAI API経由での翻訳)

🔗 例2: 技術的な質問

以下の例は、Reactでの<></>の使い方をOpenAIに質問したものです。

response = client.chat(
    parameters: {
        model: "gpt-3.5-turbo", # 必須
        messages: [{ role: "user", content: "What is <> in react?"}], # 必須
        temperature: 0.7,
    })

puts response.dig("choices", 0, "message", "content")
# Reactでは、`<>`シンボル(またはフラグメントの省略形とも呼ばれる)を使用して、複数の要素をまとめてグループ化することができます....

Reactでは、<>記号はフラグメント(fragment)のショートハンドとしても知られており、複数の要素を足さなくてもグループ化するのに使われる。
(OpenAI API経由での翻訳)

🔗 例3: 会話の履歴を移動する

以下の例では、会話をよりインタラクティブかつダイナミックに行う目的で、会話の履歴を指示として渡しています。

response = client.chat(
  parameters: {
    model: "gpt-3.5-turbo", # Required.
    messages: [
      { role: "system", content: "You are an assistant that recommends movies" }
    ],
    temperature: 0.7,
  }
)

puts response.dig("choices", 0, "message", "content")
# Great, I am happy to help you with movie recommendations. Can you please let me know your preference? Do you have any specific genre or language in mind?

素晴らしいですね、映画のおすすめをお手伝いできて嬉しいです。ご希望のジャンルや言語はありますか?特に好みのあるものがありますか?
(OpenAI API経由での翻訳)

response = client.chat(
  parameters: {
    model: "gpt-3.5-turbo", # Required.
    messages: [
      { role: "system", content: "You are an assistant that recommends movies" },
      { role: "user", content: "Suggest an animation movie" },
    ],
    temperature: 0.7,
  }
)

puts response.dig("choices", 0, "message", "content")
# Sure, how about "Soul" directed by Pete Docter and Kemp Powers? It's a heartwarming and visually stunning movie about a middle school music teacher named Joe who finally gets his big break as a jazz musician, but then falls into a manhole and finds himself in a mystical realm where souls are prepared for life on earth. With the help of a spunky and determined soul named 22, Joe learns the true meaning of life and the importance of following your passions. It's a great movie for all ages and has won multiple awards including the Academy Award for Best Animated Feature.

どうですか、ピート・ドクターとケンプ・パワーズが監督した「ソウル」はいかがでしょう?これは中学校の音楽教師であるジョーが、ついにジャズミュージシャンとして大ブレイクするチャンスを手に入れますが、その後マンホールに落ちて自分自身を地球上で生活するための魂が準備される神秘的な領域に迷い込んでしまいます。元気で意欲的な22という魂の助けを借りて、ジョーは人生の真の意味や情熱を追求することの重要性を学びます。全年齢向けの素晴らしい映画であり、アカデミー賞最優秀アニメーション映画賞を含む多くの賞も受賞しています。
(OpenAI API経由での翻訳)

response = client.chat(
  parameters: {
    model: "gpt-3.5-turbo", # Required.
    messages: [
      { role: "system", content: "You are an assistant that recommends movies" },
      { role: "user", content: "Suggest an animation movie" },
      { role: "assistant", content: "Sure, how about 'Soul' directed by Pete Docter and Kemp Powers? It's a heartwarming and visually stunning movie about a middle school music teacher named Joe who finally gets his big break as a jazz musician, but then falls into a manhole and finds himself in a mystical realm where souls are prepared for life on earth. With the help of a spunky and determined soul named 22, Joe learns the true meaning of life and the importance of following your passions. It's a great movie for all ages and has won multiple awards including the Academy Award for Best Animated Feature."},
      { role: "user", content: "What is the runtime of the movie?" }
    ],
    temperature: 0.7,
  }
)

puts response.dig("choices", 0, "message", "content")
# The runtime of 'Soul' is 1 hour and 47 minutes.

『ソウル』の上映時間は1時間47分です。
(OpenAI API経由での翻訳)

🔗 回答をストリーミングする

回答をより魅力的なものにするために、回答をストリーミングできます。

結果をストリーミングするには、roleパラメータとcontentパラメータの他にstreamパラメータも渡す必要があります。

streamパラメータには、生成される回答のチャンクのストリームを出力するprocを渡せます。これで、gistのガイドに沿ってRailsアプリ内でChatGPTのようなメッセージのストリーミングを設定できるようになります。

以下の例では、OpenAI APIに色彩理論の説明を求めています。この結果では、完璧な結果を待つ代わりに詳細な解説が出力されるようになり、ユーザーに表示されるチャンクをストリーミングしてユーザー体験を向上することが可能になります。

client.chat(
    parameters: {
        model: "gpt-3.5-turbo", # 必須
        messages: [{ role: "user", content: "Explain about color theory"}], # 必須
        temperature: 1,
        stream: proc do |chunk, _bytesize|
            print chunk.dig("choices", 0, "delta", "content")
        end
    })
# The Hubble constant is a measure of the rate at which the universe is expanding. It is denoted by the symbol H0.....

ハッブル定数は、宇宙の膨張速度を測る指標です。H0という記号で表されます....
(OpenAI API経由での翻訳)

🔗 テキスト補完

GPT-3.5のtext-davinci-003モデルを用いてテキストを補完することにします。

モデルがテキストを補完するのに使うコンテンツはpromptパラメータにわたす必要があります。また、テキストを補完するために生成する必要のある最大トークン数も指定できます。

🔗 例1: SNSの文面を補完する

以下の例では、SNSの記述の補完をOpenAIに指示しています。

response = client.completions(
    parameters: {
        model: "text-davinci-003",
        prompt: "Complete this social media description 'Went for a hike'",
        max_tokens: 15
    })
puts response["choices"].map { |c| c["text"] }
# Today I went for a hike in nature! It was a peaceful

今日は自然の中でハイキングに行ってきました!とても平和な時間でした。
(OpenAI API経由での翻訳)

🔗 例2: コードを補完させる

以下の例では、Rubyのシンプルな加算コードをOpenAI APIに補完させています。

response = client.completions(
    parameters: {
        model: "text-davinci-003",
        prompt: "ruby sum of two numbers 'a = 10; b = 12;",
        max_tokens: 20
    })
puts response["choices"].map { |c| c["text"] }
# a + b # => 22

🔗 テキストの編集

テキスト編集にはtext-davinci-edit-001モデルを使うことにします。

inputパラメータにはコンテンツを、instructionパラメータにはタスクの指示をそれぞれ渡す必要があります。

🔗 例1: コードを別のプログラミング言語に変換する

以下の例では、あるコードスニペットをC言語に変換するよう指示しています。出力にはCのプログラム全体が返されています。ここで興味深いのは、渡したコードスニペットがどんな言語で書かれたものなのかについて何も指示していないことです。

response = client.edits(
  parameters: {
    model: "text-davinci-edit-001",
    input: "var a=10; var b=13;",
    instruction: "Convert the code into C language"
  }
)

puts response.dig("choices", 0, "text")
# #include<stdio.h>
# int main()
# {
# int a=10,b=13;
# printf("%d\t%d", a, b);
# return 0;
# }

🔗 例2: 検索置換とフォーマット整形

以下の例では、inputに文を渡して、単語の置き換え(theをthereに)と、各単語の冒頭を大文字に変更するようOpenAI APIに指示しています。

response = client.edits(
  parameters: {
    model: "text-davinci-edit-001",
    input: "the once was a ship that put",
    instruction: "Replace the with there and capitalize each word in the sentence"
  }
)

puts response.dig("choices", 0, "text")
# There Once Was A Ship That Put

テキストのモデレーション

OpenAI APIでテキストをモデレーションできます。APIは、コンテンツがOpenAIの利用ポリシーに準拠しているかどうかをチェックします。

OpenAI APIには7つのモデレーションカテゴリがあり、7つすべてについて点数を生成します。配点は0〜7で、値が大きいほど信頼性が高いことを表します。

参考: Moderation - OpenAI API

以下の例ではtext-moderation-stableモデルを利用しています。結果には「ヘイト」カテゴリの点数が返されます。

 response = client.moderations(parameters: { input: "I'm worried about that." })

 puts response.dig("results", 0, "category_scores", "hate")
 # 1.10379015e-05 ~= 0.0000110379

🔗 画像生成

DALL-Eモデルを使うと、画像を自然言語で説明することで画像を生成できます。

response = client.images.generate(parameters: { prompt: "Oil painting of a space shuttle", size: "512x512" })

puts response.dig("data", 0, "url")
# https://oaidalleapiprodscus.blob.core.windows.net/private/org-I.....

promptパラメータでは、生成する必要がある画像を説明し、sizeパラメータでは解像度を指定できます。生成できる画像サイズは256x256、512x512、または1024x1024です。sizeパラメータが指定されていない場合は、デフォルトで1024x1024に設定されます。

以下は生成した画像です。

chat-gpt-oil-painting

🔗 画像編集

画像の編集も可能ですが、そのためには画像を透明なセクションでマスキングする必要があります。マスキングされたセクションは、指示に基づいて変更できるようになります。

 response = client.images.edit(parameters: { prompt: "A dog standing under tree", image: "tree.png" })

 puts response.dig("data", 0, "url")
# https://oaidalleapiprodscus.blob.core.windows.net/private/org-INB....

以下はテストに用いた木の画像です。

tree.png

以下は生成した画像です。

chat-gpt-dog-under-tree

🔗 文字起こし

whisper-1モデルは音声データの文字起こしに利用できます。

response = client.transcribe(
  parameters: {
  model: "whisper-1",
  file: File.open("Conference.wav")
})

puts response["text"]
# This is Peter. This is Johnny. Kenny. And Josh. We just wanted to take a minute to thank you.

これはピーターです。ジョニーです。ケニーです。そしてジョシュです。私たちはちょっとした時間を割いて、あなたに感謝の気持ちを伝えたかっただけです。
(OpenAI API経由での翻訳)

🔗 まとめ

OpenAI APIを統合することで、ユーザー体験を強化してサイトを魅力的にする方法はいくらでも広がります。OpenAI APIは多彩な言語モデルを提供しており、従来のタスクのほとんどをシンプルにして複雑な問題も解決できるようになります。チャットボットで音声を翻訳・書き起こししたり、コードの作成やデバッグを行ったり、画像の生成や編集など、さまざまなことが可能になります。

関連記事

Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)


  1. 訳注: OpenAI APIは有料です。参考: Pricing -- openai.com 

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。