using providers
Some checks failed
CI/CD Pipeline / build (push) Failing after 4s

This commit is contained in:
2026-03-13 15:55:05 -06:00
parent 4de7db6f56
commit 179149b986
7 changed files with 80 additions and 37 deletions

View File

@@ -40,11 +40,11 @@ defmodule ElixirAi.ChatUtils do
} }
end end
def request_ai_response(server, messages, tools) do def request_ai_response(server, messages, tools, provider) do
Task.start(fn -> Task.start(fn ->
api_url = Application.fetch_env!(:elixir_ai, :ai_endpoint) api_url = provider.completions_url
api_key = Application.fetch_env!(:elixir_ai, :ai_token) api_key = provider.api_token
model = Application.fetch_env!(:elixir_ai, :ai_model) model = provider.model_name
if is_nil(api_url) or api_url == "" do if is_nil(api_url) or api_url == "" do
Logger.warning("AI endpoint is empty or nil") Logger.warning("AI endpoint is empty or nil")

View File

@@ -38,12 +38,18 @@ defmodule ElixirAi.ChatRunner do
last_message = List.last(messages) last_message = List.last(messages)
provider =
case Conversation.find_provider(name) do
{:ok, p} -> p
_ -> nil
end
if last_message && last_message.role == :user do if last_message && last_message.role == :user do
Logger.info( Logger.info(
"Last message role was #{last_message.role}, requesting AI response for conversation #{name}" "Last message role was #{last_message.role}, requesting AI response for conversation #{name}"
) )
ElixirAi.ChatUtils.request_ai_response(self(), messages, tools(self(), name)) ElixirAi.ChatUtils.request_ai_response(self(), messages, tools(self(), name), provider)
end end
{:ok, {:ok,
@@ -53,9 +59,7 @@ defmodule ElixirAi.ChatRunner do
streaming_response: nil, streaming_response: nil,
pending_tool_calls: [], pending_tool_calls: [],
tools: tools(self(), name), tools: tools(self(), name),
ai_provider_url: Application.get_env(:elixir_ai, :ai_provider_url), provider: provider
ai_model: Application.get_env(:elixir_ai, :ai_model),
ai_token: Application.get_env(:elixir_ai, :ai_token)
}} }}
end end
@@ -107,7 +111,13 @@ defmodule ElixirAi.ChatRunner do
store_message(state.name, new_message) store_message(state.name, new_message)
new_state = %{state | messages: state.messages ++ [new_message]} new_state = %{state | messages: state.messages ++ [new_message]}
ElixirAi.ChatUtils.request_ai_response(self(), new_state.messages, state.tools) ElixirAi.ChatUtils.request_ai_response(
self(),
new_state.messages,
state.tools,
state.provider
)
{:noreply, new_state} {:noreply, new_state}
end end
@@ -296,7 +306,13 @@ defmodule ElixirAi.ChatRunner do
if new_pending_tool_calls == [] do if new_pending_tool_calls == [] do
broadcast_ui(state.name, :tool_calls_finished) broadcast_ui(state.name, :tool_calls_finished)
ElixirAi.ChatUtils.request_ai_response(self(), state.messages ++ [new_message], state.tools)
ElixirAi.ChatUtils.request_ai_response(
self(),
state.messages ++ [new_message],
state.tools,
state.provider
)
end end
{:noreply, {:noreply,

View File

@@ -52,25 +52,16 @@ defmodule ElixirAi.ConversationManager do
def handle_call( def handle_call(
{:create, name, ai_provider_id}, {:create, name, ai_provider_id},
_from, _from,
%{conversations: conversations, subscriptions: subscriptions} = state %{conversations: conversations} = state
) do ) do
if Map.has_key?(conversations, name) do if Map.has_key?(conversations, name) do
{:reply, {:error, :already_exists}, state} {:reply, {:error, :already_exists}, state}
else else
case Conversation.create(name, ai_provider_id) do case Conversation.create(name, ai_provider_id) do
:ok -> :ok ->
case start_and_subscribe(name, subscriptions) do reply_with_started(name, state, fn new_state ->
{:ok, pid, new_subscriptions} -> %{new_state | conversations: Map.put(new_state.conversations, name, [])}
{:reply, {:ok, pid}, end)
%{
state
| conversations: Map.put(conversations, name, []),
subscriptions: new_subscriptions
}}
{:error, _reason} = error ->
{:reply, error, state}
end
{:error, _} = error -> {:error, _} = error ->
{:reply, error, state} {:reply, error, state}
@@ -81,16 +72,10 @@ defmodule ElixirAi.ConversationManager do
def handle_call( def handle_call(
{:open, name}, {:open, name},
_from, _from,
%{conversations: conversations, subscriptions: subscriptions} = state %{conversations: conversations} = state
) do ) do
if Map.has_key?(conversations, name) do if Map.has_key?(conversations, name) do
case start_and_subscribe(name, subscriptions) do reply_with_started(name, state)
{:ok, pid, new_subscriptions} ->
{:reply, {:ok, pid}, %{state | subscriptions: new_subscriptions}}
{:error, _reason} = error ->
{:reply, error, state}
end
else else
{:reply, {:error, :not_found}, state} {:reply, {:error, :not_found}, state}
end end
@@ -148,6 +133,17 @@ defmodule ElixirAi.ConversationManager do
end end
end end
defp reply_with_started(name, state, update_state \\ fn s -> s end) do
case start_and_subscribe(name, state.subscriptions) do
{:ok, pid, new_subscriptions} ->
new_state = update_state.(%{state | subscriptions: new_subscriptions})
{:reply, {:ok, pid}, new_state}
{:error, _reason} = error ->
{:reply, error, state}
end
end
defp start_and_subscribe(name, subscriptions) do defp start_and_subscribe(name, subscriptions) do
result = result =
case Horde.DynamicSupervisor.start_child( case Horde.DynamicSupervisor.start_child(

View File

@@ -113,5 +113,23 @@ defmodule ElixirAi.Conversation do
end end
end end
def find_provider(name) do
sql = """
SELECT p.name, p.model_name, p.api_token, p.completions_url
FROM conversations c
JOIN ai_providers p ON c.ai_provider_id = p.id
WHERE c.name = $(name)
LIMIT 1
"""
params = %{"name" => name}
case DbHelpers.run_sql(sql, params, "conversations", Provider.schema()) do
{:error, _} -> {:error, :db_error}
[] -> {:error, :not_found}
[row | _] -> {:ok, struct(Provider, row)}
end
end
defp now, do: DateTime.truncate(DateTime.utc_now(), :second) defp now, do: DateTime.truncate(DateTime.utc_now(), :second)
end end

View File

@@ -48,7 +48,10 @@ defmodule ElixirAi.MessageStorageTest do
test "run_sql is called with user message params" do test "run_sql is called with user message params" do
conv_name = setup_conversation() conv_name = setup_conversation()
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools -> :ok end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools, _provider ->
:ok
end)
ElixirAi.ChatRunner.new_user_message(conv_name, "hello world") ElixirAi.ChatRunner.new_user_message(conv_name, "hello world")
@@ -60,7 +63,7 @@ defmodule ElixirAi.MessageStorageTest do
test "run_sql is called with assistant message params" do test "run_sql is called with assistant message params" do
conv_name = setup_conversation() conv_name = setup_conversation()
stub(ElixirAi.ChatUtils, :request_ai_response, fn server, _messages, _tools -> stub(ElixirAi.ChatUtils, :request_ai_response, fn server, _messages, _tools, _provider ->
id = make_ref() id = make_ref()
send(server, {:start_new_ai_response, id}) send(server, {:start_new_ai_response, id})
send(server, {:ai_text_chunk, id, "Hello from AI"}) send(server, {:ai_text_chunk, id, "Hello from AI"})
@@ -80,7 +83,7 @@ defmodule ElixirAi.MessageStorageTest do
conv_name = setup_conversation() conv_name = setup_conversation()
# First AI call triggers the tool; subsequent calls (after tool completes) are no-ops. # First AI call triggers the tool; subsequent calls (after tool completes) are no-ops.
expect(ElixirAi.ChatUtils, :request_ai_response, fn server, _messages, _tools -> expect(ElixirAi.ChatUtils, :request_ai_response, fn server, _messages, _tools, _provider ->
id = make_ref() id = make_ref()
send(server, {:start_new_ai_response, id}) send(server, {:start_new_ai_response, id})
@@ -93,7 +96,9 @@ defmodule ElixirAi.MessageStorageTest do
:ok :ok
end) end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools -> :ok end) stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools, _provider ->
:ok
end)
ElixirAi.ChatRunner.new_user_message(conv_name, "store something") ElixirAi.ChatRunner.new_user_message(conv_name, "store something")

View File

@@ -39,7 +39,11 @@ defmodule ElixirAiWeb.ConnCase do
setup _tags do setup _tags do
stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic -> [] end) stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic -> [] end)
stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic, _schema -> [] end) stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic, _schema -> [] end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools -> :ok end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools, _provider ->
:ok
end)
{:ok, conn: Phoenix.ConnTest.build_conn()} {:ok, conn: Phoenix.ConnTest.build_conn()}
end end
end end

View File

@@ -13,7 +13,11 @@ defmodule ElixirAi.TestCase do
setup do setup do
stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic -> [] end) stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic -> [] end)
stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic, _schema -> [] end) stub(ElixirAi.Data.DbHelpers, :run_sql, fn _sql, _params, _topic, _schema -> [] end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools -> :ok end)
stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools, _provider ->
:ok
end)
:ok :ok
end end
end end