このコラムはLiveViewでLLMを使います
そして非同期処理を対応します
実行イメージ
チャッピーの動き方で文字が流れるイメージしてもらえると良いかも
前提知識
ElixirでOllama
LiveViewの非同期処理
今回はOllamaも非同期にします(streamの部分)
プログラムを書く
lib/llm_async_web/live/index.ex
defmodule LlmAsyncWeb.Index do
use LlmAsyncWeb, :live_view
def mount(_params, _session, socket) do
socket =
assign(socket, text: "実行ボタンを押してください")
|> assign(input_text: "Elixirについて教えてください")
|> assign(btn: true)
{:ok, socket}
end
def handle_event("start", _, socket) do
pid = self()
input_text = socket.assigns.input_text
socket =
assign(socket, btn: false)
|> assign(text: "")
|> assign_async(:ret, fn -> run(pid, input_text) end)
{:noreply, socket}
end
def handle_event("update_text", %{"text" => new_text}, socket) do
{:noreply, assign(socket, input_text: new_text)}
end
def handle_info(%{"done" => false, "response" => response}, socket) do
text = socket.assigns.text <> response
{:noreply, assign(socket, text: text)}
end
def handle_info(%{"done" => true}, socket) do
socket =
assign(socket, btn: true)
{:noreply, socket}
end
def run(pid, text) do
client = Ollama.init()
{:ok, stream} =
Ollama.completion(client,
model: "gemma3:27b",
prompt: text,
stream: true
)
stream
|> Stream.each(&Process.send(pid, &1, []))
|> Stream.run()
{:ok, %{ret: :ok}}
end
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="p-5">
<form>
<textarea id="text_input" name="text" phx-change="update_text" class="input w-[400px]">{@input_text}</textarea>
</form>
<button disabled={!@btn} class="btn" phx-click="start">実行</button>
<p class="m-2">{@text}</p>
</div>
</Layouts.app>
"""
end
end
解説
今回このコラムのポイントのみ書きます
-
実行ボタンクリック
def handle_event("start", _, socket) do- 実行ボタンを無効化
- assign_async(:ret, fn -> run(pid, input_text) end)
- Ollamaのプロセスを実行 ここでassign_asyncによってプロセスが独立します
- つまり、GUIをブロックしません
-
Ollamaのプロセス
def run(pid, text) do- Ollama.completionで
stream: trueを指定 -
Stream.each(&Process.send(pid, &1, [])を書く- Ollamaが結果をリアルタイムで返します
- Ollama.completionで
-
Ollamaのプロセスの実行中に受け取る
def handle_info(%{"done" => false, "response" => response}, socket) do- done" => false は実行中の意味
- "response" => responseは実行した瞬間の結果を取得
-
text = socket.assigns.text <> responseで過去の結果と連携
-
Ollamaのプロセスの実行終了を受け取る
def handle_info(%{"done" => true}, socket) do- "done" => trueは終了の意味
- 実行ボタンを有効化
ソース