🔴

【Ruby】RailsでChatGPT(ruby-openai)導入する方法

2024/05/25に公開

完成イメージ

Railsのプロジェクトさえ作成していれば、だれでも簡単に以下のようなチャットボットが作成できます。

前提

  • Railsのプロジェクトを作成済みである。

1.OpenAIのwebサイトにログイン

https://openai.com/chatgpt/

2.クレジットカードの登録

⚙からBillingPayment methodsAdd payment methodを選択。

クレカの登録を行う。

3.APIキーの作成

🔒からDashboard>API keys>Create new secret keyをクリックする。

必要な項目を入力しCreate secret keyをクリックする。

4.dotenv-railsのインストール

Gemファイルにgem 'dotenv-rails'と記載し、ターミナルでbundle installを実行する。

Gemfile
gem 'dotenv-rails'
ターミナル
bundle install

5.Railsのプロジェクト内に.envを作成

.envファイルを作成するためターミナルでtouch .envを実行。(手動でプロジェクトのルートディレクトリに.envファイルを作成してもよい)

ターミナル
touch .env

作成した、.envファイル内にOPENAI_ACCESS_TOKEN=your_openai_api_keyの記載を行う。your_openai_api_keyの部分に手順3で取得したAPIキーを貼り付け、””(ダブルクォーテーション)で囲む。

.env
OPENAI_ACCESS_TOKEN="your_openai_api_key"

6..gitignoreファイルの編集

.gitignoreファイル内に/.envと記載し、先ほどのAPIキーを機密情報として管理する。

7.ruby-openaiのインストール

Gemfileruby-openaiと記載し、ターミナルでbundle installを実行する。

Gemfile
gem 'ruby-openai'
ターミナル
bundle install

8.設定ファイルの作成

ターミナルで以下のコマンドを実行するか、initializersファイル内にopenai.rbファイルを手動で作成する。

ターミナル
touch config/initializers/openai.rb

ファイル内に下記の記述を行う。

intializers/openai.rb
OpenAI.configure do |config|
  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
  config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID", nil) # Optional
  config.log_errors = true # Optional, but recommended for development
end

9.コントローラの作成

ターミナルで以下のコマンドを実行し、コントローラを作成する。

ターミナル
rails g controller openai/tests
ターミナル(出力結果)
$ rails g controller openai/tests
Running via Spring preloader in process 306032
      create  app/controllers/openai/tests_controller.rb
      invoke  erb
      create    app/views/openai/tests
      invoke  test_unit
      create    test/controllers/openai/tests_controller_test.rb
      invoke  helper
      create    app/helpers/openai/tests_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/openai/tests.scss

10.コントローラの編集

以下コントローラの内容を先ほど作成したtests_controller.rbファイル内に張り付ける。

app/controllers/openai/test_controller.rb
class Openai::TestsController < ApplicationController
  protect_from_forgery

  def show

  end

  def generate_text
    @user_input = params[:user_input]

    if @user_input.present?
      client = OpenAI::Client.new
      response = client.chat(
        parameters: {
          model: "gpt-3.5-turbo",
          messages: [
            { role: "system", content: "関西弁で返答してください" },
            { role: "user", content: @user_input }
          ],
          temperature: 0.7
        }
      )
      @generated_text = response.dig("choices", 0, "message", "content")
      render json:{text: @generated_text}
    end
  end
end
コントローラの記述内容について詳しく知りたい方はこちら
コントローラの基本形
  client = OpenAI::Client.new
  response = client.chat(
    parameters: {
      model: "gpt-3.5-turbo", # 使用するモデルを指定
      messages: [
        { role: "system", content: "関西弁で返答してください" },
        { role: "user", content: @user_input }
      ],
      temperature: 0.7 # オプション:生成の多様性を制御
    }
  )

<Parameters>
model:GPTのモデルを指定する

  • gpt-4o (リリース:2024-05-13 / 学習データ : 2023年10月)
  • gpt-4-turbo (リリース:2024-04-09 / 学習データ : 2023年12月)
  • gpt-3.5-turbo(リリース:2023-6-13 / 学習データ :2021年9月)
modelの詳細

SettingsPersonalLimitsから確認可能

messages:配列でroleとcontentを指定し、返答の方法を決める。
【role】

  • system:役割、設定、前提などの入力に使用
  • user:ユーザの入力
  • assistant:AIからの出力
例:関西弁で返答してほしいとき
controller
response = client.chat(
      parameters: {
        model: "gpt-3.5-turbo", 
        messages: [
            { role: "system", content: "関西弁で返答してください"},
            { role: "user", content: "こんにちは"}
        ],
        temperature: 0.7 
      }
    )

出力結果

temperature:0~2の範囲で設定できる。値が低いほど回答のランダム性が減る。特定の用途に使いたい場合などは0に近づけて、会話などのランダム性を楽しみたい場合には2に近づけていく。

max_tokens:サンプルには記載していないパラメータであるが、返答の最大トークン数を設定できる。無駄に長い返答がきたりすることもあるので料金面などを気にする場合はきちんと設定してあげた方が良い。

APIからのレスポンス(JSON形式)の中身について
 @generated_text = response.dig("choices", 0, "message", "content")

responseChatGPTAPIのレスポンスの中身)は、以下のようなJSON形式で返される。

