Skip to content

Latest commit

 

History

History
212 lines (173 loc) · 4.86 KB

demo_chat.livemd

File metadata and controls

212 lines (173 loc) · 4.86 KB

LM Prompting

Mix.install([
  {:kino, "~> 0.9.1"},
  {:poison, "~> 4.0"},
  {:httpoison, "~> 1.8"},
  {:explorer, "~> 0.5.0"},
  {:kino_explorer, "~> 0.1.7"}
])

Section

defmodule OpenAIClient do
  require HTTPoison

  defstruct [:openai_key, :model]

  def new(openai_key \\ nil, openai_key_path \\ nil) do
    key =
      case openai_key do
        nil -> load_openai_key(openai_key_path)
        _ -> openai_key
      end

    %OpenAIClient{:openai_key => key}
  end

  def load_openai_key(path \\ "openai_medsi_key.txt") do
    {:ok, key} = File.read(Path.expand(path))
    String.trim(key)
  end

  defp get_completion_body(model, prompt) do
    Poison.encode!(%{
      "model" => model,
      "prompt" => prompt,
      "temperature" => 0.9,
      "max_tokens" => 150,
      "top_p" => 1,
      "frequency_penalty" => 0.0,
      "presence_penalty" => 0.6,
      "stop" => [" Human:", " AI:"]
    })
  end

  defp get_chat_body(model, prompt) do
    Poison.encode!(%{
      "model" => model,
      "messages" => [%{"role" => "user", "content" => prompt}],
      "temperature" => 0.9,
      "max_tokens" => 15
    })
  end

  defp is_completion_model_name(model) do
    model |> String.contains?("text-davinci")
  end

  defp get_openai_params(model, prompt) do
    if is_completion_model_name(model) do
      {"https://api.openai.com/v1/completions", get_completion_body(model, prompt)}
    else
      {"https://api.openai.com/v1/chat/completions", get_chat_body(model, prompt)}
    end
  end

  defp _get_response(client, prompt, model) do
    headers = [
      {"Content-Type", "application/json"},
      {"Authorization", "Bearer #{client.openai_key}"}
    ]

    {url, request_body} = get_openai_params(model, prompt)
    options = [recv_timeout: 10000, timeout: 10000]
    HTTPoison.post(url, request_body, headers, options)
  end

  defp get_response(%{"choices" => choices}) do
    [result | _] = choices
    {:ok, get_response_text(result)}
  end

  defp get_response(%{"error" => error}) do
    {:error, error["message"]}
  end

  defp get_response_text(%{"message" => message}) do
    message["content"]
  end

  defp get_response_text(%{"text" => text}) do
    text
  end

  def get_completion_response(client, prompt, model) do
    maybe_api_response = _get_response(client, prompt, model)

    case maybe_api_response do
      {:ok, %_{body: body}} ->
        body = Poison.decode!(body)
        response = get_response(body)
        response

      {status, %_{reason: reason}} ->
        {status, Atom.to_string(reason)}

      _ ->
        maybe_api_response
    end
  end
end
defmodule Generator do
  def openai_client do
    OpenAIClient.new(nil, "openai_key.txt")
  end

  def generate(prompt, model \\ "gpt-3.5-turbo-0613") do
    maybe_result = openai_client() |> OpenAIClient.get_completion_response(prompt, model)

    case maybe_result do
      {:error, result} -> "Error: " <> result
      {:ok, result} -> result
      _ -> "OpenAI Error"
    end
  end
end
response = Generator.generate("a cat walks into a bar")
Kino.Markdown.new("### Chat\n----------")
models = ["text-davinci-003", "gpt-3.5-turbo-0613", "gpt-4-0613"]

inputs = [
  prompt: Kino.Input.text("Prompt"),
  model: Kino.Input.select("model", for(m <- models, do: {m, m}))
]

chat_form = Kino.Control.form(inputs, submit: "Send", reset_on_submit: [:prompt])
frame = Kino.Frame.new()
Kino.Markdown.new("### Viewable table results\n------------")
json_frame = Kino.Frame.new()
Kino.Markdown.new("### Exportable results in JSONLines\n----------")
table_frame = Kino.Frame.new()
defmodule KinoUtils do
  def prettyprint(record) do
    ~s"""
    #### prompt:
    #{record["prompt"]},
    #### response:
    #{record["response"]}
    """
    |> String.trim()
  end

  def prepare_outputs(prompt, response) do
    %{"prompt" => prompt, "response" => response}
  end

  def display_tables(prompt, response, table_frame, json_frame) do
    record = prepare_outputs(prompt, response)
    Kino.Frame.append(table_frame, Kino.Text.new(Poison.encode!(record)))
    Kino.Frame.append(json_frame, Kino.Markdown.new(prettyprint(record)))
  end
end

Kino.listen(chat_form, fn %{data: %{prompt: prompt, model: model}, origin: origin} ->
  if prompt != "" do
    prompt_md = Kino.Markdown.new("**user**: #{prompt}. Generating...")
    Kino.Frame.append(frame, prompt_md)
    response = Generator.generate(prompt, model)
    content_md = Kino.Markdown.new("**bot**: #{response}")
    Kino.Frame.append(frame, content_md)
    KinoUtils.display_tables(prompt, response, table_frame, json_frame)
  else
    content = Kino.Markdown.new("_ERROR! You need a name and message to submit..._")
    Kino.Frame.append(frame, content, to: origin)
  end
end)