Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

WebServer

webserver.livemd

WebServer

Mix.install(
  [
    {:plug, "~> 1.17"},
    {:kino, "~> 0.15.3"},
    {:bandit, "~> 1.6"},
    {:req, "~> 0.5.10"},
    {:kino_bumblebee, "~> 0.5.0"},
    {:exla, ">= 0.0.0"}
  ],
  config: [nx: [default_backend: EXLA.Backend]]
)

1. Writting a simple webserver

defmodule MyWeb do
  use Plug.Builder
  
  plug :fetch_query_params
  plug :render

  def render(conn, _opts) do
    text = conn.params["text"]
    output = Nx.Serving.batched_run(:my_ai, text)
    [%{label: label, score: _score} | _tail] = output.predictions
    Plug.Conn.send_resp(conn, 200, "Analysis: #{label}")
  end
end

################################### ML Model
{:ok, model_info} =
  Bumblebee.load_model({:hf, "finiteautomata/bertweet-base-emotion-analysis"})

{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "vinai/bertweet-base"})

serving =
  Bumblebee.Text.text_classification(model_info, tokenizer,
    compile: [batch_size: 1, sequence_length: 100],
    defn_options: [compiler: EXLA]
  )

Kino.start_child!({Nx.Serving, serving: serving, name: :my_ai})
Kino.start_child!({Bandit, plug: MyWeb, port: 6789})

22:42:40.961 [info] Running MyWeb with Bandit 1.6.11 at 0.0.0.0:6789 (http)
#PID<0.347.0>

2. Kino start child

Kino docs here

Starts a process under the Kino supervisor.

The process is automatically terminated when the current process terminates or the current cell reevaluates.

After run this, Kino knows there is a supervision tree to render.

#Kino.start_child!({Bandit, plug: MyWeb, port: 6789})
nil

3. Test our webserver using Req

Req.get!("http://localhost:6789/", params: [text: "Coding in elixir is awesome!"]) # -> 200 
%Req.Response{
  status: 200,
  headers: %{
    "cache-control" => ["max-age=0, private, must-revalidate"],
    "date" => ["Mon, 14 Apr 2025 04:42:56 GMT"],
    "vary" => ["accept-encoding"]
  },
  body: "Analysis: joy",
  trailers: %{},
  private: %{}
}

4. AI Spech to Text

################################### ML Model

{:ok, model_info} =
  Bumblebee.load_model({:hf, "finiteautomata/bertweet-base-emotion-analysis"})

{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "vinai/bertweet-base"})

serving =
  Bumblebee.Text.text_classification(model_info, tokenizer,
    compile: [batch_size: 1, sequence_length: 100],
    defn_options: [compiler: EXLA]
  )

Kino.start_child!({Nx.Serving, serving: serving, name: :my_ai})
#PID<0.4428.0>
text_input = Kino.Input.textarea("Text", default: "Oh wow, I didn't know that!")
form = Kino.Control.form([text: text_input], submit: "Run")
frame = Kino.Frame.new()

Kino.listen(form, fn %{data: %{text: text}} ->
  Kino.Frame.render(frame, Kino.Text.new("Running..."))
  output = Nx.Serving.batched_run(:my_ai, text)

  output.predictions
  |> Enum.map(&amp;{&amp;1.label, &amp;1.score})
  |> Kino.Bumblebee.ScoredList.new()
  |> then(&amp;Kino.Frame.render(frame, &amp;1))
end)

Kino.Layout.grid([form, frame], boxed: true, gap: 16)