Current section
Files
Jump to
Current section
Files
lib/chat.ex
defmodule HyperLLM.Chat do
@derive Jason.Encoder
@moduledoc """
HypperLLM.Chat is a single interface for interacting with LLM providers.
The interface uses the OpenAI chat completion API. https://platform.openai.com/docs/api-reference/chat
## Example
A Liveview that sends messages to the chat and updates the chat with the response.
defmodule ChatLive do
use Phoenix.LiveView
def mount(params, session, socket) do
{:ok,
socket
|> assign(chat: HyperLLM.Chat.start(model: "gpt-4o-mini"))}
end
def handle_event("send_message", %{"message" => message}, socket) do
chat = HyperLLM.Chat.append(socket.assigns.chat, message)
send(self(), :chat_completion)
{:noreply, socket |> assign(chat: chat)}
end
def handle_info(:chat_completion, socket) do
with {:ok, response} <- HyperLLM.Chat.completion(socket.assigns.chat) do
chat = HyperLLM.Chat.append(socket.assigns.chat, response)
{:noreply, socket |> assign(chat: chat)}
end
end
end
"""
@type t :: %__MODULE__{}
@type config :: [Keyword.t()]
@enforce_keys [:messages, :provider, :config]
defstruct [:messages, :provider, :config]
defmodule Message do
@derive Jason.Encoder
@moduledoc false
@type t :: %__MODULE__{}
@enforce_keys [:role, :content]
defstruct [:role, :content]
end
@doc """
Start a new chat.
## Examples
iex> HyperLLM.Chat.start(model: "gpt-4o-mini")
%HyperLLM.Chat{messages: [], provider: HyperLLM.Provider.OpenAI, config: [model: "gpt-4o-mini"]}
"""
@spec start(config()) :: t()
def start(config \\ []) when is_list(config) do
model = Keyword.fetch!(config, :model)
provider = HyperLLM.Models.provider_for!(model)
%__MODULE__{
messages: [],
provider: provider,
config: config
}
end
@spec append(t(), atom(), binary()) :: t()
def append(%__MODULE__{} = chat, role, content) when is_atom(role) do
append(chat, %Message{role: role, content: content})
end
@doc """
Append a message to the chat.
## Examples
iex> chat = HyperLLM.Chat.start(model: "gpt-4o-mini")
iex> HyperLLM.Chat.append(chat, ["Hello", "World"])
%HyperLLM.Chat{messages: [%HyperLLM.Chat.Message{role: :user, content: "Hello"}, %HyperLLM.Chat.Message{role: :user, content: "World"}], provider: HyperLLM.Provider.OpenAI, config: [model: "gpt-4o-mini"]}
iex> chat = HyperLLM.Chat.start(model: "gpt-4o-mini")
iex> HyperLLM.Chat.append(chat, "Hello")
%HyperLLM.Chat{messages: [%HyperLLM.Chat.Message{role: :user, content: "Hello"}], provider: HyperLLM.Provider.OpenAI, config: [model: "gpt-4o-mini"]}
"""
@spec append(t(), [Message.t()]) :: t()
def append(%__MODULE__{} = chat, messages) when is_list(messages) do
Enum.reduce(messages, chat, fn message, acc ->
append(acc, message)
end)
end
@spec append(t(), Message.t()) :: t()
def append(%__MODULE__{} = chat, message) when is_binary(message) do
append(chat, %Message{role: :user, content: message})
end
def append(%__MODULE__{} = chat, message) do
%{chat | messages: chat.messages ++ [message]}
end
@spec completion(t(), config()) :: binary()
def completion(%__MODULE__{} = chat, config \\ []) do
chat.provider.completion(chat.messages, Keyword.merge(chat.config, config))
end
end
defimpl String.Chars, for: HyperLLM.Chat do
def to_string(chat) do
Jason.encode!(chat)
end
end
defimpl String.Chars, for: HyperLLM.Chat.Message do
def to_string(message) do
Jason.encode!(message)
end
end