{
"id": "XXXXXXXX",
"object": "chat.completion",
"created": 99999999,
"model": "gpt-3.5-turbo-0301",
"usage": { "prompt_tokens": 19, "completion_tokens": 27, "total_tokens": 46 },
"choices": [
{
"message": {
"role": "assistant",
"content": "おおきに!元気かい?"
},
"finish_reason": "stop",
"index": 0
}
]
}

11.ビューの作成

app/views/openaiフォルダ内にshow.html.erbファイルを作成し、以下のコードをファイル内に張り付ける。

show.html.erbファイル内に張り付けるコード
app/views/openai/show.html.erb
<div class="flex flex-col items-center justify-center py-6 w-full h-full">
  <div class="max-w-4xl w-full space-y-8 h-full">
    <div id="conversation" class="space-y-4 p-4 rounded overflow-y-auto">
    </div>
    <form id="text_form" class="flex items-center px-2 py-1 border-t border-gray-200 bg-white">
      <label for="file_input" class="cursor-pointer inline-block relative">
        <svg class="w-6 h-6 fill-current text-gray-600 transition-colors duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
        <path d="M364.2 83.8c-24.4-24.4-64-24.4-88.4 0l-184 184c-42.1 42.1-42.1 110.3 0 152.4s110.3 42.1 152.4 0l152-152c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-152 152c-64 64-167.6 64-231.6 0s-64-167.6 0-231.6l184-184c46.3-46.3 121.3-46.3 167.6 0s46.3 121.3 0 167.6l-176 176c-28.6 28.6-75 28.6-103.6 0s-28.6-75 0-103.6l144-144c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-144 144c-6.7 6.7-6.7 17.7 0 24.4s17.7 6.7 24.4 0l176-176c24.4-24.4 24.4-64 0-88.4z"/>
        </svg>
        <input type="file" id="file_input" class="hidden">
      </label>
      <input type="text" placeholder="入力してください" id="user_input" class="flex-1 px-3 py-2 border rounded border-gray-300 focus:border-blue-500 focus:outline-none focus:ring">
      <button id="button_post" class="px-4 py-2 bg-green-500 text-white rounded-md transition duration-300 ease-in-out hover:bg-green-600 focus:outline-none focus:ring focus:ring-green-200 ml-2">送信</button>
    </form>
  </div>
</div>

<script>
  const button_post = document.getElementById('button_post');
  button_post.addEventListener('click', function(event) {
    event.preventDefault();

    const formText = document.querySelector("#user_input");
    if (formText.value.trim() === "") {
      return;
    }

    const url = "<%= openai_tests_path('json') %>?user_input=" + formText.value;
    const postOptions = {
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      method: "GET"
    };

    fetch(url, postOptions)
      .then(response => response.json())
      .then(item => {
        const list = document.getElementById("conversation");

        const inputHTML = `
          <div class="text-right flex items-end justify-end space-x-2">
            <div class="chat-bubble inline-block bg-blue-100 px-4 py-2 rounded-lg max-w-3/4 break-words">
              <span>${formText.value}</span>
            </div>
            <div class="icon-container w-8 h-8 flex-shrink-0">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="w-full h-full"><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg>
            </div>
          </div>`;
        list.insertAdjacentHTML("beforeend", inputHTML);

        const responseHTML = `
          <div class="text-left flex items-start space-x-2">
            <div class="icon-container w-8 h-8 flex-shrink-0">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" class="w-full h-full"><path d="M320 0c17.7 0 32 14.3 32 32V96H472c39.8 0 72 32.2 72 72V440c0 39.8-32.2 72-72 72H168c-39.8 0-72-32.2-72-72V168c0-39.8 32.2-72 72-72H288V32c0-17.7 14.3-32 32-32zM208 384c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H208zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H304zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H400zM264 256a40 40 0 1 0 -80 0 40 40 0 1 0 80 0zm152 40a40 40 0 1 0 0-80 40 40 0 1 0 0 80zM48 224H64V416H48c-26.5 0-48-21.5-48-48V272c0-26.5 21.5-48 48-48zm544 0c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H576V224h16z"/></svg>
            </div>
            <div class="chat-bubble inline-block bg-gray-100 px-4 py-2 rounded-lg max-w-3/4 break-words">
              <span>${item.text}</span>
            </div>
          </div>`;
        list.insertAdjacentHTML("beforeend", responseHTML);
        formText.value = "";
        list.scrollTop = list.scrollHeight;
      })
      .catch(error => console.error('Error:', error));
  });
</script>

fetch()メソッド

https://breezegroup.co.jp/202004/javascript-fetch/
https://zenn.dev/syu/articles/946a2b5fdf292e
https://qiita.com/soarflat/items/1a9613e023200bbebcb3

12.Tailwindcssの導入(CDN)

以下のコードをapp/views/layouts/application.html.erb内の<head></head>タグ内に張り付ける。

TailswindcssのCDNのコード
<script src="https://cdn.tailwindcss.com"></script>
app/views/layouts/application.html.erbのheadタグ
<!DOCTYPE html>
<html>
  <head>
    <title>Demo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://cdn.tailwindcss.com"></script> //ここ
  </head>

13.ルーティングの設定

app/config/routes.rbファイル内に以下の記述を行う。

app/config/routes.rb
  namespace :openai do
    get 'tests/', to: 'tests#generate_text'
    get 'tests/show', to: 'tests#show'
  end

14.サーバーを立ち上げて表示を確認

ターミナル
rails s

お疲れ様でした。

Discussion