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

Building a chat with OpenAI's GPT-3 and Kino

chat_with_openai_and_kino.livemd

Building a chat with OpenAI’s GPT-3 and Kino

Mix.install(
  [
    {:openai, "~> 0.1.1"},
    {:kino, "~> 0.6.1"}
  ],
  config: [
    openai: [
      api_key: "...",
      organisation_key: "..."
    ]
  ]
)

Test OpenAI

After adding your OpenAI api_key and organisation_key above, let’s create a function to wrap what we need to have a chat with our AI friend:

defmodule AI do
  def run(prompt) do
    OpenAI.completions(
      # engine_id
      "davinci",
      prompt: prompt,
      max_tokens: 60,
      temperature: 0.5,
      top_p: 1,
      presence_penalty: 0.0,
      frequency_penalty: 0.5,
      stop: "You:",
      best_of: 1
    )
  end
end

Now we can call the function and, if your API key is right, we will see an answer:

message = "Why do you like paella?"

{:ok, %{choices: [%{"text" => response}]}} = AI.run("You:#{message}\nFriend:")

response

Try changing the message above and run the cell again to see more answers. However, soon you’ll see that our AI friend doesn’t have short-term memory. Let’s improve that!

Conversation

We’ll use an agent to store all messages sent and received in a Conversation module:

defmodule Conversation do
  use Agent

  def start do
    Agent.start_link(fn -> "" end, name: __MODULE__)
  end

  def get do
    Agent.get(__MODULE__, fn conv -> conv end)
  end

  def append(message) do
    Agent.update(__MODULE__, fn conv -> conv <> message end)
  end

  def reset do
    Agent.update(__MODULE__, fn _ -> "" end)
  end

  def stop do
    Agent.stop(__MODULE__)
  end
end

Now we start the agent:

Conversation.start()

Let’s confirm it works:

Conversation.append("You:Hi!\nFriend:Hey\n")
Conversation.append("You:Do you like pizza?\nYes!\n")
Conversation.get()

Let’s improve our AI module to keep track of the conversation:

defmodule AI do
  def ask(text) do
    Conversation.append("You:" <> text <> "\nFriend:")
    conversation = Conversation.get()

    case run(conversation) do
      {:ok, %{choices: [%{"text" => response}]}} ->
        Conversation.append(response)
        response

      error ->
        IO.inspect(error)
        ""
    end
  end

  defp run(prompt) do
    OpenAI.completions(
      "davinci",
      prompt: prompt,
      max_tokens: 60,
      temperature: 0.5,
      top_p: 1,
      presence_penalty: 0.0,
      frequency_penalty: 0.5,
      stop: "You:",
      best_of: 1
    )
  end
end

Chat

Now we’ll use Kino to build our chat. To have a better understand of this part, have a look at the great tutorial Building a chat app with Kino.Control

frame = Kino.Frame.new()
inputs = [message: Kino.Input.text("Message")]
form = Kino.Control.form(inputs, submit: "Send", reset_on_submit: [:message])

As we use a stream, notice that it will run indefinitely until you stop it. New messages from you are appended to the frame and AI.ask is called to get the response of our AI friend.

Try sending a few messages.

Conversation.reset()

for %{data: %{message: message}} <- Kino.Control.stream(form) do
  content = Kino.Markdown.new("**You**: #{message}")
  Kino.Frame.append(frame, content)

  ai_message = AI.ask(message)
  ai_content = Kino.Markdown.new("**AI**: #{ai_message}")
  Kino.Frame.append(frame, ai_content)
end