tool calling fix
Some checks failed
CI/CD Pipeline / build (push) Failing after 4s

This commit is contained in:
2026-03-23 12:43:32 -06:00
parent e0ca44df23
commit 6ecd7d9d31
3 changed files with 54 additions and 28 deletions

View File

@@ -130,20 +130,16 @@ defmodule ElixirAi.AiTools do
# Private # Private
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
defp dispatch_to_liveview(server, tool_name, args) do defp dispatch_to_liveview(server, tool_name, args) do
case GenServer.call(server, :get_liveview_pid) do pids = GenServer.call(server, :get_liveview_pids)
nil ->
case pids do
[] ->
{:ok, "no browser session active, #{tool_name} skipped"} {:ok, "no browser session active, #{tool_name} skipped"}
liveview_pid -> _ ->
send(liveview_pid, {:liveview_tool_call, tool_name, args, self()}) Enum.each(pids, &send(&1, {:liveview_tool_call, tool_name, args}))
{:ok, "#{tool_name} sent to #{length(pids)} session(s)"}
receive do
{:liveview_tool_result, result} -> result
after
5_000 -> {:ok, "browser session timed out, #{tool_name} skipped"}
end
end end
end end
end end

View File

@@ -23,8 +23,8 @@ defmodule ElixirAi.ChatRunner do
GenServer.call(via(name), {:register_liveview_pid, liveview_pid}) GenServer.call(via(name), {:register_liveview_pid, liveview_pid})
end end
def deregister_liveview_pid(name) do def deregister_liveview_pid(name, liveview_pid) when is_pid(liveview_pid) do
GenServer.call(via(name), :deregister_liveview_pid) GenServer.call(via(name), {:deregister_liveview_pid, liveview_pid})
end end
@spec get_conversation(String.t()) :: any() @spec get_conversation(String.t()) :: any()
@@ -102,8 +102,7 @@ defmodule ElixirAi.ChatRunner do
server_tools: server_tools, server_tools: server_tools,
liveview_tools: liveview_tools, liveview_tools: liveview_tools,
provider: provider, provider: provider,
liveview_pid: nil, liveview_pids: %{}
liveview_monitor_ref: nil
}} }}
end end
@@ -352,6 +351,17 @@ defmodule ElixirAi.ChatRunner do
{:noreply, %{state | streaming_response: nil, pending_tool_calls: []}} {:noreply, %{state | streaming_response: nil, pending_tool_calls: []}}
end end
def handle_info({:DOWN, ref, :process, pid, _reason}, state) do
case Map.get(state.liveview_pids, pid) do
^ref ->
Logger.info("ChatRunner #{state.name}: LiveView #{inspect(pid)} disconnected")
{:noreply, %{state | liveview_pids: Map.delete(state.liveview_pids, pid)}}
_ ->
{:noreply, state}
end
end
def handle_call(:get_conversation, _from, state) do def handle_call(:get_conversation, _from, state) do
{:reply, state, state} {:reply, state, state}
end end
@@ -360,20 +370,24 @@ defmodule ElixirAi.ChatRunner do
{:reply, state.streaming_response, state} {:reply, state.streaming_response, state}
end end
def handle_call(:get_liveview_pid, _from, state) do def handle_call(:get_liveview_pids, _from, state) do
{:reply, state.liveview_pid, state} {:reply, Map.keys(state.liveview_pids), state}
end end
def handle_call({:register_liveview_pid, liveview_pid}, _from, state) do def handle_call({:register_liveview_pid, liveview_pid}, _from, state) do
# Clear any previous monitor
if state.liveview_monitor_ref, do: Process.demonitor(state.liveview_monitor_ref, [:flush])
ref = Process.monitor(liveview_pid) ref = Process.monitor(liveview_pid)
{:reply, :ok, %{state | liveview_pid: liveview_pid, liveview_monitor_ref: ref}} {:reply, :ok, %{state | liveview_pids: Map.put(state.liveview_pids, liveview_pid, ref)}}
end end
def handle_call(:deregister_liveview_pid, _from, state) do def handle_call({:deregister_liveview_pid, liveview_pid}, _from, state) do
if state.liveview_monitor_ref, do: Process.demonitor(state.liveview_monitor_ref, [:flush]) case Map.pop(state.liveview_pids, liveview_pid) do
{:reply, :ok, %{state | liveview_pid: nil, liveview_monitor_ref: nil}} {nil, _} ->
{:reply, :ok, state}
{ref, new_pids} ->
Process.demonitor(ref, [:flush])
{:reply, :ok, %{state | liveview_pids: new_pids}}
end
end end
def handle_call({:set_tool_choice, tool_choice}, _from, state) do def handle_call({:set_tool_choice, tool_choice}, _from, state) do
@@ -395,11 +409,6 @@ defmodule ElixirAi.ChatRunner do
}} }}
end end
def handle_info({:DOWN, ref, :process, _pid, _reason}, %{liveview_monitor_ref: ref} = state) do
Logger.info("ChatRunner #{state.name}: LiveView disconnected, clearing liveview_pid")
{:noreply, %{state | liveview_pid: nil, liveview_monitor_ref: nil}}
end
defp broadcast_ui(name, msg), defp broadcast_ui(name, msg),
do: Phoenix.PubSub.broadcast(ElixirAi.PubSub, chat_topic(name), msg) do: Phoenix.PubSub.broadcast(ElixirAi.PubSub, chat_topic(name), msg)

