LoginSignup
11
3

More than 1 year has passed since last update.

【Elixir/Phoenix】LiveViewありがとう!はじめてでもChatGPTのWeb画面みたいなの作れたよ。

Posted at

はじめに

LivebookでChatGPIのAPIを使ってみて、使い方は理解できました。
要するに、メッセージのまとまりを渡すと、次のメッセージが返ってくる。
メッセージを編集できるUIがあったら便利そうなのでPhoenixの勉強も兼ねて作ってみます。

プロジェクト作成

DBサーバを用意するのが面倒だったのでsqlite3にします。

$ mix phx.new --database sqlite3 mygpt
$ cd mygpt
$ mix ecto.create
$ mix phx.server

ブラウザーで表示を確認してみます。

image.png

phx.gen.liveでメッセージの編集画面を作成

メッセージは、ChatGPTのAPI仕様とあわせて、contentとroleの値を持たせることにします。
roleの値は整数とします。APIを呼び出すときのroleとの対応を決めておきます。

API呼び出し時の値 roleの値
system 0
user 1
assistant 2

phx.gen.liveでDBと、liveviewを追加します。

$ mix phx.gen.live Chatlog Message messages role:integer content:string

router.exの書き換え

     pipe_through :browser
 
     get "/", PageController, :home
+
+    live "/messages", MessageLive.Index, :index
+    live "/messages/new", MessageLive.Index, :new
+    live "/messages/:id/edit", MessageLive.Index, :edit
+
+    live "/messages/:id", MessageLive.Show, :show
+    live "/messages/:id/show/edit", MessageLive.Show, :edit
   end
 
   # Other scopes may use custom stacks.
$ mix ecto.migrate

メッセージの追加削除編集ができる事を確認してみます。
http://localhost:4000/messagesを開くとメッセージの編集ができます。ChatGPTに問い合わせる内容をsystem, userのロールそれぞれで入力してみます。

image.png

ここまでが一瞬でできるのがいいすね。
いい感じで編集もできるので、テンションも上がってきます。
あとはAPI呼び出しするだけ。

ChatGPTのAPI呼び出しを追加する

環境設定等

openaiのAPIを呼び出すモジュールを追加

/mix.exs
       {:telemetry_poller, "~> 1.0"},
       {:gettext, "~> 0.20"},
       {:jason, "~> 1.2"},
-      {:plug_cowboy, "~> 2.5"}
+      {:plug_cowboy, "~> 2.5"},
+      {:openai, "~> 0.3.1"}
     ]
   end

openaiのconfigを行う。

  • APIキーを設定する処理を追加
  • タイムアウトを延ばす(openaiの応答に時間がかかるので必須)
/config/config.exs
 # Use Jason for JSON parsing in Phoenix
 config :phoenix, :json_library, Jason
 
+config :openai,
+  api_key: System.get_env("LB_OPENAI_API_KEY"),
+  # optional, passed to [HTTPoison.Request](https://hexdocs.pm/httpoison/HTTPoison.Request.html) options
+  http_options: [recv_timeout: 30_000]
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.

APIキーは環境変数LB_OPENAI_API_KEYに設定しておきます。
私は、WSLの.bashrcに次のように記述して追加しています。

export LB_OPENAI_API_KEY=*******************************************

API呼び出しのロジック追加

'/lib/mygpt/chat_gpt.ex'にChatGPTのAPIを呼び出すロジックを追加します。

Chatlogに保存されてるすべてのメッセージを'Chatlog.list_messages()'で取り出してChatGPTのAPIを呼び出します。roleの値を変換する処理がありますが、messages:にそのまま渡すだけ。

    messages =
      Chatlog.list_messages()
      |> Enum.map(fn %{content: content, role: role_num} ->
        %{content: content, role: to_str(role_num)}
      end)

    {:ok, result} =
      OpenAI.chat_completion(
        model: "gpt-3.5-turbo",
        messages: messages
      )
      |> IO.inspect(label: "api result")

呼び出した後、ChatGPTからの応答を、'Chatlog.create_message(new_message)'でChatlogに追加しています。

    message = hd(result.choices)["message"]

    new_message = %{
      content: message["content"],
      role: to_num(message["role"])
    }

    Chatlog.create_message(new_message)

chat_gpt.ex全体はこれ

/lib/mygpt/chat_gpt.ex
defmodule Mygpt.ChatGpt do
  alias Mygpt.Chatlog

  def callapi() do
    messages =
      Chatlog.list_messages()
      |> Enum.map(fn %{content: content, role: role_num} ->
        %{content: content, role: to_str(role_num)}
      end)

    {:ok, result} =
      OpenAI.chat_completion(
        model: "gpt-3.5-turbo",
        messages: messages
      )
      |> IO.inspect(label: "api result")

    message = hd(result.choices)["message"]

    new_message = %{
      content: message["content"],
      role: to_num(message["role"])
    }

    Chatlog.create_message(new_message)
  end

  def to_num(str) do
    case str do
      "system" -> 0
      "user" -> 1
      "assistant" -> 2
    end
  end

  def to_str(role_num) do
    case role_num do
      0 -> "system"
      1 -> "user"
      2 -> "assistant"
    end
  end
end

送信ボタン追加

/lib/mygpt_web/live/message_live/index.html.heex
     </.link>
   </:action>
 </.table>
+<.button phx-click="send">Send</.button>
 
 <.modal :if={@live_action in [:new, :edit]} id="message-modal" show on_cancel={JS.patch(~p"/messages")}>
   <.live_component

送信ボタンのハンドラー追加

ここが今回のプロラムで、一番苦労したところは、ここでした。

最初は、Sendボタンを押したら'Mygpt.ChatGpt.callapi()'を呼び出すだけにしていました。
これだと、ボタンを押すと、APIが呼び出せれ、DBにChatGPTからのメッセージが追加されます。
しかし、画面には追加したメッセージが表示されません。ページリロードすると表示されます。
新規のメッセージを追加するときの処理を真似して、stream_insert()使って、ChatGPTからのメッセージを画面にも追加するようにしました。

/lib/mygpt_web/live/message_live/index.ex
 
     {:noreply, stream_delete(socket, :messages, message)}
   end
+
+  def handle_event("send", _, socket) do
+    {:ok, message} = Mygpt.ChatGpt.callapi()
+
+    {:noreply, stream_insert(socket, :messages, message)}
+  end
 end

実行

$ mix deps.get
$ mix phx.server

送信ボタンを押すと、ChatGPTからのメッセージ「なにか食べたい?」が追加されました。

image.png

子供のメッセージを追加して、再び、Sendしてみます

image.png

会話ができました!

まとめ

  • phx.gen.liveでメッセージの編集画面がサクッとつくれてテンションが上がった
  • stream_insert()の使い方が勉強になった
  • ChatGPIのWeb画面にある会話のヒストリー機能も勢いで作れそう
  • もうちょっとUIを改善したら普段使いできるんじゃないか
  • Elixirは楽しい

ソースコードここです

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3