Any module that implements the LlmComposer.Provider behaviour can be used as a provider.
Required Callbacks
@callback name() :: atom()
@callback run([Message.t()], Message.t() | nil, keyword()) ::
{:ok, LlmResponse.t()} | {:error, term()}name/0— returns an atom identifying your provider (e.g.:my_provider).run/3— executes a completion request and returns a normalizedLlmResponse.
Minimal Implementation
defmodule MyApp.Providers.MyProvider do
@behaviour LlmComposer.Provider
alias LlmComposer.LlmResponse
alias LlmComposer.Message
@impl LlmComposer.Provider
def name, do: :my_provider
@impl LlmComposer.Provider
def run(messages, system_message, opts) do
model = Keyword.fetch!(opts, :model)
api_key = Keyword.fetch!(opts, :api_key)
body = build_request(messages, system_message, model)
case call_api(body, api_key) do
{:ok, raw} ->
response =
LlmResponse.new(%{
status: :ok,
main_response: Message.new(:assistant, raw["text"]),
provider: name(),
provider_model: model,
raw: raw
})
{:ok, response}
{:error, reason} ->
{:error, reason}
end
end
endRegistration
Pass your module via :provider (single) or :providers (multi-provider routing):
# Single provider
%LlmComposer.Settings{
provider: MyApp.Providers.MyProvider,
provider_opts: [model: "my-model", api_key: "..."]
}
# Multi-provider routing
%LlmComposer.Settings{
providers: [
{MyApp.Providers.MyProvider, [model: "my-model", api_key: "..."]}
]
}Streaming
LlmComposer.parse_stream_response/3 only supports the built-in providers. A custom provider that
streams must parse its own stream.