# `LlmComposer.ProviderRouter`
[🔗](https://github.com/doofinder/llm_composer/blob/master/lib/llm_composer/provider_router.ex#L1)

Behaviour for implementing provider routing strategies.

Allows customization of how providers are selected and how failures are handled.
This enables users to implement custom logic for provider failover, circuit breaking,
load balancing, or any other routing strategy.

## Example Usage

```elixir
defmodule MyApp.SimpleRouter do
  @behaviour LlmComposer.ProviderRouter

  @impl true
  def on_provider_success(_provider, _response, _metrics) do
    # Could log success metrics here
    :ok
  end

  @impl true
  def on_provider_failure(_provider, error, _metrics) do
    # Block on server errors, continue on client errors
    case error do
      %{status: status} when status >= 500 -> :block
      _ -> :continue
    end
  end

  @impl true
  def select_provider(providers) when length(providers) > 0 do
    # Simple random selection
    {provider, opts} = Enum.random(providers)
    {:ok, {provider, opts}}
  end

  def select_provider([]), do: :none_available
end
```

# `error`

```elixir
@type error() :: term()
```

# `failure_response`

```elixir
@type failure_response() :: :block
```

# `metrics`

```elixir
@type metrics() :: map()
```

# `ok_res`

```elixir
@type ok_res() :: {:ok, Tesla.Env.t()}
```

# `provider`

```elixir
@type provider() :: module() | atom()
```

# `providers`

```elixir
@type providers() :: [{provider(), keyword()}]
```

# `cleanup`
*optional* 

```elixir
@callback cleanup() :: :ok
```

Optional callback to clean up resources when the router is no longer needed.

This can be used to:
- Clean up ETS tables
- Stop monitoring processes
- Release any held resources

If not implemented, no cleanup is performed.

# `init`
*optional* 

```elixir
@callback init(otps :: keyword()) :: :ok
```

Optional callback to initialize any resources needed by the router.

This is called once when the router is first used and can be used to:
- Initialize ETS tables
- Start monitoring processes
- Set up any required state

If not implemented, no initialization is performed.

# `on_provider_failure`

```elixir
@callback on_provider_failure(provider(), error(), map()) :: failure_response()
```

Called when a provider fails to handle a request.

This callback allows the router to decide how to handle the failure
and whether the provider should be temporarily blocked.

## Returns
- `:block` - temporarily block this provider (using default blocking duration)

## Parameters
- `provider` - The provider module that failed
- `error` - The error returned by the provider

# `on_provider_success`

```elixir
@callback on_provider_success(provider(), ok_res(), metrics()) :: :ok
```

Called when a provider successfully handles a request.

This callback can be used to:
- Reset failure counters
- Update success metrics
- Adjust provider weights
- Close circuit breakers
- Log successful interactions

## Parameters
- `provider` - The provider module that succeeded

# `select_provider`

```elixir
@callback select_provider(providers :: providers()) ::
  {:ok, {provider(), keyword()}} | :none_available
```

Selects an eligible provider from the given list.

This callback allows the router to implement custom logic for selecting the
next provider to use, potentially based on health, load, or other criteria.

## Returns
- `{:ok, provider}` - The selected provider module.
- `:none_available` - No eligible providers are currently available.

## Parameters
- `providers` - A list of `{:provider_module, provider_opts}` tuples.

# `start_link`
*optional* 

```elixir
@callback start_link(args :: term()) :: {:ok, pid()} | {:error, term()}
```

Starts the process linked to the current process.

This callback is optional. If implemented, it should start the process with the given arguments
and return `{:ok, pid}` on success or `{:error, reason}` on failure.

## Parameters

  - `args`: The arguments required to start the process.

## Returns

  - `{:ok, pid}` when the process is successfully started and linked.
  - `{:error, reason}` if the process could not be started.

Implement this callback to support supervised or linked process initialization for your module.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
