🤖

【Rails】OpenAI APIとTurboでchatbotを作成する

2024/08/03に公開

はじめに

こんにちは。Ruby on Railsを学習中のmockeyと申します。
現在作成中のアプリ内にFAQ対応のchatbot機能をopenAI APIとturboを使って実装します。
chatbot機能を実装する理由は、ユーザーが気軽に疑問点をアプリ内で解決できれば便利だと考えたからです。

備忘録がてら記載しますので、どなたかのお役に立てれば幸いです。

この記事のゴール

  • OpenAI API、Turboを使ってチャットボットを作成する

Image from Gyazo

Turbo Streamとは?

Turbo Streamは、Hotwireの一部であり、Railsアプリケーションでリアルタイムのインタラクションを実現するための技術です。ページ全体をリロードすることなく、部分的に更新する事ができます。

なぜ使用するのか?

Turbo Streamを使用することで、以下のメリットがあると考えています。

  • パフォーマンスの向上:
    ページ全体のリロードをせず必要な部分だけを更新するので、サーバーへのリクエストが減り、必要なデータだけを取得して更新できる。
    これにより、ユーザーは待たされることなく、すぐに新しい情報を見ることができる。

  • ユーザーエクスペリエンスの向上:
    動的でスムーズなインタラクション(ユーザーによる操作)が可能になり、ユーザーにとって使いやすいアプリケーションを構築できる。

  • コードのシンプル化
    複雑なJavaScriptを自分で書く必要が減る。サーバーサイドでHTMLを生成して送信できるので、クライアント側のコードがシンプルになり、メンテナンスもしやすくなる。

今回はTurbo Streamを使ってユーザーが質問を送信した際に、その回答を非同期で表示する機能を実装します。

バージョン

  • Ruby 3.2.3
  • Rails 7.1.3.4
  • PosgresSQL 14.11
  • tailwindcss 3.4.7
  • daisyUI 4.12.10
  • 開発環境 Docker
  • デプロイ render.com

OpenAI APIキーの取得

実装のためにはAPIキーが必要なので以下のサイトから取得します。
アカウントの登録がまだの方はアカウントの登録と、支払い方法の登録をこのリンクから実施してください。

https://platform.openai.com/api-keys

こちらのchatbot作成記事も大変分かりやすいです。(DBに保存しない方法)

  • マイページ内、「+Create new secret key」をクリックします。

  • 任意の名前を決めて「Create secret key」をクリックします。
    クリックするとキーが発行されるので、他人に見せないようにコピーをして保管します。
    Image from Gyazo

.env内にOPENAI_API_KEY="your_openai_api_key"を記載

.envファイルにOPENAI_API_KEY="your_openai_api_key"を記載します。
APIキーを非公開にする方法は以下の記事に記載しましたので、もしよろしければご覧ください。

環境変数と.gitignoreを使用してAPIキーを非公開にする手順

openAIのgemをインストール

RailsアプリケーションのGemfileに以下のコードを追記し、bundle installを実行します。
https://github.com/alexrudall/ruby-openai

gem ruby-openaiって?

OpenAI APIとやり取りするためのGemです。これを使うことで、OpenAIのエンドポイントにリクエストを送り、応答を受け取ることができます。
初期化時にAPIキーを設定し、クライアントを使ってAPIリクエストを送信します。(ChatGPTより)

Gemfile
gem "ruby-openai"

APIキーが読み込まれているかをコンソールで確認します。
下記コマンドを打った後にAPIキーが表示されたらOKです。

terminal
irb(main):001> puts ENV['OPENAI_API_KEY']

APIクライアントの設定

config/initializers/openai.rbファイルを作成し、環境変数を読み込む設定を追加します。

config/initializers/openai.rb
require 'openai'

OpenAI.configure do |config|
  config.access_token = ENV.fetch("OPENAI_API_KEY")
  config.log_errors = true # 開発時にエラーを確認できるようにする。本番環境ではfalseにする
end

チャットボット用のコントローラーを作成する

terminal
rails g controller Chatbots

フォームの送信を処理するためのコントローラーを設定します。
ユーザーの質問を受け取り、その後、AIの回答を取得して表示します。
GPTの種類は先日発表されたばかりのgpt-4o-miniを使用します。
また、ユーザーの質問とAIからの返答をturboを使って非同期で表示します。

app/controllers/chatbots_controller.rb

    class ChatbotsController < ApplicationController
  before_action :require_login

  def ask #ユーザーがアクセスして質問を入力するページを表示
  end

  def answer #ユーザーが質問を入力して送信すると、質問をOpenAIに送信して回答を取得
    client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])

    prompt = params[:question] #ユーザーが入力した質問を取得

    response = client.chat(
      parameters: {
        model: "gpt-4o-mini",
        messages: [
          { role: "system", content: "あなたは海外旅行初心者に役立つ優秀な旅行アシスタントです。ユーザーの質問に対して、日本語で明確かつ簡潔な旅行アドバイスを提供してください。300字以内で書いてください。リストや重要なポイントを箇条書きで改行してください。各ポイントには具体例や説明を加えてください。記号やマークダウン形式(**など)は使用しないでください。" },
          { role: "user", content: prompt }
        ],
        max_tokens: 500,
        temperature: 0.7
      }
    )

    if response["choices"] && response["choices"][0] && response["choices"][0]["message"]
      @answer = response["choices"][0]["message"]["content"]
    else
      @answer = "回答が見つかりませんでした"
    end

    respond_to do |format|
      format.turbo_stream
    end
  end
