diff --git a/lib/elixir_ai/ai_utils/chat_utils.ex b/lib/elixir_ai/ai_utils/chat_utils.ex index 4c9e894..31ed406 100644 --- a/lib/elixir_ai/ai_utils/chat_utils.ex +++ b/lib/elixir_ai/ai_utils/chat_utils.ex @@ -40,11 +40,11 @@ defmodule ElixirAi.ChatUtils do } end - def request_ai_response(server, messages, tools) do + def request_ai_response(server, messages, tools, provider) do Task.start(fn -> - api_url = Application.fetch_env!(:elixir_ai, :ai_endpoint) - api_key = Application.fetch_env!(:elixir_ai, :ai_token) - model = Application.fetch_env!(:elixir_ai, :ai_model) + api_url = provider.completions_url + api_key = provider.api_token + model = provider.model_name if is_nil(api_url) or api_url == "" do Logger.warning("AI endpoint is empty or nil") diff --git a/lib/elixir_ai/chat_runner.ex b/lib/elixir_ai/chat_runner.ex index bc25266..0519ea5 100644 --- a/lib/elixir_ai/chat_runner.ex +++ b/lib/elixir_ai/chat_runner.ex @@ -38,12 +38,18 @@ defmodule ElixirAi.ChatRunner do 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 Logger.info( "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 {:ok, @@ -53,9 +59,7 @@ defmodule ElixirAi.ChatRunner do streaming_response: nil, pending_tool_calls: [], tools: tools(self(), name), - ai_provider_url: Application.get_env(:elixir_ai, :ai_provider_url), - ai_model: Application.get_env(:elixir_ai, :ai_model), - ai_token: Application.get_env(:elixir_ai, :ai_token) + provider: provider }} end @@ -107,7 +111,13 @@ defmodule ElixirAi.ChatRunner do store_message(state.name, 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} end @@ -296,7 +306,13 @@ defmodule ElixirAi.ChatRunner do if new_pending_tool_calls == [] do 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 {:noreply, diff --git a/lib/elixir_ai/conversation_manager.ex b/lib/elixir_ai/conversation_manager.ex index 8ae6f02..2a9df0a 100644 --- a/lib/elixir_ai/conversation_manager.ex +++ b/lib/elixir_ai/conversation_manager.ex @@ -52,25 +52,16 @@ defmodule ElixirAi.ConversationManager do def handle_call( {:create, name, ai_provider_id}, _from, - %{conversations: conversations, subscriptions: subscriptions} = state + %{conversations: conversations} = state ) do if Map.has_key?(conversations, name) do {:reply, {:error, :already_exists}, state} else case Conversation.create(name, ai_provider_id) do :ok -> - case start_and_subscribe(name, subscriptions) do - {:ok, pid, new_subscriptions} -> - {:reply, {:ok, pid}, - %{ - state - | conversations: Map.put(conversations, name, []), - subscriptions: new_subscriptions - }} - - {:error, _reason} = error -> - {:reply, error, state} - end + reply_with_started(name, state, fn new_state -> + %{new_state | conversations: Map.put(new_state.conversations, name, [])} + end) {:error, _} = error -> {:reply, error, state} @@ -81,16 +72,10 @@ defmodule ElixirAi.ConversationManager do def handle_call( {:open, name}, _from, - %{conversations: conversations, subscriptions: subscriptions} = state + %{conversations: conversations} = state ) do if Map.has_key?(conversations, name) do - case start_and_subscribe(name, subscriptions) do - {:ok, pid, new_subscriptions} -> - {:reply, {:ok, pid}, %{state | subscriptions: new_subscriptions}} - - {:error, _reason} = error -> - {:reply, error, state} - end + reply_with_started(name, state) else {:reply, {:error, :not_found}, state} end @@ -148,6 +133,17 @@ defmodule ElixirAi.ConversationManager do 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 result = case Horde.DynamicSupervisor.start_child( diff --git a/lib/elixir_ai/data/conversation.ex b/lib/elixir_ai/data/conversation.ex index da52d89..77e055c 100644 --- a/lib/elixir_ai/data/conversation.ex +++ b/lib/elixir_ai/data/conversation.ex @@ -113,5 +113,23 @@ defmodule ElixirAi.Conversation do 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) end diff --git a/test/message_storage_test.exs b/test/message_storage_test.exs index 72fad3d..3cc0e0b 100644 --- a/test/message_storage_test.exs +++ b/test/message_storage_test.exs @@ -48,7 +48,10 @@ defmodule ElixirAi.MessageStorageTest do test "run_sql is called with user message params" do 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") @@ -60,7 +63,7 @@ defmodule ElixirAi.MessageStorageTest do test "run_sql is called with assistant message params" do 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() send(server, {:start_new_ai_response, id}) send(server, {:ai_text_chunk, id, "Hello from AI"}) @@ -80,7 +83,7 @@ defmodule ElixirAi.MessageStorageTest do conv_name = setup_conversation() # 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() send(server, {:start_new_ai_response, id}) @@ -93,7 +96,9 @@ defmodule ElixirAi.MessageStorageTest do :ok 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") diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index a621f33..709479b 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -39,7 +39,11 @@ defmodule ElixirAiWeb.ConnCase 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, _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()} end end diff --git a/test/support/test_case.ex b/test/support/test_case.ex index 9c47659..55fe24d 100644 --- a/test/support/test_case.ex +++ b/test/support/test_case.ex @@ -13,7 +13,11 @@ defmodule ElixirAi.TestCase do setup do 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.ChatUtils, :request_ai_response, fn _server, _messages, _tools -> :ok end) + + stub(ElixirAi.ChatUtils, :request_ai_response, fn _server, _messages, _tools, _provider -> + :ok + end) + :ok end end