View File

@@ -14,6 +14,7 @@ defmodule ElixirAiWeb.ChatLive do
if connected?(socket) do if connected?(socket) do
Phoenix.PubSub.subscribe(ElixirAi.PubSub, chat_topic(name)) Phoenix.PubSub.subscribe(ElixirAi.PubSub, chat_topic(name))
:pg.join(ElixirAi.LiveViewPG, {:liveview, __MODULE__}, self()) :pg.join(ElixirAi.LiveViewPG, {:liveview, __MODULE__}, self())
ChatRunner.register_liveview_pid(name, self())
send(self(), :sync_streaming) send(self(), :sync_streaming)
end end
@@ -258,11 +259,31 @@ defmodule ElixirAiWeb.ChatLive do
{:noreply, assign(socket, ai_error: error_message, streaming_response: nil)} {:noreply, assign(socket, ai_error: error_message, streaming_response: nil)}
end end
def handle_info({:liveview_tool_call, "set_background_color", %{"color" => color}}, socket) do
{:noreply, assign(socket, background_color: color)}
end
def handle_info({:liveview_tool_call, "navigate_to", %{"path" => path}}, socket) do
{:noreply, push_navigate(socket, to: path)}
end
def handle_info({:liveview_tool_call, _tool_name, _args}, socket) do
{:noreply, socket}
end
def handle_info({:set_background_color, color}, socket) do def handle_info({:set_background_color, color}, socket) do
Logger.info("setting background color to #{color}") Logger.info("setting background color to #{color}")
{:noreply, assign(socket, background_color: color)} {:noreply, assign(socket, background_color: color)}
end end
def terminate(_reason, %{assigns: %{conversation_name: name}} = socket) do
if connected?(socket) do
ChatRunner.deregister_liveview_pid(name, self())
end
:ok
end
defp get_snapshot(%{assigns: %{runner_pid: pid}} = _socket) when is_pid(pid) do defp get_snapshot(%{assigns: %{runner_pid: pid}} = _socket) when is_pid(pid) do
case GenServer.call(pid, :get_streaming_response) do case GenServer.call(pid, :get_streaming_response) do
nil -> %{id: nil, content: "", reasoning_content: "", tool_calls: []} nil -> %{id: nil, content: "", reasoning_content: "", tool_calls: []}