@brainlid
I asked Chat-GPT to make the code conversion to pure Phoenix Channels
code (no dependency on LiveView
) and here is the result, there seems to be more work to do since I get Jason
encoder errors related to MessageDelta{}
(protocol not implemented).
defmodule LangChainDemoWeb.AgentChatChannel do
use Phoenix.Channel
alias LangChainDemoWeb.AgentChatLive.Agent.ChatMessage
alias LangChain.Chains.LLMChain
alias LangChain.Message
alias LangChainDemo.FitnessUsers
alias LangChainDemo.FitnessLogs
alias LangChain.Message.ToolCall
alias LangChain.Message.ToolResult
# When a user joins the channel, initialize state
def join("agent_chat:lobby", _message, socket) do
current_user = FitnessUsers.get_fitness_user!(1)
llm_chain = initialize_llm_chain(current_user)
socket =
assign(socket, :current_user, current_user)
|> assign(:llm_chain, llm_chain)
|> assign(:display_messages, [
%ChatMessage{
role: :assistant,
hidden: false,
content: "Hello! I'm your personal trainer. How can I help you today?"
}
])
{:ok, socket}
end
# Handle incoming "validate" event
def handle_in("validate", %{"chat_message" => params}, socket) do
changeset = ChatMessage.create_changeset(params) |> Map.put(:action, :validate)
push(socket, "validate_response", %{form: to_form(changeset)})
{:noreply, socket}
end
# Handle incoming "save" event
def handle_in("save", %{"chat_message" => params}, socket) do
case ChatMessage.new(params) do
{:ok, message} ->
updated_socket = add_user_message(socket, message.content)
push(updated_socket, "message_saved", %{content: message.content})
{:noreply, run_chain(updated_socket)}
{:error, changeset} ->
push(socket, "save_error", %{errors: changeset.errors})
{:noreply, socket}
end
end
# Handle timezone event to update user's timezone
def handle_in("browser-timezone", %{"timezone" => timezone}, socket) do
user = socket.assigns.current_user
socket =
if timezone != user.timezone do
{:ok, updated_user} = FitnessUsers.update_fitness_user(user, %{timezone: timezone})
assign(socket, :current_user, updated_user)
else
socket
end
{:noreply, socket}
end
# Handle incoming chat delta from async processing
def handle_info({:chat_delta, delta}, socket) do
updated_chain = LLMChain.apply_delta(socket.assigns.llm_chain, delta)
push(socket, "chat_delta", %{delta: delta})
{:noreply, assign(socket, :llm_chain, updated_chain)}
end
# Handle tool execution message from async processing
def handle_info({:tool_executed, tool_message}, socket) do
message = %ChatMessage{role: tool_message.role, tool_results: tool_message.tool_results}
updated_socket = assign(socket, :llm_chain, LLMChain.add_message(socket.assigns.llm_chain, tool_message))
push(updated_socket, "tool_executed", %{tool_message: message})
{:noreply, updated_socket}
end
# Handle updated user information
def handle_info({:updated_current_user, updated_user}, socket) do
socket =
assign(socket, :current_user, updated_user)
|> assign(:llm_chain, LLMChain.update_custom_context(socket.assigns.llm_chain, %{current_user: updated_user}))
{:noreply, socket}
end
# Handle task errors in async processing
def handle_info({:task_error, reason}, socket) do
push(socket, "task_error", %{reason: reason})
{:noreply, socket}
end
# Function to initialize LLMChain
defp initialize_llm_chain(current_user) do
LLMChain.new!(%{
llm: ChatOpenAI.new!(%{model: "gpt-4", temperature: 0, request_timeout: 60_000, stream: true}),
custom_context: %{live_view_pid: self(), current_user: current_user},
verbose: false
})
end
# Handle running the LLM chain async
defp run_chain(socket) do
chain = socket.assigns.llm_chain
live_view_pid = self()
callback_fn = fn
%LangChain.MessageDelta{} = delta -> send(live_view_pid, {:chat_delta, delta})
%LangChain.Message{role: :tool} = message -> send(live_view_pid, {:tool_executed, message})
_ -> :ok
end
Task.start(fn ->
case LLMChain.run(chain, while_needs_response: true, callback_fn: callback_fn) do
{:ok, _updated_chain, _last_message} -> :ok
{:error, reason} -> send(live_view_pid, {:task_error, reason})
end
end)
assign(socket, :llm_chain, chain)
end
# Add user message to chain
defp add_user_message(socket, user_text) do
updated_chain = LLMChain.add_message(socket.assigns.llm_chain, Message.new_user!(user_text))
assign(socket, :llm_chain, updated_chain)
end
end