はじめに
LivebookでChatGPIのAPIを使ってみて、使い方は理解できました。
要するに、メッセージのまとまりを渡すと、次のメッセージが返ってくる。
メッセージを編集できるUIがあったら便利そうなのでPhoenixの勉強も兼ねて作ってみます。
プロジェクト作成
DBサーバを用意するのが面倒だったのでsqlite3にします。
$ mix phx.new --database sqlite3 mygpt
$ cd mygpt
$ mix ecto.create
$ mix phx.server
ブラウザーで表示を確認してみます。
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のロールそれぞれで入力してみます。
ここまでが一瞬でできるのがいいすね。
いい感じで編集もできるので、テンションも上がってきます。
あとはAPI呼び出しするだけ。
ChatGPTのAPI呼び出しを追加する
環境設定等
openaiのAPIを呼び出すモジュールを追加
{: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の応答に時間がかかるので必須)
# 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全体はこれ
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
送信ボタン追加
</.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からのメッセージを画面にも追加するようにしました。
{: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からのメッセージ「なにか食べたい?」が追加されました。
子供のメッセージを追加して、再び、Sendしてみます
会話ができました!
まとめ
- phx.gen.liveでメッセージの編集画面がサクッとつくれてテンションが上がった
- stream_insert()の使い方が勉強になった
- ChatGPIのWeb画面にある会話のヒストリー機能も勢いで作れそう
- もうちょっとUIを改善したら普段使いできるんじゃないか
- Elixirは楽しい
ソースコードここです