Packages
llm_composer
0.11.0
0.20.2
0.20.1
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.3
0.12.2
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.8.0
0.7.0
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
LlmComposer is an Elixir library that facilitates chat interactions with language models, providing tools to handle user messages, generate responses, and execute functions automatically based on model outputs.
Current section
Files
Jump to
Current section
Files
lib/llm_composer/providers_runner.ex
defmodule LlmComposer.ProvidersRunner do
@moduledoc """
Handles provider execution logic including fallback strategies, routing,
and error handling for multiple provider configurations.
"""
alias LlmComposer.LlmResponse
alias LlmComposer.Message
alias LlmComposer.Settings
require Logger
@type messages :: [Message.t()]
@doc """
Runs provider execution with fallback support for multiple providers.
"""
@spec run(messages(), Settings.t(), Message.t()) :: {:ok, any()} | {:error, atom()}
def run(messages, %Settings{providers: [{provider, provider_opts}]} = settings, system_msg) do
# only one provider, directly run it.
provider_opts = get_provider_opts(provider_opts, settings)
provider.run(messages, system_msg, provider_opts)
end
def run(messages, %Settings{providers: providers} = settings, system_msg)
when is_list(providers) and length(providers) > 1 do
router = get_provider_router()
execute_with_fallback(router, providers, messages, system_msg, settings)
end
# old case, TODO: remove for llm_composer 0.12.0
def run(
messages,
%Settings{provider: provider, provider_opts: provider_opts} = settings,
system_msg
)
when provider != nil do
provider_opts = get_provider_opts(provider_opts, settings)
provider.run(messages, system_msg, provider_opts)
end
def run(_messages, %Settings{}, _system_msg) do
{:error, :no_providers_configured}
end
@spec execute_with_fallback(
module(),
[{module(), keyword()}],
messages(),
Message.t(),
Settings.t()
) ::
{:ok, any()} | {:error, atom()}
defp execute_with_fallback(router, all_providers, messages, system_msg, settings) do
case router.select_provider(all_providers) do
{:ok, {selected_provider, provider_opts}} ->
provider_opts = get_provider_opts(provider_opts, settings)
{exec_time_us, result} =
:timer.tc(fn ->
selected_provider.run(messages, system_msg, provider_opts)
end)
metrics = build_metrics(result, exec_time_us, selected_provider, provider_opts)
Logger.debug("#{selected_provider.name()} metrics: #{inspect(metrics)}")
case handle_provider_result(result, selected_provider, router, metrics) do
{:halt, ok_res} ->
ok_res
{:cont, _err_res} ->
execute_with_fallback(router, all_providers, messages, system_msg, settings)
end
:none_available ->
{:error, :no_providers_available}
end
end
@spec handle_provider_result(
{:ok, map()} | {:error, any()},
module(),
module(),
map()
) :: {:cont, {:error, any()}} | {:halt, {:ok, any()}}
defp handle_provider_result({:ok, _res} = ok_res, provider, router, metrics) do
router.on_provider_success(provider, ok_res, metrics)
{:halt, ok_res}
end
defp handle_provider_result({:error, error} = err_res, provider, router, metrics) do
Logger.warning(
"[#{provider.name()}] failed (#{inspect(error)}) in #{Float.round(metrics.latency_ms, 2)} ms"
)
router.on_provider_failure(provider, err_res, metrics)
# if failure, we continue with next provider if any
{:cont, err_res}
end
@spec get_provider_opts(keyword(), Settings.t()) :: keyword()
defp get_provider_opts(opts, settings) do
opts
|> Keyword.put_new(:functions, settings.functions)
|> Keyword.put_new(:stream_response, settings.stream_response)
|> Keyword.put_new(:track_costs, settings.track_costs)
|> Keyword.put_new(:api_key, settings.api_key)
end
defp get_provider_router do
:llm_composer
|> Application.get_env(:provider_router, [])
|> Keyword.get(:name, LlmComposer.ProviderRouter.Simple)
end
@spec build_metrics({:ok, LlmResponse.t()} | {:error, term()}, number(), module(), keyword()) ::
map()
defp build_metrics(result, exec_time_us, provider, provider_opts) do
latency_ms = exec_time_us / 1000
status =
case result do
{:ok, _} -> :ok
{:error, _error} -> :error
end
%{
latency_ms: latency_ms,
status: status,
provider: provider.name(),
model: Keyword.fetch!(provider_opts, :model)
}
end
end