Behaviour every provider must implement.
A provider is a thin adapter between ExAthena.Request and a remote (or
local) inference endpoint. It is expected to:
- Normalise the request into the provider's native wire format.
- Perform the HTTP call (or SDK call, for Claude).
- Parse the response (or stream) back into an
ExAthena.Response/ExAthena.Streaming.Eventsequence. - Surface errors as
{:error, %ExAthena.Error{}}tuples using the canonical kinds.
Capabilities
Each provider declares its capabilities statically. The loop uses these to
decide the tool-call protocol and fallback strategy. See
ExAthena.Capabilities for the shape.
Summary
Callbacks
Static capability map for this provider.
Model-aware capability map. Receives the per-call opts (includes
:req_llm_provider_tag and :model) so the provider can resolve
the actual context window from a catalog (e.g. llm_db) and return
a precise max_tokens instead of a static default.
Embed one or more texts. Optional — providers that cannot embed simply omit
the callback and declare embeddings: false (or nothing) in capabilities/0;
ExAthena.embed/2 then returns a :capability error instead of crashing.
Lists the model identifiers this provider can serve, for UI selection.
Config-aware model listing, returning normalised ExAthena.Model structs.
Perform a one-shot request and return the final response.
Stream a request; callback is invoked with each Streaming.Event. Must
still return {:ok, final_response} when the stream completes normally.
Callbacks
@callback capabilities() :: ExAthena.Capabilities.t()
Static capability map for this provider.
@callback capabilities(opts :: keyword()) :: ExAthena.Capabilities.t()
Model-aware capability map. Receives the per-call opts (includes
:req_llm_provider_tag and :model) so the provider can resolve
the actual context window from a catalog (e.g. llm_db) and return
a precise max_tokens instead of a static default.
When not implemented, the loop falls back to capabilities/0.
@callback embed(input :: String.t() | [String.t()], opts :: keyword()) :: {:ok, ExAthena.Embedding.t()} | {:error, term()}
Embed one or more texts. Optional — providers that cannot embed simply omit
the callback and declare embeddings: false (or nothing) in capabilities/0;
ExAthena.embed/2 then returns a :capability error instead of crashing.
input is a single string or a list of strings; the returned
ExAthena.Embedding always carries one vector per input, in input order.
The model arrives as opts[:model], already resolved from the caller's
:model or the provider's :embedding_model config by ExAthena.embed/2.
Lists the model identifiers this provider can serve, for UI selection.
Returns {:ok, [model_string]} or {:error, reason}. Providers that cannot
enumerate their models (the model is free-form) simply omit this callback; the
UI then falls back to a free-text model input.
@callback list_models(opts :: keyword()) :: {:ok, [ExAthena.Model.t()]} | {:error, term()}
Config-aware model listing, returning normalised ExAthena.Model structs.
Preferred over list_models/0: which models exist depends on where you are
pointed (two Ollama daemons have different models installed) and on your
credentials, so listing needs the same per-call opts as query/2 —
:base_url, :api_key, :req_llm_provider_tag, :openai_compatible_backend.
This mirrors capabilities/0 vs capabilities/1.
ExAthena.list_models/2 prefers this callback and falls back to
list_models/0, wrapping its bare strings, so a provider need only implement
one. Advertise support with model_listing: true in capabilities/0.
@callback query(ExAthena.Request.t(), opts :: keyword()) :: {:ok, ExAthena.Response.t()} | {:error, term()}
Perform a one-shot request and return the final response.
@callback stream( ExAthena.Request.t(), (ExAthena.Streaming.Event.t() -> term()), opts :: keyword() ) :: {:ok, ExAthena.Response.t()} | {:error, term()}
Stream a request; callback is invoked with each Streaming.Event. Must
still return {:ok, final_response} when the stream completes normally.