ExAthena.Provider behaviour (ExAthena v0.18.0)

Copy Markdown View Source

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:

  1. Normalise the request into the provider's native wire format.
  2. Perform the HTTP call (or SDK call, for Claude).
  3. Parse the response (or stream) back into an ExAthena.Response / ExAthena.Streaming.Event sequence.
  4. 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

capabilities()

@callback capabilities() :: ExAthena.Capabilities.t()

Static capability map for this provider.

capabilities(opts)

(optional)
@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.

embed(input, opts)

(optional)
@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.

list_models()

(optional)
@callback list_models() :: {:ok, [String.t()]} | {:error, term()}

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.

list_models(opts)

(optional)
@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.

query(t, opts)

@callback query(ExAthena.Request.t(), opts :: keyword()) ::
  {:ok, ExAthena.Response.t()} | {:error, term()}

Perform a one-shot request and return the final response.

stream(t, function, opts)

(optional)
@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.