end

ルーティングの設定

routes.rb
get 'chatbots/ask' => 'chatbots#ask'
post 'chatbots/answer' => 'chatbots#answer'

ビューの作成

app/views/chatbots/ask.html.erbに質問入力フォームを追加します。
また、入力フォームを常に画面の下部に表示させるために、fixedクラスを適用しています。

app/views/chatbots/ask.html.erb
<div class="container mx-auto mt-20 min-h-screen flex flex-col">
  <h1 class="text-2xl sm:text-3xl font-bold text-center mb-8">AI Chatbot</h1>
  <p class="text-center text-xs sm:text-base">旅について知りたいこと、不安なことはありませんか?</p>
  <p class="text-center text-xs sm:text-base">AI Chatbotに質問してみましょう!</p>

  <div class="card bg-blue-50 w-2/3 mx-auto flex flex-col justify-between shadow-xl rounded-lg p-6 mt-8 mb-4 flex-grow">
    <div class="card-body text-center flex flex-col items-center justify-center flex-grow">
      <div id="chatbox" class="mt-4 w-full space-y-4 flex-grow">
        <div class="chat chat-start">
          <div class="chat-image avatar">
            <div class="w-10 h-10 rounded-full">
              <%= image_tag "ai_bot.png", class: "w-10 h-10 rounded-full" %>
            </div>
          </div>
          <div class="chat-header text-xs sm:text-base text-gray-500">
            AI chatbot
          </div>
          <div class="chat-bubble max-w-xs mx-2 py-2 px-4 rounded-lg relative text-xs sm:text-base">
            <p>こんにちは!私はAI Chatbotです。何か質問があればお気軽にどうぞ!</p>
          </div>
        </div>
      </div>
    </div>
    <div class="fixed bottom-0 left-0 right-0 bg-white p-4">
      <%= form_with(model: @chatbot, url: chatbots_answer_path, data: { controller: "form-reset", action: "turbo:submit-end->form-reset#reset", turbo_frame: "chatbot-form" }) do |f| %>
        <div class="form-group flex items-center space-x-4 w-full">
          <div class="mr-2 flex-grow">
            <%= f.text_area :question, placeholder: "聞きたい内容を入力してください", class: "input input-bordered w-full h-20 rounded text-sm" %>
          </div>
          <div>
            <%= f.submit "質問する", class: "bg-purple-300 hover:bg-purple-500 btn btn primary px-6 py-3 hover:shadow-sm hover:translate-y-0.5 transform transition rounded shadow-lg" %>
          </div>
        </div>
      <% end %>
    </div>
  </div>
</div>

  • 非同期レスポンスのビュー

指定したTurboFrameの中の最後に要素を追加するappendを使います。
daisyUIのChat bubbleコンポーネントを使用しています。
https://daisyui.com/components/chat/

app/views/chatbots/answer.turbo_stream.erb
<%= turbo_stream.append "chatbox" do %>
  <div class="chat chat-end">
    <div class="chat-image avatar">
      <div class="w-10 rounded-full">
        <%= image_tag "user.png", class: "w-10 h-10 rounded-full" %>
      </div>
    </div>
    <div class="chat-header text-xs sm:text-base">
      <%= current_user.name %>
    </div>
    <div class="chat-bubble bg-green-100 max-w-xs mx-2 py-2 px-4 rounded-lg relative text-xs sm:text-base">
      <div><%= params[:question] %></div>
    </div>
  </div>

  <div class="chat chat-start">
    <div class="chat-image avatar">
      <div class="w-10 rounded-full">
        <%= image_tag "ai_bot.png", class: "w-10 h-10 rounded-full" %>
      </div>
    </div>
    <div class="chat-header text-xs sm:text-base">
        AI chatbot
    </div>
    <div class="chat-bubble bg-green-500 max-w-xs mx-2 py-2 px-4 rounded-lg relative text-xs sm:text-base">
      <div><%= @answer %></div>
      <div class="absolute w-3 h-3 bg-green-500 bottom-0 right-2 transform translate-y-1/2 rotate-45"></div>
    </div>
  </div>
<% end %>

これでチャットbot機能の完成です。
入力フォームから送信後、フォームの内容を自動でクリアにしたい場合は以下の記事をもし良ければご覧ください。

【Rails7】Stimulusを使って入力フォームをクリアにする

おわりに

今回はgpt-4o-miniとturboを使用して実装しました。gpt-4o-miniは回答の精度も高く、今後広く使われそうだと予想しています。
コードはまだ綺麗ではないので、改善していく予定です。
今後の機能拡張としてはチャットのリアルタイム表示をできたらいいなと検討しているので、キャッチアップを頑張りたいと思います。

ご覧いただきありがとうございました!

参考

Discussion